100 Hour Game—Day 6

Hours Remaining: 60

After the excitement of working on a text adventure game engine, I’ve gone back to Barista while I wait for me and Mike’s schedules to line up and today I have mostly been thinking about: data structures!

Hold on, things are about to get craaaazy.

After finding this excellent online book about game programming patterns and reading through the first few patterns, I realised I needed to rethink my approach. Originally, I was going to use a component/entity system but I have now come to realised that it is overkill for such a straightforward game. The value from that level of abstraction only comes when dealing with much more complex systems than this game will ever have.

I listed out all of the entities within the game and it quickly became apparent that they basically fall into one of two camps, which I have named Actor and Zone. Actors are lockable, either on top (in the case of the Portafilters) or on the bottom (the cups and the milk jug). The classes for the Actor and Zone are very simple, and the lock location can be defined with a simple enum:

enum LockLocation {  
    case Top, Bottom  
}

class Actor : SKSpriteNode {  
    var lockLocation : LockLocation!  
}

class Zone : SKSpriteNode {

    var accepts : LockLocation!  
    weak var currentActor : Actor?

    func canAccept(sprite : Actor ) -> Bool {  
        if ( sprite.lockLocation == self.accepts ) {  
            self.currentActor = sprite  
            return true  
        }
        return false  
    }
}

Zones can define what lockable attributes they accept (either top or bottom). If they’re passed an actor that they can accept, then they keep hold of a reference to that Actor. This will be important later, as Zones can also dispense things (coffee, milk, steam) and they’ll need to figure out if their currently attached Actor can accept them.

Barista gif animation showing sprite blocks being dragged around
Day 6 progress

In the animation above, you can see the yellow blocks (representing the portafilters) can lock into either the brew heads or the grinder (at the far right). The cups can lock into either the drip tray, or the serving tray on the far left.

Reducing all the entities into one of these two classes has made collision detection much simpler:

func didBeginContact(contact: SKPhysicsContact) {

    var actor : Actor!  
    var zone : Zone!  
    if let bodyA = contact.bodyA.node as? Zone, bodyB = contact.bodyB.node as? Actor {  
        zone = bodyA  
        actor = bodyB  
    } else if let bodyA = contact.bodyA.node as? Actor, bodyB = contact.bodyB.node as? Zone {  
        actor = bodyA  
        zone = bodyB  
    } else {  
        // Only interested in interactions between Actors and Zones  
        return  
    }

    // Keep a copy around to create a buffer zone if the player starts dragging  
    self.zoneNode = zone

    // Touching a locked node might cause this method to be called a few times  
    // so don't re-lock the Actor back into the Zone prematurely  
    if zone.currentActor == actor {  
        return  
    }

    // Can this zone accept an actor?  
    if zone.canAccept(actor ) {  
        // If so, cancel any velocity  
        actor.physicsBody?.velocity = CGVector.zero  
        self.actorNode = actor  
        self.newPosition = CGPoint(x: zone.position.x, y: zone.position.y)  
        self.endTouches()  
    }
}

Now none of the entities need to be explicitly defined in a contact category and there’s no need for complex conditional statements to figure out who’s touching whom (ahem).

Tomorrow I’ll work on getting the Actors receiving what the Zones have to give them.