mctaylorpants / AmbulanceGame

Working title: AMBULANCE RUSH
0 stars 1 forks source link

Implement Traffic #15

Closed mctaylorpants closed 9 years ago

mctaylorpants commented 9 years ago

Add "enemy" traffic, player should respond to collisions (not sure what should happen yet)

mctaylorpants commented 9 years ago

My idea is that traffic could be spawned just like everything else. For simplicity, maybe the traffic should only travel in one direction, and would disappear when it reaches a T-intersection or corner. If the traffic is onscreen at one of these junctions, the traffic should stop.

mctaylorpants commented 9 years ago

Okay, actually going to try navigating traffic through turns. That way there's a fixed, consistent amount of traffic on the map at all times.

Rules for Traffic

States

mctaylorpants commented 9 years ago

The basic traffic sprite/control structure is implemented in the commit above. Before going further I need to refactor the player movement code into a superclass so I can share it between the player and the traffic. This will be tracked under issue #17.

mctaylorpants commented 9 years ago

Need to think about the update loop for traffic. Look at how Adventure does it; every traffic vehicle gets added to an array, which makes it easier to enumerate - you don't have to search all the child nodes. The scene itself loops through the characters, and the character's update loop calls the AI update loop.

When the character gets instantiated, the AI is hooked up via the intelligence property. So during the character's update loop, you can run update on the intelligence object. They're doing this because of the multiple classes of AI we have, so we probably don't need to do that.

In fact, we could implement all of this AI within the TrafficVehicle class, since it's the only thing that has AI right now. That oughta simplify things!

mctaylorpants commented 9 years ago

For the "tailgating" detection: make two nodes that can act as collision zones. The first (largest) zone will be the tailgating zone - if there's another car within that zone, the car will adjust speed. The smaller zone will cause the car to stop completely, for instance if a player is stopped in front.

It might be simpler to set up one physics body with a specific size, and test for contact between that body and other moving characters. Then within the contact code we measure the length between the car in front and the current car, and stop if they're within a certain distance.

mctaylorpants commented 9 years ago

Good progress on traffic. Hooked up a collision zone body (currently an SKSpriteNode but it should really be an SKNode after debugging)

The idea is that the collision zone would always be in front of the traffic vehicle, and it would detect collisions with traffic, not with other collision zones. This way we always know what vehicle we should be manipulating. So the collision zone can collide with players and traffic vehicles directly.

I just refactored some things to allow the collision logic to be passed off from didBeginContact to a collidedWith: method that should be implemented for each Character class that needs it.

Verified that the collision detection still works for the patient/player/hospital stuff. Hook up traffic and start playing around!

Commit e2bf69044dfc89ca978e802ee4c782ac76a4a6b8

mctaylorpants commented 9 years ago

Looks like we have to move authorizeMoveEvent into the MovingCharacter superclass, since TrafficVehicle needs to call it directly.

Inputs:

We just need to get a reference to the node's scene, casted as LevelScene, and make all those properties public.

mctaylorpants commented 9 years ago

Found a bug where when two traffic vehicles are following one another, they'll overlap if they encounter a stopped vehicle. This is because the rear car's collision zone permanently overlaps with the front car's body, so it never continues to check if that car has stopped. What ends up happening is all of the moving cars end up colliding with the same stopped vehicle!

mctaylorpants commented 9 years ago

Ok the bug isn't actually fixed, but I did change the adjustSpeed code so that it slows down more than the vehicle in front at first, THEN matches the speed. Continue to work on this:

mctaylorpants commented 9 years ago

OH MY GOD didEndContact is the answer to all of your worries!

mctaylorpants commented 9 years ago

See: http://gamedevelopment.tutsplus.com/tutorials/finite-state-machines-theory-and-implementation--gamedev-11867

mctaylorpants commented 9 years ago

For the FSM: The virtual method calls are good, but we need a way to handle the collisions in different ways. What if didBeginContact and didEndContact just updated variables in the class that were pointers to which nodes the collision was involved in?

mctaylorpants commented 9 years ago

Started to implement the class-based state machine today. I think this is important because it's clear the traffic AI is getting quite complex, and I'm already doing a lot of branching within multiple methods to handle all the states.

I've created a AMBTrafficVehicleState class. This class will have declarations for each method that the states should implement (update, didBeginContact, didEndContact, etc).

Each state is a subclass of AMBTrafficVehicleState - for example, AMBTrafficVehicleIsDrivingStraight. For simplicity, these states are declared within the same .h and .m files as the superclass. They should each implement the methods defined in their parent. They should take an object of type id which will be the context object (the traffic vehicle itself). Here's an example of the implementation for AMBTrafficVehicleIsDrivingStraight:

@implementation AMBTrafficVehicleIsDrivingStraight

- (void)stateTest:(id)vehicle {
    AMBTrafficVehicle *trafficVehicle = (AMBTrafficVehicle *)vehicle;
    NSLog(@"State: VehicleIsDrivingStraight: %@", trafficVehicle.name);
}

@end

Within AMBTrafficVehicle.m, we implement this like so:

#import "AMBTrafficVehicleState.h"

...

@interface AMBTrafficVehicle ()
...
@property AMBTrafficVehicleState *currentState;
...

We define the current state in the createVehicle method like this:

    vehicle.currentState = [[AMBTrafficVehicleIsDrivingStraight alloc]init];
    [vehicle stateTest];

stateTest is implemented in AMBTrafficVehicle, which handles the dynamic dispatch:

- (void)stateTest {
    // this stateTest will call through to the stateTest method implemented by the current state.
    [_currentState performSelector:@selector(stateTest:) withObject:self];
}

This is currently working. One thing we should look into is what happens to these state objects if I'm creating them like I am in createVehicle. Should these be static objects like in the State tutorial? We'll probably learn more as we try to actually switch states and whatnot.

Side Q: can delegation do what I'm trying to do here?

mctaylorpants commented 9 years ago

Matured the class-based state machine further.

The superclass AMBTrafficVehicleState and all children implement the following methods:

/** Called by the context object when switching states. */
- (void)enterState:(AMBTrafficVehicle *)vehicle;

/** Called by the state object when exiting the current state. */
- (void)exitState:(AMBTrafficVehicle *)vehicle;

/** State-specific method for handling new collision events. */
- (AMBTrafficVehicleState *)beganCollision:(SKPhysicsContact *)contact context:(AMBTrafficVehicle *)vehicle;

/** State-specific method for handling the end of collision events. */
- (AMBTrafficVehicleState *)endedCollision:(SKPhysicsContact *)contact context:(AMBTrafficVehicle *)vehicle;

/** State-specific update method. */
- (AMBTrafficVehicleState *)updateWithTimeSinceLastUpdate:(CFTimeInterval)delta context:(AMBTrafficVehicle *)vehicle;

The states themselves are objects which can either be static or instantiated, depending on the state. For instance, AMBTrafficVehicleIsDrivingStraight is a singleton class which is instantiated by calling its sharedInstance method. This is because there's no unique properties in the state. On the other hand, AMBTrafficVehicleIsStopped should be an instantiated state, because it keeps track of the vehicle that caused it to stop and waits for it to move.

Each method in AMBTrafficVehicle which needs to call through to its state method handles calling the state's method, as well as changing the current state. If the current state wishes to transition to a new state, it returns a new state object at the end of its method. For example:

AMBTrafficVehicleState.m

@implementation AMBTrafficVehicleIsDrivingStraight

- (AMBTrafficVehicleState *)beganCollision:(SKPhysicsContact *)contact context:(AMBTrafficVehicle *)vehicle {
   // collision code
   // we return nil if the context object should stay in this state. We can return a new state object (e.g. VehicleIsStopped) if a transition needs to occur.   
    return nil;    
}

...

AMBTrafficVehicle.m

- (void)beganCollision:(SKPhysicsContact *)contact {
    AMBTrafficVehicleState *newState = [_currentState beganCollision:contact  context:self];

    if (newState) {
        _currentState = newState;
        [_currentState enterState:self];
    }
}

The context's method should always call enterState if it's performing a transition. exitState can be called by the state's own method before it returns a new state object.

To kick everything off, we set an initial state during the vehicle's +createVehicle method:

    ...  

    vehicle.currentState = [AMBTrafficVehicleIsDrivingStraight sharedInstance];
    [vehicle.currentState enterState:vehicle];

    return vehicle;

Things to work on: flesh out the state methods for DrivingStraight, and begin to work on the transitions. For beganContact and endedContact, I'm just passing through the SKPhysicsContact object to work on it directly.

mctaylorpants commented 9 years ago

Implemented a basic driving straight/adjusting speed/stopped state. Seems to work pretty well.

If all the traffic turns RIGHT every time it encounters an intersection, it will simplify the design. We just place a bunch of traffic around each block, and they continuously circle the block which should hopefully create the feeling of lots of traffic if you don't look at it close enough ;)

mctaylorpants commented 9 years ago

Merged traffic commits with first_draft. Next step: spawning traffic as instances of AMBTrafficVehicle instead of just static as they are now. Trying to decide what to do about placement of traffic in Tiled. I can easily place them as objects, but they're hard to see. Or I could place them as tiles, but they have to be 256x256 so that limits the control I have in placing them..

mctaylorpants commented 9 years ago

Working on traffic placement. Traffic can be placed on the "spawn_traffic" tile layer in Tiled, which will be hidden during gameplay but the sprites will be used to spawn traffic vehicles, and their "center_x" and "center_y" properties will be used to position the sprite in the lane.

mctaylorpants commented 9 years ago

Looks like the best way to parse the spawns from a tile layer is to loop through the coordinates. There's no array of the objects like in an object layer...

mctaylorpants commented 9 years ago

Traffic has been implemented and spawns from a Tilemap.