Magic enums

There's a talk (click here to go to it) from WWDC 2015 which discusses uses of Swift enumerations in its second half. When I first watched it, I was somewhat suspicious that it was overhyping enums as "magical unicorn solutions" to all problems. However, as I've worked on my game, I've found a great deal of use cases for enums. Here are some examples of how I've used enums and what I've learned about them:

Raw values limited, computed vars unlimited


I made function that checks if two rectangles intersect in any of the four cardinal directions by using subrectangles (see the diagram below).  I use the function to allow the player to jump only when standing on top of the ground. (I actually use a spiffy PhysicsSensor class more often than this function, but more on that later).

... and therefore it is legal for the player to jump. Note: I actually run this check by enumerating over all of the obstacles in the scene, as the player could be standing on any of them.

The function returns an array of enums representing which directions had intersections between the two rectangles:

enum Direction {
    case Right, Up, Left, Down
    enum Axis {case Horizontal, Vertical}
    var axis: Axis {return [Direction.Right, Direction.Left].contains(self) ? .Horizontal : .Vertical}

}

My first attempt didn't use the 'axis' variable, but instead had the Axis enum as the raw value of 'Direction.' Unfortunately, the compiler didn't allow this because, according to the Swift Language Book"Raw values can be strings, characters, or any of the integer or floating-point number types. Each raw value must be unique within its enumeration declaration." The Axis enum failed both of these conditions.

I did a couple cursory tests and it seems that it is possible to use a custom raw value, but it has to be equatable and convertible to some form of literal. That literal must also be used in the enum 'case' declarations.
Here's an example:

enum Foo: Bar {
    case Right = "A" // Can't set this to Bar(), but have to use a string literal.
    case Up = "B"
    case Left = "C"
    case Down = "D"
}

struct Bar: StringLiteralConvertible, Equatable {
    init() {}
    init(stringLiteral value: String) {}
    init(extendedGraphemeClusterLiteral value: String) {}
    init(unicodeScalarLiteral value: String) {}
}


func == (lhs: Bar, rhs: Bar) -> Bool {return false}

A Foo can then be constructed from a rawValue of a Bar instance. The Foo case chosen will be the first one for which the Bar value constructed from the case's string literal is equal to the Bar value passed as a raw value, or nil if none of them match.

But in any case, Axis wouldn't work as a raw value because it's not unique—both Right and Left have an Axis of .Horizontal, for instance. So the story ends as it began: axis is relegated to a computed variable which I have never actually used (mostly because of the aforementioned PhysicsSensor class).

Enums, booleans, and UInt32 physics categories


I use bools a lot—the touch handler I use takes a boolean as to whether the touch was on the right side of the screen an another boolean as to whether it was a touchesBegan or touchesEnded event. I could have used a directional enum and special enum with cases .TouchesBegan and .TouchesEnded, but why bother? Bools work great for items that can only have 2 values.
But sometimes I need 3 values instead of 2, such as in these enum cases:
enum WalkDirection {case Right, Left, None}
enum AnimationDirection {
        case Forward, Reverse, ForwardAndReverse
    }
Admittedly, I should probably adapt the Direction enum from the rectangle intersection case to replace WalkDirection. And this example of enums isn't unique to Swift—it would work even in languages where enums are just ints under the hood.
The same is true for the use of enums is to abstract SpriteKit physics categories:
enum OverworldCategoryMask: UInt32 {
    case Player = 1, Enemy = 2, Obstacle = 4, Door = 8, Item = 16, NPC = 32, Foot = 64, Bullet = 128
}
However, this enum is still useful because it allows me to express contact and collision bit masks naturally (if verbosely) instead of using arcane bitshifted integers every time I wanted to create a bit mask. It also ensures that I can modify which numbers I'm using for each physics category without having to update every reference to these categories in code.
self.physicsBody!.collisionBitMask = OverworldCategoryMask.Obstacle.rawValue | OverworldCategoryMask.SomeOtherType.rawValue
The 'OptionSetType' protocol might be useful for this, but I don't really know much about it. Might be worth looking into someday.

Stored Properties of Enums


Here's an interesting one. This use of enums would not work if they were just integers:

    enum Advance {
        case Click, AnimationEnd
        case Choose(choices: [Choice])
    }
    
    struct Choice {
        var text: String
        var action: Dialog.Action
        
        init(text: String, action: Dialog.Action) {
            self.text = text
            self.action = action
        }
    }

This is used as part of a Dialog.Manager class which I use to display dialog to the user. I used to have all of the dialog data stored in a plist, so everything was just NSArrays. Back in those days, when I wanted to have user choices in the dialog, I would set the dialog Advance flag to 'choose' and create an array of choices in the plist which the Dialog.Manager would assume existed.

But I later moved to having my dialog in code:

        case "LabProfessor": return Data(
            idleAnimationName: "ProfessorIdle",
            alertAnimationName: "TalkAlert",
            dialog: [
                Dialog.Item(text: "Hello. I would tell you more about your quest but I was refactored recently. ` You can send an angry email to the dev team if you're disappointed. ` Or you can go through the portal on the left and continue your journey.", animationName: "ProfessorTalk"),
                Dialog.Item(text: "Oh, by the way, sorry about this but I have to test the choices feature.", advance: Dialog.Advance.Choose(choices: [
                    Dialog.Choice(text: "And why do I have to waste my time for this?") {manager in
                        manager.dialog.insert(Dialog.Item(text: "Well because you have to. Sorry"), atIndex: 0)
                    },
                    Dialog.Choice(text: "That's OK with me.") {manager in
                        manager.dialog.insert(Dialog.Item(text: "Great!", mood: .Loud, animationName: "ProfessorJumpTalk", advance: .AnimationEnd), atIndex: 0)
                    }
                    ])),
                Dialog.Item(text: "Yeah anyway. G'day feller.")
            ])

(My justifications for putting this "data" in code were that (1) I am the only person working on this project so it doesn't really matter whether the code and data are separate or not, (2) I might need to put in custom action closures for the dialog anyway, so it's not pure "data" to begin with, and (3) Xcode's plist editor is fairly annoying and doesn't provide any safety or spellchecking the way it does for Swift.)

In any case, I wanted to use Swift-safe structs to create dialog. So how would I make sure that choice dialogs took an array of choices, while click-to-advance dialogs didn't? I was all set to use the sledgehammer of SuperClasses and Inheritance when I realized that enums were infinitely easier to work with. Just have the dialog item hold a Dialog.Advance enum and make each advance mode hold whatever extra data it needs! 

    struct Item {
        ...
        var animationDirection: SKSpriteNode.AnimationDirection
        ...
    }

This is magical not only for the Dialog.Item struct declaration, but also for the 'switch' checking in the Dialog.Manager class, which can now know that it will have an array of Choices if the dialog requires that the user choose a response:

switch dialogItem.advance {
            case .Click:
                waitingForClick = true
                speaker.animateWithAtlasNamed(dialogItem.animationName, animationDirection: dialogItem.animationDirection)
                
            case .Choose(let choices):
                showChoices(choices)
                speaker.animateWithAtlasNamed(dialogItem.animationName, animationDirection: dialogItem.animationDirection)
                //yes there's duplication but it's one method call chill out
                
            case .AnimationEnd:
                speaker.animateWithAtlasNamed(dialogItem.animationName, iterationCount: 1, animationDirection: dialogItem.animationDirection) {self.showNext()}
            }


Notice that inside the block of the switch statement for .Choose, the choices stored by the .Choose enum are bound to a local variable that is guaranteed to exist. The Dialog.Advance enum thus allows the Dialog.Item to fulfill multiple roles while still gaining access to the full capacity of Swift's type safety and Xcode's spellchecker.


The PhysicsSensor Class: Localized Enums


This last use case of enums doesn't take advantage of Swift enum features as much as the Dialog.Advance enum did, but it's a nice case of gaining spellcheck with enums.
The PhysicsSensor class is something that I created to distinguish between different types of physics contacts. For instance, when a player collides with an enemy, the player should be knocked back. But if the player jumps on an enemy, the enemy should be defeated. 
Diagram of how physics contacts are handled with the PhysicsSensor. I've used PhysicsSensors not only for the player but also for patrolling enemies (to notify them when they have collided with a wall from the side so that they can reverse direction)

The way I implemented this was by creating a PhysicsSensor below the player that triggered when the player collided with an enemy from above:
        /*inside PlayerSprite's setup method*/ addChild(PhysicsSensor(
            position: CGPoint(x: 0, y: -size.height/2),
            size: CGSize(width: size.width * 0.5, height: 15),
            name: Sensor.EnemyJump.rawValue,
            delegate: self,
            contactMask: OverworldCategoryMask.Enemy.rawValue
            ))
Can you spot the enum? (Actually, there are two of them. One's the category bit mask enum I discussed earlier). The PhysicsSensor is identified by a string, but I've set that string in an enum so that wherever it's referenced from code, it's guaranteed to be spelled correctly. This enum is locally scoped to the PlayerSprite class:
    enum Sensor: String {
        case EnemyJump
        case FloorContactEvent
    }
In the case of a collision, the player sprite's associated state machine states (built on GameplayKit, but this blog post's too long already for a discussion of that framework) create a Sensor enum from the sensor's name (passing it as a rawValue) and switch it to determine what the collision was (.FloorContactEvent is used to determine when the player has landed and then exit the Jump state)
    func sensorNamed(name: String, didContact body: SKPhysicsBody) {
        switchSensor: switch PlayerSprite.Sensor(rawValue: name)! {
            
        case .EnemyJump:
            guard body.node != nil else {break switchSensor}
            (body.node as? EnemySprite)?.defeatedByPlayer()
            enterState(PlayerJumpState)
            
        case .FloorContactEvent: break
            
        }
        playerState.sensorNamed(name, didContact: body)
    }
    
If I add a new case to the Sensor enum, the Swift compiler will alert me and I can expand this switch statement—a safety check I wouldn't get if I were using strings directly. And, of course, the spellchecking benefit is here too.

So that's enums. Quite useful and even magical at times. Now all I need to do is add unicorns to the game (kidding, probably)

Comments

Popular posts from this blog

The Hidden Garageband Sampler (and other features)

How to add a folder to Launchpad