Closed mctaylorpants closed 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.
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
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.
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!
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.
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
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.
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!
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:
OH MY GOD didEndContact is the answer to all of your worries!
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?
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?
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.
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 ;)
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..
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.
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...
Traffic has been implemented and spawns from a Tilemap.
Add "enemy" traffic, player should respond to collisions (not sure what should happen yet)