text: Chapter 4. 2D Game Engine
  Finally, the good stuff! The first three chapters of this book introduced the basics involved in programming for the iPhone and the concepts behind a game engine. We even laid out a nice framework to get us started. All that’s left is to make our game…but wait, aren’t we forgetting something? What kind of game are we going to make? Before we start coding away, we need to have a plan. We need a game design.
  Game Design
  The first step in coming up with a good game design is brainstorming. What will the game look like, and how will it feel to play the game? What are the goals, and how will you keep players entertained? Remember that as much fun as it is to make a game, the end result needs to be something that’s fun to play.
  To begin, we’ll need to keep in mind the special considerations of the iPhone:
  Touch-screen input
  Limited resolution and memory
  Play sessions that are short and can be interrupted at any time by a phone call
  This means we need to come up with a design that incorporates:
  A simplistic input scheme
  Efficient use of memory for graphics and sound
  Game play that is broken into short segments that can easily be stopped and resumed
  To fulfill all of these requirements, we will create a 2D tile-based engine. Tile-based means the level background will be composed of a limited number of square tiles that are repeated and put together to form the level (this allows us to create a large level that uses only a small amount of texture memory). Most 2D games are static (the screen does not scroll at all—think puzzle games), side-scrolling (think Super Mario Bros.), or top-down (like Zelda). A puzzle game is going to be too limiting in terms of AI logic, and most side-scrolling games are too demanding in terms of user input, so top-down best fits our needs. And since this is an O’Reilly book, we’ll borrow from the theme of O’Reilly animals.
  With these parameters in mind, let’s create O’Reilly’s Wildlife Adventure. As the name suggests, this will be an adventure game involving wild animals. Players will need to help our main character, Tom O’Reilly, as he tends to the animals of a wild animal park. Each “level” will represent a different animal pen, which will nicely segment game play and allow us to challenge players in unique ways with each type of animal they encounter. Tom will handle various special objects as he deals with each animal.
  This game design probably won’t win any major awards, but it will definitely cover all of the major facets of game development for the iPhone.
  Feature List
  Now that we have finished brainstorming, it’s time to nail down exactly what we need to bring this design into reality.
  - Graphical layout
  To render the game, we will need to draw:
  The level background
  The main character
  Several different types of animals
  Any random objects that may appear in a level
  If the levels are larger than the screen, we will need to be able to scroll our view into the world to keep the player centered on the screen.
  - Input
  We will need to interpret user input to:
  Control the main character
  Create an AI engine to control the animal behaviors
  Code up some physics to handle the movement and interaction of those entities with each other and the level
  We will also need game logic to determine when goals have been met and store the player’s progress.
  - Display
  Apart from drawing the world, we may need to overlay additional information in a heads-up display (HUD) for things such as:
  The player’s condition
  Notification of any special items that have been picked up
  Possibly a button to open a pause menu
  Furthermore, we will probably need to display some text for dialogs if our main character has anything interesting to say.
  - Game progress
  So far, we’ve discussed only what goes on during game play. We will also need to add:
  Code to link the levels together and show game progression
  Something like a map that lets players choose what level to play next and unlocks new areas as they complete old ones
  Putting it all together, we have the following technical feature list:
  A 2D tile-based render engine with scrolling capability
  Animated sprite entities for the main character, animals, and possibly level entities
  Functions to handle user input to control the main character
  Animal entity logic for several types of animals (such as lions, crocodiles, and emus)
  Physics to allow entities to move where they should, and not where they shouldn’t (such as into other entities)
  Level logic to determine when goals have been met and to store the player’s progress through the game
  A HUD for important information, including character dialogs
  A menu that allows players to select what level to play and displays their progression through the game
  User Input Scheme
  As we mentioned earlier, the touch-screen feature of the iPhone means user input has to be a major focus of game design. Specifically, there are three problems that our game design should try to avoid.
  The first problem to avoid is requiring accurate touches. When a player touches the screen, she is actually touching many pixels at once; the iPhone will estimate the center pixel among those pixels and events will correspond to just that one pixel. It is important to make touchable interfaces (such as buttons) or in-game objects large enough that an inaccurate touch event will still find its mark if the player touched close enough. Therefore, we should use a rule of thumb (pun intended) that touchable objects should be at least 44×44 pixels.
  The second problem to avoid is causing the player to cover the screen with her hand while trying to use the screen for input. This is one of the major downsides to a touch-screen interface; because both input and output happen on the same surface, they end up fighting each other for space. One way to solve this is to require as little input from the user as possible. Another is to take user input only in nonessential parts of the screen, or nonessential application states—states where fast response to game actions is not required.
  The third problem to avoid is requiring many different types of touches. The iPhone can record not only single and double taps but also finger dragging. With a little work, it is even possible to detect when the player forms symbols (such as a circle or square) with her finger. However, it is important to keep in mind that game play can get frantic, and complex input schemes will frustrate players if they cannot quickly and easily communicate their intended actions to the game.
  We chose a top-down design to help reduce the amount of user input activity needed to play the game. Specifically, with a top-down view, we can keep the camera zoomed out far enough that the player can tap an area of the level she wants to move to, and the game will plot out a path and move the character for her. In this way, one simple input (tapping a part of the level) is translated into a series of more complex movements (move forward, turn right, move forward, turn left, move forward), saving the player from a lot of busy work.
  However, movement is not the only type of input that will be required during game play. We may also need to handle special actions such as jumping, picking up items, and anything else the game requires. We can handle these in a similar fashion to movement: by translating a simple command into an intelligent behavior. Specifically, we know that if the player touches an area of the level the character is not standing in, we should move the character to that point. However, if she taps the square the character is already standing in, we can treat it as a special command.
  But how do we know what that special command is? By determining the context in which the command is used. If the character is standing on the edge of a gap when the player taps, we could consider this as a command to jump across the gap. If instead the character is standing next to a door, we could consider this as a command to open the door. Based on the proximity of the character to a special feature of the level, tapping the character will cause the character to interact with that feature. Such actions are considered context-sensitive.
  This allows us to have a system that can produce a lot of complex behavior, but the input system remains absolutely simple. In fact, the player needs to know only two motions: tap the area you want the character to move to, or tap the character itself to perform a context-sensitive action.
  Learning Curve
  By now, we have a basic understanding of the structure of the game world (top-down, tile-based, with entities moving about) and how the user will interact with it (by tapping the level or the character). It is time to come up with some level concepts. However, an important part of level design is to consider the game’s learning curve.
  When a player first picks up your game, she will not be an expert at it. In fact, learning how to play your game will be part of the fun for her. But if the first levels she encounters are too challenging, she will become frustrated easily and stop playing. On the other hand, if she masters your game in the first five minutes, she will get bored and put it down.
  Therefore, it is important to design the levels in such a fashion that the player is neither bored nor overwhelmed. Typically, you should try to introduce one new concept (or a combination of two previously introduced concepts) per level to ensure that players stay awake.
  Level 1
  Our first level will involve merely walking about, with no context-sensitive actions or fear of death. However, if the player only had to walk from one side of the level to the other without a challenge, she would get bored. So, we will give her something to chase to motivate her to keep playing.
  A mother emu’s chicks have wandered from the nest and our character will have to chase them in the right direction. This will also give us a chance to work on some flocking AI.
  Level 2
  Our second level will continue with the movement-only-centered activity, but will increase the intensity of the game.
  Specifically, we will ask the player to walk across a habitat filled with sleeping lions to the back of the cave to grab a McGuffin. A McGuffin is a nonsense term that means “something that moves the plot.”[1] Basically, we need a reason to send our main character to the back of a lion’s cave. It doesn’t have to be a good reason, just a reason. That’s what the McGuffin is for. If the player avoids the lions, the level will be easy. If she gets too close, the lions will wake up and swipe at her. This will allow us to work on some multistate AI (sleeping and chasing).
  Level 3
  The third level will dial back the intensity, but it will introduce a new concept. Here, we’ll teach the player how to use the context-sensitive actions by providing her with button objects she can interact with to change the level.
  We will set up the level as a puzzle: a grid of cells (3×3) will contain a cat and a mouse, as well as cheese below a control room that contains three buttons and the main character. Between each cell is a door that can only be opened by pressing the corresponding button from the control room.
  The goal is to open the doors in such a way that the mouse reaches the cheese without meeting the cat. This will show the flexibility of our game engine, being able to create a casual puzzle type of game play experience.
  Level 4
  The fourth level will focus on using the context-sensitive action method of input in a time-sensitive setting. We will place a McGuffin at the top end of the level past several streams of water that must be crossed.
  To do this, we will place logs in the streams that the player can jump on, using the context-sensitive action. The player will have to move quickly, however, as the logs will slowly sink and disappear into the water, requiring a series of decisive jumps.
  And if that weren’t enough, we will also have crocodiles patrolling the waters, jumping and snapping at the player if she gets too close.
  At this point, we are ready to start implementing. Please download the Chapter 4 example code from.
  Tile Engine
  The first step in constructing our game will be to focus on the foundation of the game world: the tile engine. The tile engine is responsible for loading the tiles that represent the level and drawing them to the screen. If the level is larger than the viewable area of the screen, we will keep track of a “camera offset” that will be used to draw the portion of the level we are interested in, as well as to draw the rest of the objects on top of the level with the same offset.
  Unique Tiles
  To begin, we will start with an image that represents our level (see Figure 4-1). This image was constructed out of tiles that are 32×32 pixels wide and high. The level is 15 tiles wide by 25 tiles high, or 375 tiles total. However, because it was created from a small set of tiles, we need to load only the unique tiles into memory; we need only 32 tiles (see Figure 4-2).
  That’s more than 90% less memory! However, we’re also going to need to store some data that tells us which unique tile image to use when rendering the level. So, in addition to the unique tile image, we will also need an index array, such as:
  1,0,0,0,0,0,0,0,0,0,0,0,0,0,2 1,0,0,0,0,11,12,13,14,15,0,0,0,0,2 1,0,10,0,0,16,17,18,19,20,0,0,0,0,2 1,0,0,0,0,21,22,23,24,25,0,0,10,0,0,0,4,0,0,0,0,2 1,0,4,5,0,0,0,0,0,0,0,0,0,0,2 1,0,0,10,0,0,0,0,0,0,0,0,5,6,0,0,0,0,0,0,0,2 1,3,4,0,0,10,0,0,0,0,0,0,0,0,2 1,0,0,0,0,0,0,0,0,0,8,9,0,0,2 1,0,0,0,0,0,0,0,0,0,10,0,0,0,2 1,0,0,0,0,0,0,0,0,0,0,0,0,0,2 1,0,0,0,0,0,0,0,0,0,0,0,10,8,2 1,0,0,0,0,0,0,0,0,0,0,0,0,0,2 1,0,0,0,0,0,0,0,4,0,0,0,0,0,2 1,0,0,0,3,4,0,0,0,0,0,9,0,0,2 1,0,0,0,0,0,0,0,0,0,5,6,0,0,2 1,0,0,0,0,0,0,0,0,0,0,0,0,0,2 1,0,0,0,0,0,0,0,0,0,0,0,0,0,2
  The preceding code describes the tile indexes of a 15×25-tile map. The top-left tile uses the second frame (index “1” because indexes start at “0”). The next frame uses frame index “0,” and so on.
  Drawing Tiles
  As we discussed in Animation, sprites are
  textures that contain multiple images that should be drawn in succession
  to represent an animation. Each image in the sprite texture is called a
  frame. However, the class we presented in Chapter 3 for rendering textures,
  GLTexture, is not suitable for drawing
  frames.
  Specifically, we want to consider the unique tile strip (Figure 4-2) to be like a film strip.
  Each frame is exactly the same width, so finding the location of a
  single frame in the texture is as easy as
  x =
  frame*frameWidth; where
  frameWidth is the same as the tile
  size.
  The iPhone also limits us to a maximum of 1,024 for both the width and the height of the texture. That means that if our unique tiles are 32×32 and we have more than 32 tiles in either direction (because 32 * 32 = 1,024), we have to wrap them to keep our image width or height from exceeding 1,024.
  Warning
  The OpenGL ES implementation on the iPhone will try to automatically scale down any texture larger than 1,024 in height or width. This could cause unintended side effects for any images that are too large; if you are noticing problems with your textures, make sure they aren’t too big.
  That means the calculation for the frame offset becomes:
  int x = (frame * tileSize) % 1024; int row = (( frame * tileSize ) - x) / 1024; int y = row * tileSize;
  Because of the number of mathematical operations involved in
  calculating this position, it is more efficient to store the results
  after the first calculation and reuse them, rather than recalculating
  the frame for each tile in every loop. Therefore, we will create
  a
  Tile class that can
  store the offset into a
  GLTexture, like
  so:
  //Tile.h typedef enum { UNWALKABLE = 1, WATER = 2, EMUTEARS = 4, } PhysicsFlags; @interface Tile : NSObject { NSString* textureName; CGRect frame; PhysicsFlags flags; } @property (nonatomic, copy) NSString* textureName; @property (nonatomic) CGRect frame; - (void) drawInRect:(CGRect)rect; - (Tile*) initWithTexture:(NSString*)texture withFrame:(CGRect) _frame; @end //Tile.m @implementation Tile @synthesize textureName; @synthesize frame; - (Tile*) init { [super init]; flags = 0; return self; } - (Tile*) initWithTexture:(NSString*)texture withFrame:(CGRect) _frame { [self init]; self.textureName = texture; self.frame = _frame; return self; } - (void) drawInRect:(CGRect)rect { [[g_ResManager getTexture:textureName] drawInRect:rect withClip:frame withRotation:0]; } - (void) dealloc { [super dealloc]; } @end
  Notice the
  PhysicsFlags
  enumeration. We will use this later for entity-to-world collision
  detection.
  TileWorld Class
  With the unique tile index array data, we can now write a class
  that will render the level to the screen using the
  Tile class. To do this, we will create a new
  class named
  TileWorld that
  a
  GLESGameState can use.
  When the
  GameState is
  initialized, it will create the
  TileWorld and load the level data; when the
  GameState is rendering, it will tell
  the
  TileWorld to draw itself before
  anything else.
  This class needs to keep a list of tiles, along with a rectangle that describes what portion of the screen to render those tiles to. In addition, our levels may be larger than the viewable area, so we will need to keep a “camera” offset to pan our rendering to display the portion of the level we want to see:
  //TileWorld.h //global tile size. #define TILE_SIZE 32 @class Tile; @class Entity; @interface TileWorld : NSObject { Tile*** tiles; CGRect view; //typically will be the same rect as the screen. in pixels. considered to be in opengl coordinates (0, 0 in bottom left) int world_width, world_height; //in tiles. int camera_x, camera_y; //in pixels, relative to world origin (0, 0). // view will be centered on this point. NSMutableArray* entities; } - (TileWorld*) initWithFrame:(CGRect) frame; - (void) loadLevel:(NSString*) levelFilename withTiles:(NSString*)imageFilename; - (void) draw; - (void) setCamera:(CGPoint)position; //utility function - (CGPoint) worldPosition:(CGPoint)screenPosition; - (void) addEntity:(Entity*) entity; - (Tile*) tileAt:(CGPoint)worldPosition; - (BOOL) walkable:(CGPoint) point;
  There are a number of things to note here before we move on to the
  implementation. First, what is that
  TILE_SIZE definition? We stated earlier that
  we would be using 32×32 pixel tiles. This definition helps us
  avoid magic numbers.
  A magic number is a certain numeric value that is used over and
  over again in code, and it is bad because you cannot tell when, for
  example, a value of 32 is used to represent a tile size or some other
  concept. It’s also bad because if you wanted to change the tile size to
  some other value, such as 16, you’d have to do a search and replace,
  being careful to replace only the instances of 32 that represent tile
  size. You should always use a
  define
  for values instead of magic numbers, to make your intended use
  clear.
  The next important piece of code is the
  Tile*** tiles; line. This represents a
  dynamically allocated 2D array of
  Tile pointers. Because the tile engine could
  be used to represent a level of any width or height, we must allocate
  the tiles dynamically.
  Finally, you will notice the
  NSMutableArray* entities; definition. This
  array will keep a list of objects we wish to draw in every frame.
  Because objects in front should be drawn on top of objects behind them,
  we need to sort those objects and draw them in order (from back to
  front). We also need to offset the rendering position of those objects
  by the same camera offset the level tiles are being drawn with.
  You can view the implementation of the
  TileWorld class in the TileWorld.mm file.
  Let’s hone in on the
  loadLevel:
  function, which accepts a text file in the following format:
  [height]x[width] [comma separated tile texture indexes] [comma separated tile physics flags]
  First, it loads the file contents into an
  NSString. Next, it
  parses out the width and height values from the first line by
  separating them using the
  componentsSeparatedByString: function of
  NSString to grab the characters to
  the left and right of the
  x
  character. Then it calls the
  intValue function to parse the string into
  an integer value for each.
  Once we have the width and height of the level in tiles, we know
  how many
  Tile object pointers must
  be allocated. This is done in the
  allocateWidth: height: function.
  Next, we parse through the file and create a new
  Tile object for each tile in the level,
  initializing it with a
  CGRect
  representing the frame position of the unique tile index found in the
  .txt file, as we discussed in
  Drawing Tiles.
  Once all of the tiles have been created, we skip past the second
  blank line and start parsing the physics flags. For each tile, we read
  in the value and initialize the
  flags property of the corresponding
  Tile object.
  Finally, we can release the original file data. Remember, since
  we have allocated the
  Tiles array
  and objects, we will need to deallocate them when we unload the
  level.
  Rendering
  Now that we have the
  Tiles
  initialized, we can render the level. Keep in mind that the
  TileWorld instance will be a member of
  a
  GLESGameState class,
  which will be calling the
  draw:
  function:
  -(void) draw { CGFloat xoff = -camera_x + view.origin.x + view.size.width/2; CGFloat yoff = -camera_y + view.origin.y + view.size.height/2; CGRect rect = CGRectMake(0, 0, TILE_SIZE, TILE_SIZE); for(int x=0;x<world_width;x++){ rect.origin.x = x*TILE_SIZE + xoff; //optimization: don't draw offscreen tiles. // Useful only when world is much larger than screen, // which our emu level happens to be. if(rect.origin.x + rect.size.width < view.origin.x || rect.origin.x > view.origin.x + view.size.width) { continue; } for(int y=0;y<world_height;y++){ rect.origin.y = y*TILE_SIZE + yoff; if(rect.origin.y + rect.size.height < view.origin.y || rect.origin.y > view.origin.y + view.size.height) { continue; } [tiles[x][y] drawInRect:rect]; } } if(entities){ [entities sortUsingSelector:@selector(depthSort:)]; for(Entity* entity in entities){ [entity drawAtPoint:CGPointMake(xoff, yoff)]; } } }
  First we calculate the offset between the world and the viewable area of the screen. We are basically transforming between world coordinates and screen coordinates.
  As an optimization, we also initialize a rectangle the size of a
  tile for use in the
  Tile object’s
  drawInRect: function. Rather than
  creating the rectangle for each tile, we can set it up outside the
  for loop and have to change only
  the x and y positions,
  saving us some processing power.
  You may recall from Chapter 2 that
  another form of graphics optimization is culling. Inside the
  for loop, we begin by culling any columns of
  Tiles that wouldn’t show up in the
  viewable area. Then we start the inner
  for loop to draw the actual
  Tiles, first being sure to cull any
  individual tiles in that row that are outside the viewable
  area.
  Now we get back to the entities array. We will discuss the
  Entity class in more detail later,
  but recall that entities need to be sorted and drawn in order from
  back to front so that they overlap each other correctly. Because the
  entities variable here is
  an
  NSMutableArray we
  can use the
  sortUsingSelector
  method which allows us to apply a comparison function we’ve written
  for this purpose.
  The comparison function is expected to be called as a message on
  one object, with a single parameter being an object of the same type
  that should be compared. The return value must be
  NSOrderedAscending,
  NSOrderedSame, or
  NSOrderedDescending based on the result of
  the comparison.
  A default comparison function is available for use with simple
  types such as
  NSString
  and
  NSNumber. However,
  because we are comparing entities, we must write a custom
  comparison.
  Our custom function is named
  depthSort: and is located in the Entity.m file:
  - (NSComparisonResult) depthSort:(Entity*) other { if (self->worldPos.y > other->worldPos.y) return NSOrderedAscending; if (self->worldPos.y < other->worldPos.y) return NSOrderedDescending; //the logical thing to do at this point is return NSOrderedSame, but that //causes flickering when two items are overlapping at the same y. Instead, //one must be drawn over the other deterministically... we use the memory //addresses of the entities for a tie-breaker. if (self < other) return NSOrderedDescending; return NSOrderedAscending; }
  The function compares the
  y
  values of the objects, returning
  NSOrderedAscending if the “other”
  Entity has a smaller
  y value. Recall that y = 0 at the top of the
  screen, so the smaller the
  y value,
  the farther “up” on the screen the object will be drawn. We want
  things closer to the bottom of the screen to be considered “close” and
  things at the top to be considered “far,” so returning the objects in
  ascending order by y coordinate will make sure
  that entities that are higher on the screen (farther away) will be
  drawn first, and entities lower on the screen (closer) will be drawn
  last (thereby showing on top of the others).
  Camera
  We mentioned that the tiles are able to be offset so that the viewable area shows the part of the level we are interested in. We call it the camera offset because it’s like a camera panning around inside the level area. In particular, we want to be able to set the camera offset to follow our main character as he walks around the level.
  By calling
  setCamera: on our
  TileWorld with a target position (in world coordinates), we can
  calculate the offset necessary to draw the tiles so that they will be
  visible within the screen coordinates. If we offset a point within the
  tile rectangle by the negated value of that point, it will show up at
  the top left of the viewable area.
  However, we want the main character to show up in the center of
  the screen, not the top left. Furthermore, if the character walks
  close to the left side of the world, rather than staying in the middle
  of the screen (thereby causing the viewable area to extend beyond the
  level and show empty space), we want to force the camera to stay
  within the bounds of the level and instead cause the character to
  leave the center of the screen and get close to the edge. The same
  condition applies for the top, bottom, and right sides of the screen.
  Therefore, when the intended camera position is sent to
  setCamera:, we do a
  bounds check on each side of the viewable area to make
  sure the camera stays put, like so:
  -(void) setCamera:(CGPoint)position { camera_x = position.x; camera_y = position.y; if (camera_x < 0 + view.size.width/2) { camera_x = view.size.width/2; } if (camera_x > TILE_SIZE*world_width - view.size.width/2) { camera_x = TILE_SIZE*world_width - view.size.width/2; } if (camera_y < 0 + view.size.height/2) { camera_y = view.size.height/2; } if (camera_y > TILE_SIZE*world_height - view.size.height/2) { camera_y = TILE_SIZE*world_height - view.size.height/2; } }
  Physics
  Apart from drawing the tiles and entities at their appropriate
  offsets, the last feature of the
  TileEngine is
  physics. Remember that each
  Tile in
  the
  tiles array has a physics flag
  that was initialized when we loaded the level file.
  During the
  GameState update
  function, we will be checking entity-to-world collision by asking
  the
  TileWorld for each
  entity if it is colliding with any tiles that have special physics
  flags.
  To this end, we provide the
  tileAt: function, which will return the
  Tile object located at a particular
  point (in world coordinates). Furthermore, as a convenience, we will
  also provide the
  walkable: function
  to determine whether the
  Tile has
  any special physical properties we should worry about.
  Animation
  Our game will contain a main character and several animated animals. Each of these entities will require one or more animations for each of their behaviors and each of the directions they could be facing during those behaviors. For instance, the main character could have “idle,” “walking,” and “jumping” animations in the “north,” “south,” “east,” and “west” directions.
  Once our artist has rendered each of these, we can assemble them together into a sprite texture similar to the unique tile strip texture we will use for the tile engine map. However, these animations are much more complex than the unique tile strip. See Figure 4-3, for instance, for the images we need to represent a walking emu chick.
  As you can see, the emu chick sprite texture has three animations: walking west, walking north, and walking south (we omitted walking east because it can be created by flipping the walking west animation horizontally, which saves precious texture memory).
  To make things more complicated, the animations are set up in such a way that some frames should be used multiple times in a single animation sequence. Specifically, the walking west animation should be displayed in the frame sequence 0, 1, 2, 3, 4. If we had a more complex animation, this format would support something similar to 0, 1, 0, 2, 0, 3…, where the frame indexes are not sequential.
  Additionally, we could want some frames to take longer than others, so we should specify how long (in milliseconds) each frame should take: 200, 300, 200, 100, 200, 100, 100, 200. This adds up to an animation sequence that takes 1,400 ms (or 1.4 s) to complete.
  To complicate things further, some animations should be looped until we change to a different animation, whereas other animations should play only once and stop. In addition, some animations should specify another animation to switch to as soon as they have completed.
  Animation and Sprite Classes
  However, our game logic should not have to deal with all of the
  intricacies of animation programming. We need to encapsulate the
  animation logic inside an
  Animation
  class.
  To begin, we need a way to specify the animation sequences that an
  Animation object will represent. We
  could hardcode all of the animation logic, but that would be a lot of
  work with all of the animals and such, and we would have to throw it
  away the next time we wrote a game. We could also create a proprietary
  file format, but then we’d need to write a tool that could take in all
  of the animation sequences and write them to the file, which is beyond
  the scope of this book.
  Instead, we will use something familiar to Mac developers: the
  .plist file. You already used a
  .plist file to set up certain
  parameters for your Xcode project. Here we will use a .plist file to
  contain all of our animation sequence data, and we will write an
  initialization function in our
  Animation class that will read in that
  data.
  Property list
  We can use Xcode to create a .plist file for animation purposes. We will use the emuchick.png file as our texture. Our goal is to describe the “walking left,” “walking up,” “walking down,” and “walking right” animations:
  From within Xcode, select File→New File to open the New File dialog.
  From the left column select Other and in the right window select Property List.
  Type
  Animations.plistfor the name and click the Finish button.
  Select the Animations.plist file within Xcode. The .plist editor view will appear.
  All items in a property list are added to the root dictionary item, in key-value pairs. You can structure your data by nesting dictionaries or arrays. We are going to structure our animation data by using a dictionary for each sprite texture.
  We will begin by adding a dictionary named
  emuchick.png:
  Right-click the Root row and select Add Row.
  Replace the “New Item” text with “emuchick.png” in the Key column.
  Change the Row type to Dictionary by clicking the Type column (it is set to String by default) and selecting Dictionary.
  Now we can start adding rows to the
  emuchick.png dictionary to describe the
  animations found in this sprite texture:
  Add a row to the
  emuchick.pngdictionary by right-clicking and selecting Add Row. Make sure you added the row to our dictionary, not the root: the new row should be indented farther to the right than the
  emuchick.pngrow is in the Key column.
  Name the new row “frameCount” and set its type to Number.
  Set the value for frameCount to 15, since there are 15 frames in the emuchick.png texture.
  Next, we want to store information about all of the different
  animations that can be found in this sprite. For each separate
  animation, we will create a new dictionary below the
  emuchick.png dictionary:
  Add another row to the
  emuchick.pngdictionary by right-clicking and selecting Add Row again.
  Name this one “walkLeft” and set its type to Dictionary. It will represent an animation of the emu chick walking to the left.
  Each animation will need to specify a sequence of frames and the animation period for each of those frames. For “walkLeft”, we want to use a frame sequence of 0,1,2,3,4 with 100 ms for each frame:
  Add two rows to the
  walkLeftdictionary and name them “anim” and “time”. Leave their types as String.
  In “anim”, set the value to “0,1,2,3,4”, with no spaces.
  In “time”, set the value to “100,100,100,100,100”, also with no spaces.
  Great; now we have the first sequence done. For the next two, we
  can actually copy and paste the
  walkLeft dictionary and rename it to save us
  some time:
  Right-click on “walkLeft” and select Copy.
  Left-click on “emuchick.png”, then right-click and select Paste (it is important to have it highlighted by left-clicking before pasting, or your new copy will end up in the wrong place). Double-check that the new copy is a child of
  emuchick.png; it should be at the same indentation as the original
  walkLeftdictionary.
  Rename the first new copy to “walkup” and change the “anim” value to “5,6,7,8,9”.
  Rename the second copy to “walkdown” and change its “anim” value to “10,11,12,13,14”.
  Now we need a walking right animation. But the sprite has no frames of the emu chick walking to the right. Instead, following our plan when we asked the artist to draw the images, we’ll flip the left-facing frames for use in the walking right animation:
  Copy and paste the
  walkLeftdictionary onto
  emuchick.pngonce more.
  This time, add a row to
  walkRight, named “flipHorizontal”.
  Set its type to Boolean and toggle the checkbox that shows up in the Value column. It should be marked with a check, which means the value is set to
  true.
  The last piece of data we need to store about the emuchick.png animations is an offset value for all of the frames. When we want to draw the emu chick, we don’t want to have to figure out how big the frame is and draw from the upper-left corner; instead, we set that value here so that we can automatically offset the graphics. In this case, we want the origin to be at 16,6 within the frame:
  Add one more row to the
  emuchick.pngdictionary.
  Name this one “anchor” and keep its type as String.
  Set its value to “16,6”.
  Now we have a .plist file
  that contains all of the data to represent a series of animations used
  by an entity. Next, we will create an
  Animation class that will allow us to use
  this data in our game.
  Consider, however, that we do not want to allocate a new
  Animation object for each of our
  entities. For instance, let’s say we have three siren salamanders
  walking around in our level. Just as they are all using the same
  GLTexture for their image data,
  they should also use the same
  Animation object for their animation data.
  You should consider an
  Animation
  object as a resource such as a texture.
  However, certain aspects of the animation should belong to each
  Salamander entity: specifically,
  the current animation sequence being rendered, and the time since the
  start of the animation.
  To facilitate this, we will also create a
  Sprite class. This
  class will keep track of the animation data, as well as the exact
  frame and start time of the animation being rendered.
  Animation class
  Although the
  Animation class
  will represent the animation used by an object in our game, it’s
  possible that a single object could have multiple behaviors. In the
  preceding example, the emu chick had only a walking animation, but it
  could also have had an idle animation or a sleeping animation.
  We call each of these behaviors an animation sequence, and our
  Animation class will keep track of multiple
  AnimationSequence objects.
  In the preceding example, the
  Animation object used by the emu chick would
  have only one
  AnimationSequence.
  We will start by defining the
  AnimationSequence class, followed by the
  Animation class:
  //Animation.h @interface AnimationSequence : NSObject { @public int frameCount; float* timeout; CGRect* frames; bool flipped; NSString* next; } - (AnimationSequence*) initWithFrames:(NSDictionary*) animData width:(float) width height:(float) height; @end @interface Animation : NSObject { NSString* image; NSMutableDictionary* sequences; CGPoint anchor; } - (Animation*) initWithAnim:(NSString*) img; - (void) drawAtPoint:(CGPoint) point withSequence:(NSString*) sequence withFrame:(int) frame; -(int) getFrameCount:(NSString*) sequence; -(NSString*) firstSequence; -(AnimationSequence*) get:(NSString*) sequence; @end
  The
  AnimationSequence class
  keeps track of the number of frames, the time that each frame should
  be displayed, and (similar to the
  Tile class) a
  CGRect to represent the subsection of the
  GLTexture that represents each
  frame. It also keeps a Boolean to determine whether the frame should
  be drawn flipped on the horizontal axis.
  The
  Animation class keeps a
  string that represents the name of the
  GLTexture for use with our
  ResourceManager’s
  getTexture: function, as well as an
  NSMutableDictionary to keep track of our
  AnimationSequences. To make things
  easy, we will use a string as the key value. When we want to access
  the walking animation sequence, all we have to do is call
  [sequences valueForKey:"walking"]. Finally,
  we keep an anchor point that allows us to draw the animation at an
  offset.
  The
  AnimationSequence class
  we defined had only one function, an initialization method, which will
  parse the animation sequence data from a .plist file:
  //Animation.mm @implementation AnimationSequence - (AnimationSequence*) initWithFrames:(NSDictionary*) animData width:(float) width height:(float) height { [super init]; NSArray* framesData = [[animData valueForKey:@"anim"] componentsSeparatedByString:@","]; NSArray* timeoutData = [[animData valueForKey:@"time"] componentsSeparatedByString:@","]; //will be nil if "time" is not present. bool flip = [[animData valueForKey:@"flipHorizontal"] boolValue]; self->next = [[animData valueForKey:@"next"] retain]; frameCount = [framesData count]; frames = new CGRect[frameCount]; flipped = flip; for(int i=0;i<frameCount;i++){ int frame = [[framesData objectAtIndex:i] intValue]; int x = (frame * (int)width) % 1024; int row = (( frame * width ) - x) / 1024; int y = row * height; frames[i] = CGRectMake(x, y, width, height); } timeout = NULL; if(timeoutData){ timeout = new float[frameCount]; for(int i=0;i<frameCount;i++){ timeout[i] = [[timeoutData objectAtIndex:i] floatValue] / 1000.0f; if(i > 0) timeout[i] += timeout[i-1]; } } return self; } - (void) dealloc { delete frames; if(timeout) delete timeout; [self->next release]; [super dealloc]; } @end
  We begin by grabbing the array of animation frames labeled
  “anim” from inside the
  animData
  dictionary. We do this by passing
  @"anim" as the key into the
  valueForKey: function of
  NSDictionary, which returns a string value.
  In the same line, we split that string into substrings by separating
  each section marked by a comma using the
  componentsSeparatedByString: method of
  NSString. We do the same thing on
  the next line, only we grab the “time” entry instead.
  We also grab the “flipHorizontal” entry and convert it to a bool value from which we directly initialize our flipped member variable. After that, we get the “next” entry to determine what the next animation sequence should be: if the value is nil, we will simply stop animating; otherwise, we will continue into the next animation sequence automatically.
  Next, we want to create a
  CGRect that represents the section of the
  GLTexture each frame represents. We
  know the width and height of the
  CGRect because they were passed into our
  function and each frame is the same size. All we need to do is
  calculate the x and y
  offsets based on the frame number of each animation.
  We index the
  framesData array
  using the
  objectAtIndex: function
  and convert each element to an integer to get the frame index. Next,
  we calculate the x and y
  offsets based on the frame height and width. Remember, we need to wrap
  the frames if the width or height is larger than 1,024.
  After we are done creating the frame rectangles, we initialize
  our
  timeout array. After indexing
  the
  timeoutData array and
  converting each element into an
  int
  (the same as we did earlier), we divide the result by 1,000. We
  mentioned earlier that the iPhone counts time by seconds, although it
  allows fractional values. Because the time periods in our .plist are in milliseconds, dividing by
  1000.0f converts from integer milliseconds to floating-point seconds,
  which is what we want.
  Now we can implement the
  Animation class. Recall that the
  Animation class is a resource that multiple
  Sprite classes will use; it should
  not store any data used to render a particular instance of an
  animation, only the list of
  AnimationSequences. The drawing function
  should have parameters that specify exactly which frame of which
  AnimationSequence is currently
  being requested so that multiple
  Sprites can render the same animation at
  different locations of the screen and of the animation.
  Let’s start by looking at the initialization function:
  //Animation.mm - (Animation*) initWithAnim:(NSString*) img { NSData* pData; pData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Animations" ofType:@"plist"]]; NSString *error; NSDictionary* animData; NSPropertyListFormat format; animData = [NSPropertyListSerialization propertyListFromData:pData mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&error]; animData = [animData objectForKey:img]; GLTexture *tex = [g_ResManager getTexture:img]; image = img; float frameWidth, frameHeight; if([animData objectForKey:@"frameCount"]){ int frameCount = [[animData objectForKey:@"frameCount"] intValue]; frameWidth = [tex width] / (float)frameCount; frameHeight = [tex height]; } if([animData objectForKey:@"frameSize"]){ NSArray* wh = [[animData objectForKey:@"frameSize"] componentsSeparatedByString:@"x"]; frameWidth = [[wh objectAtIndex:0] intValue]; frameHeight = [[wh objectAtIndex:1] intValue]; } //anchor is the position in the image that is considered the center. In //pixels. Relative to the bottom left corner. Will typically be positive. //all frames in all sequences share the same anchor. NSString* anchorData = [animData valueForKey:@"anchor"]; if(anchorData){ NSArray* tmp = [anchorData componentsSeparatedByString:@","]; anchor.x = [[tmp objectAtIndex:0] floatValue]; anchor.y = [[tmp objectAtIndex:1] floatValue]; } NSEnumerator *enumerator = [animData keyEnumerator]; NSString* key; sequences = [NSMutableDictionary dictionaryWithCapacity:1]; while ((key = [enumerator nextObject])) { NSDictionary* sequencedata = [animData objectForKey:key]; if (![sequencedata isKindOfClass:[NSDictionary class]]) continue; AnimationSequence* tmp = [[AnimationSequence alloc] initWithFrames:sequencedata width:frameWidth height:frameHeight]; [sequences setValue:tmp forKey:key]; [tmp release]; } [sequences retain]; return self; }
  The
  initWithAnim: function
  accepts the name of an image as its only parameter. This will be used
  to grab the associated animation from within the Animations.plist file, as well as the
  GLTexture by the same name.
  We start by using an
  NSData
  pointer to grab the contents of the Animations.plist file. Next, we convert it
  into an
  NSDictionary for easy
  access of the data inside using the
  propertyListFromData: method of the
  NSPropertyListSerialization utility class.
  However, the
  NSDictionary
  we just got contains all of the animations in the
  .plist file, and we want only the
  animation that corresponds to the image name passed into our
  initialization function. We grab the subsection with
  [animData objectForKey:img], reassigning it
  to our
  NSDictionary pointer. We
  don’t need to worry about a memory leak while doing this because the
  original
  NSDictionary returned by
  propertyListFromData: is put into
  an auto-release pool before it’s handed to us.
  Now that we have only the data that pertains to this animation,
  we will extract the
  Animation and
  AnimationSequence information from
  it. For the
  Animation class, we
  need the data that applies to all of the
  AnimationSequences, including the frame
  height and width and the anchor offset.
  The rest of the entries into the dictionary comprise
  AnimationSequence data. However, we don’t
  know what their names are or how many there will be, so we need to use
  an
  NSEnumerator to
  iterate across all of the entries. We do this by calling
  keyEnumerator: on the
  animData dictionary.
  We also need a place to store the
  AnimationSequences we create, so we
  initialize our
  sequences variable
  as an empty
  NSMutableDictionary.
  Since we are calling
  alloc on sequences here, we need to remember
  to release it in the
  dealloc
  function, as well as all of the entries inside it.
  Next, we start the
  while loop
  to iterate through all of the keys in our
  animData dictionary. The entries are not in
  a particular order, so we need to be sure to skip any entries that we
  have already read (and therefore are not
  AnimationSequence dictionaries). We know
  that any entry that is an
  NSDictionary will represent an
  AnimationSequence, so we check the type of
  entry using
  isKindOfClass:. If the
  entry is not an
  NSDictionary, we
  skip it.
  Finally, we know we have an
  AnimationSequence dictionary. We grab it
  from
  animData using
  objectForKey: and then send it into a newly
  allocated
  AnimationSequence object.
  Once the
  AnimationSequence is
  initialized, we can store it into our
  sequences dictionary. Since we called
  alloc on the
  AnimationSequence when we were creating it,
  we need to call
  release. Rest
  assured, however, that it was retained by
  NSDictionary when we inserted it into
  sequences.
  And now the rendering function:
  //Animation.mm - (void) drawAtPoint:(CGPoint) point withSequence:(NSString*) sequence withFrame:(int) frame { AnimationSequence* seq = [sequences valueForKey:sequence]; CGRect currframe = seq->frames[frame]; [[g_ResManager getTexture:image] drawInRect:CGRectMake( point.x+(seq->flipped?currframe.size.width:0)-anchor.x, point.y-anchor.y, seq->flipped?-currframe.size.width:currframe.size.width, currframe.size.height) withClip:currframe withRotation:0]; }
  Compared to the initialization function, rendering is fairly
  simple. We first grab the
  AnimationSequence as detailed by the
  sequence parameter. We then extract
  the
  CGRect that represents the
  portion of a
  GLTexture that
  contains the frame we want from the
  AnimationSequence and store it as
  currFrame.
  Next, we grab the
  GLTexture
  from the
  ResourceManager and call
  drawInRect:, creating the
  destination rectangle using the utility function
  CGRectMake and using
  currFrame as our source rectangle. Creating
  the destination rectangle is a little tricky because we have to
  consider whether the animation is flipped and whether there is an
  anchor offset. Fortunately, the math is simple, as you can see.
  Sprite class
  Now that we have an
  Animation, we can create a
  Sprite class that
  will allow us to use it. The
  Sprite
  class needs to keep track of what animation it is associated with,
  which
  AnimationSequence is
  currently being drawn, when that sequence started, and what the
  current frame is.
  Because the
  Sprite class will
  be used directly in our game code, we are going to make an
  autorelease constructor instead of the
  typical
  init function. We will also
  need a rendering function that accepts a position to draw the sprite,
  an update function that will calculate when the current frame needs to
  change based on the timeout values from the current
  AnimationSequence, and finally, a way to set
  which sequence we want to display:
  //Sprite.h @interface Sprite : NSObject { Animation* anim; NSString* sequence; float sequence_time; int currentFrame; } @property (nonatomic, retain) Animation* anim; @property (nonatomic, retain) NSString* sequence; + (Sprite*) spriteWithAnimation:(Animation*) anim; - (void) drawAtPoint:(CGPoint) point; - (void) update:(float) time; @end
  Notice the
  @property tag
  we are using for
  anim
  and
  sequence variables. It will
  cause a special setter function to be called whenever we assign
  anything to
  anim or to
  sequence. Since
  retain is one of the keywords, it will
  automatically be retained when we set these.
  Although the default setter is acceptable for
  anim, we want to specify our own setter
  function for
  sequence because
  whenever the sequence changes we need to reset the
  sequence_time and
  currentFrame back to zero. We do this by
  overloading the setter function named
  setSequence in the following code.
  The implementation of
  Sprite
  follows:
  //Sprite.m + (Sprite*) spriteWithAnimation:(Animation*) anim { Sprite* retval = [[Sprite alloc] init]; retval.anim = anim; retval.sequence = [anim firstSequence]; [retval autorelease]; return retval; } - (void) drawAtPoint:(CGPoint) point { [anim drawAtPoint:point withSequence:sequence withFrame:currentFrame]; } - (void) update:(float) time{ AnimationSequence* seq = [anim get:sequence]; if(seq->timeout == NULL){ currentFrame++; if(currentFrame >= [anim getFrameCount:sequence]) currentFrame = 0; } else { sequence_time += time; if(sequence_time > seq->timeout[seq->frameCount-1]){ sequence_time -= seq->timeout[seq->frameCount-1]; } for(int i=0;i<seq->frameCount;i++){ if(sequence_time < seq->timeout[i]) { currentFrame = i; break; } } } } - (void) setSequence:(NSString*) seq { [seq retain]; [self->sequence release]; self->sequence = seq; currentFrame = 0; sequence_time = 0; }
  The constructor function needs to be statically accessible (you
  call it before an object exists, not on an object), so we use
  + instead of
  − in the definition. It starts by allocating
  and initializing a
  Sprite
  object.
  We then set the animation to the
  anim variable sent in the parameter.
  Remember that we declared this as a
  retain parameter, so it will automatically
  be retained when we set this, meaning we need to release it in our
  dealloc function.
  We start the first
  AnimationSequence by
  using a utility function to ask the
  Animation which we should use first and then
  setting it to
  sequence. This is
  also a property, but it will be calling our custom setter, the
  setSequence: function.
  Finally, we add the new
  Sprite to an
  autorelease pool and return it.
  The rendering function is very simple: just pass the point to
  draw at, the current sequence, and the current frame into
  the
  Animation
  drawAtPoint: function we discussed earlier.
  The update function needs to start by grabbing a pointer to the
  AnimationSequence we are currently
  running. Next, we need to check that it has a list of frame times; if
  it does not, we simply increment the frame every update loop. If we
  reach the end of the
  AnimationSequence, we start over from the
  first
  Animation.
  If there is frame time information, we need to know whether the
  current frame should be incremented. The
  time parameter of the update function
  represents the amount of time that has elapsed since the last call to
  update, so we can simply add this to the
  sequence_time variable and check whether it
  is larger than the timeout value of the current frame. We calculate
  the current frame based on the total
  sequence_time elapsed.
  The
  setSequence: function is
  our overloaded setter function, as we already mentioned. A normal
  setter function has two jobs: retain the new object being assigned to,
  and release the old one. Our custom setter will also initialize the
  currentFrame and
  sequence_time to zero so that the new
  sequence starts at the beginning.
  Physics
  In our game, physics will be a simple matter of entity-to-world and entity-to-entity collision in two dimensions. In Level 4, we have water tiles, but that will be a special case of entity-to-world.
  Entities
  What are “entities”? As we mentioned in Physics Engine, the game world consists of the “level,” the “player,” and the “animals.” To simplify things, we will say the game world consists of the “level” and “entities.” An entity is anything that exists in the game world within a particular level.
  The player is an entity because she is in the world but can move around independently. The animals are also entities that can move around on their own. There can also be stationary entities, such as a button. Because a button has an animation and logic behind it (pressed and unpressed), it cannot be fully represented as merely a part of the tile-engine portion of the level; it must be an entity.
  All entities need to have a position, a way to render themselves, and an update function that gets called every frame to allow their internal logic to progress.
  We will begin by creating a class named
  Entity (which you already saw referenced in
  TileWorld):
  //Entity.h @interface Entity : NSObject { CGPoint worldPos; CGPoint velocity; Sprite* sprite; TileWorld* world; } @property (nonatomic, retain) Sprite* sprite; @property (nonatomic) CGPoint position; - (id) initWithPos:(CGPoint) pos sprite:(Sprite*)sprite; - (void) drawAtPoint:(CGPoint) offset; - (void) update:(CGFloat) time; - (void) setWorld:(TileWorld*) newWorld; @end
  Notice the pointer to
  TileWorld. This allows the entity to check for entity-to-world collisions on its own.
  The implementation of the
  Entity class is very simple, with the
  exception of the render function, which adds its position to a camera
  offset before calling
  drawAtPoint: on
  its
  Sprite:
  //Entity.m - (void) drawAtPoint:(CGPoint) offset { offset.x += worldPos.x; offset.y += worldPos.y; [sprite drawAtPoint:offset]; }
  The rest of the functions are simple or empty because
  Entity is a base class. We will create other
  classes that inherit from
  Entity and
  implement more interesting functionality.
  Entity-to-World
  Entities that can move but are limited by the physical properties of the level must detect entity-to-world collisions. But first we must have a way to know what physical property each level tile has.
  We do this in a way similar to the unique tile indexes of the tile graphics. Just as we had a file with texture indexes for each tile, we can have a file with physics flags for each tile. Our game design incorporates the following physics properties for tiles: normal, impassable, and water. Normal is applied to tiles that can be walked upon, impassable is used for walls and tiles that are otherwise inaccessible to entities, and water is for special tiles that represent water.
  Giving values (0 = normal, 1 = impassable, 2 = water), we could represent the first level with the following data:
  Not very exciting, but you get the point. Entities cannot enter any of the tiles with a “1,” but they can walk freely in any tile with a “0.”
  Now that we know what each tile is supposed to represent physically, we can perform entity-to-world collision detection (remember the separation between collision “detection” and collision “resolution”).
  The most naive approach is to do a rectangle-to-rectangle
  collision between the entity and each level tile. However, this quickly
  becomes inefficient as you have to perform
  levelWidth*levelHeight checks for all entities
  at every frame. Fortunately, we can make a number of
  optimizations.
  Since this check will be performed every update loop, we can assume that our entity is in a stable state at the start of our check (because any collisions would have been resolved during the last frame). Therefore, if the entity is not moving (its velocity is zero), we can skip the process altogether (for the current loop).
  Meanwhile, if the entity is moving, we only need to check for collisions along its intended path. Even better, if we know that our entities aren’t moving faster than one tile length per frame, we only need to check the end of the path on each frame (because it will give the same result as the middle of the path if we’re not passing through more than one tile).
  Unfortunately for us, the iPhone SDK does not come with a helpful 2D vector class. Therefore, we have to provide our own functions for vector math located in PointMath.h:
  add
  Performs vector addition on two
  CGPointvectors
  sub
  Performs vector subtraction on two
  CGPointvectors
  scale
  Performs scalar multiplication between a
  CGPointand a scalar float
  distsquared
  Returns the squared magnitude of a
  CGPointvector
  unit
  Returns a unit vector based on a
  CGPointvector
  towards
  A utility function that gets the vector between two
  CGPoints and derives its unit vector
  The following is an example of how we will check for collisions in
  our entity code using our functions for vector math (remember that the
  time parameter of our update function
  represents time passed since the last update):
  CGPoint distThisFrame = scale(velocity, time); CGPoint projection = add( worldPos, distThisFrame ); Tile* newTile = [world tileAt:projection]; if( overtile == nil || (overtile->flags & UNWALKABLE) != 0 ) { //tile is out of bounds or impassable projection = worldPos; //don't move } worldPos = projection;
  First we calculate the amount of space that will be moved during this frame. Note that we scale our velocity by the time passed during the last frame, instead of merely using the entire velocity. This is because our speed is in units per second, but our update function is not called once per second (hopefully it’s being called 33 times per second). If we used the full velocity, the entities would be moving far too quickly, and more importantly, they would speed up or slow down if our game loop was faster or slower.
  Next, we use vector math to add the
  distThisFrame to our current
  worldPos to get the projected position at the
  end of the current loop. Now we need to check to see whether that
  projected point is going to be in a safe tile or if it is
  impassable.
  We use the
  tileAt: function
  of
  TileWorld to grab the
  tile that is underneath that point. If the point is out of the bounds of
  the level, it will return nil.
  We check to see whether the result is nil or whether it has an
  UNWALKABLE physics flag. If either of
  these cases is true, we should not let the entity walk onto the tile, so
  we set the projection to our current position.
  Finally, we accept the projection vector as our new current position.
  Special Tiles
  Although impassable tile collisions can simply be resolved as soon as they are detected, other special types of tiles need extra game logic. To handle these situations, we can send the entity a message when it collides with a certain type of tile. This allows us to write code to allow entities to handle special tiles in their own way.
  If a hippopotamus is walking on land and collides with a water tile, it can move to a swimming state and keep going. In contrast, a cougar might decide it doesn’t want to get wet and the AI can use the message to turn the cougar around and find another path.
  Entity-to-Entity
  Entity-to-world collision is fairly straightforward. Your only goal is to keep entities from entering impassable tiles, and notifying them when they’re on special tiles such as water. Entity-to-entity collision, however, is more complex.
  With entity-to-entity collision, there are many results from different types of collisions. If an animal touches another animal, we could simply choose not to let the animals pass through each other, or we could ignore the situation if we want to allow animals to walk over each other. Or if they are two different types of animals, they might attack each other.
  When collision resolution takes many different forms due to game design, the line between physics code and game logic code gets blurred. Similar to special tiles, we can handle this through special messages alerting entities of events so that the entities themselves can handle the resolution logic.
  However, we might also want to resolve these collisions strictly
  at the
  GameState level. A
  single entity knows only as much about the world as we give it access
  to. However, the
  GameState envelops
  the entire world, including all of the entities, the level, and all of
  the game logic code.
  To clarify, consider the following example. For the first level, we want to determine when one of the emu chicks has walked onto the nest at the top of the level. To represent the nest, we will have a special kind of entity called a trigger area. A trigger area has a bounding box for determining physical collisions just like a regular entity, but it doesn’t necessarily have a graphical representation. Because the nest is already represented in the tiles of the level background, we don’t need to draw anything for this trigger area. Instead, we merely perform entity-to-entity collision between emu chicks and the nest trigger area.
  However, when we detect this collision, we want to remove the emu
  entity from the level. Simply sending a message to the entity won’t
  allow it to remove itself. We need the resolution to happen at the
  GameState level.
  In addition, not all entities require collision detection against each other. Using the previous example, although an emu chick should be tested for collision against the nest trigger area, the main character should not be. Because of the wide variety of expected results from entity-to-entity collision, it should be handled on a case-by-case basis.
  In the next section, we will examine the implementation of these cases.
  Level 1 Implementation
  As we stated before, the goal for Level 1 is for the player to control the main character in a way that will herd a flock of emu chicks into their mother’s nest.
  This will require the following resources:
  A tile level with a clear depiction of a nest and a wide space for the chicks to run around in
  A main character sprite with idle and walking animations
  An emu chick sprite with idle and running animations
  A mother emu sprite that will pace back and forth on the nest
  The game logic will include detecting when an emu chick has walked onto the nest and removing it from the world when it does, a nest trigger area for determining when emu chicks have reached the nest, and a level complete state transition when all emu chicks have reached the goal.
  AI logic will consist of:
  A very basic emu mother entity that walks back and forth on the nest
  An emu chick entity that will flock together with other emu chick entities and try to run away from the main character
  User input will include detecting a single touch on the screen from the player, translating that into world coordinates to detect the target square, and setting the main character’s velocity to move toward that square.
  gsEmuLevel
  To hold our tile engine, entities, and game logic for the first level, let’s create
  a class named
  gsEmuLevel that
  inherits from the
  GLESGameState
  class.
  gsEmuLevel will need to keep
  track of the
  TileWorld, the main
  character entity, the emu flock entities, and the emu mother entity, as
  well as the level’s complete state:
  @interface gsEmuLevel : GLESGameState { TileWorld* tileWorld; Tom* m_tom; EmuMother* mama; Emu** flock; int flock_len; BOOL win; } @end
  TileWorld
  In the constructor, we initialize the
  TileWorld object with the level data:
  - (id) initWithFrame:(CGRect)frame andManager:(GameStateManager*)pManager; { if (self = [super initWithFrame:frame andManager:pManager]) { [self setupWorld]; } return self; } - (void) setupWorld { tileWorld = [[TileWorld alloc] initWithFrame:self.frame]; [tileWorld loadLevel:@"lvl1_idx.txt" withTiles:@"lvl1_tiles.png"]; }
  In the
  render function of our
  game state, we tell the
  TileWorld to
  draw itself:
  - (void) Render { //clear anything left over from the last frame, and set background color. glClearColor(0xff/256.0f, 0x66/256.0f, 0x00/256.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); [tileWorld draw]; [self swapBuffers]; }
  Now that we have a level to walk around in, we need something to walk around in it.
  Main Character
  To start, we need the main character sprite. We want the main character to walk around or stand still, so we need walking and idle frames for all facings. Again we will use the draw flipped technique for the right-facing frames so that we need only up, down, and left frames on the texture. Figure 4-4 shows the sprite representing Tom O’Reilly.
  Once we have the sprite, we can create an
  Entity class to wrap around it. Then we can
  put this
  Entity object into
  gsEmuLevel where we will initialize it and
  hook it into the render and update loops. And since Tom is going to be
  controlled by the player directly, we can hook it up to the user input
  code as well.
  Entity
  The
  Entity class that represents Tom will be named, well, Tom. It is actually quite a simple class, as most of the
  rendering code will already have been handled by the
  Entity class it inherits from, and the
  entity logic will simply be to move toward a square designated by the
  player:
  @interface Tom : Entity { CGPoint destPos; } - (id) initWithPos:(CGPoint) pos sprite:(Sprite*)spr ; - (void) moveToPosition:(CGPoint) point; @end
  The
  initWithPos function
  simply calls the
  Entity initWithPos
  function and initializes the
  destPos to be equal to the current
  worldPos (otherwise, it would be moving when
  the game loop starts).
  The
  moveToPosition function
  will be called by the user input code of
  gsEmuLevel and will simply set the
  destination position to the given point in world coordinates.
  Tom also overrides the
  update: function inherited from
  Entity, like this:
  //Tom.m - (void) update:(CGFloat) time { float xspeed = 200*time, yspeed=200*time; //pixels per second.
  What is that
  200 value?
  Uh-oh, that’s a magic number! We should replace that with a
  define such as
  PLAYER_SPEED. Notice how we multiply it by
  time (like the
  distThisFrame
  example earlier) to get the scaled speed per frame. Next, we save the
  current
  worldPos in case our
  attempt at moving fails (because of a wall or the edge of the
  level):
  CGPoint revertPos = worldPos;
  And now we project the entity toward the destination position,
  first on the x-axis followed by the
  y-axis. If our speed causes us to go past the
  square (
  fabs(dx) < xspeed), we
  snap the distance traveled to end exactly at the destination position.
  Otherwise, we multiply the speed by positive or negative 1 depending
  on whether we’re moving “right” or “left.”
  float dx = worldPos.x - destPos.x; if(dx != 0){ if(fabs(dx) < xspeed){ worldPos.x = destPos.x; } else { worldPos.x += -sign(dx)*xspeed; } } float dy = worldPos.y - destPos.y; if(dy != 0){ if(fabs(dy) < yspeed){ worldPos.y = destPos.y; } else { worldPos.y += -sign(dy)*yspeed; } }
  Now we check to see whether we have moved to an acceptable tile.
  If not, we don’t want to simply stand still; it would be better to try
  to slide along the edge of whatever obstacle we have reached. First,
  we project ourselves using only the new
  x value, keeping our old
  y value as though we had no intention of
  moving in any direction but horizontal; if that projection works, we
  accept it and move on. If not, we try to project our position only in
  the
  y direction; again, if that
  works, we accept it and move on. Finally, if neither attempt works, we
  accept our failure and revert to the old position:
  if(![world walkable:worldPos]){ if([world walkable:CGPointMake(worldPos.x, revertPos.y)]){ worldPos = CGPointMake(worldPos.x, revertPos.y); } else if([world walkable:CGPointMake(revertPos.x, worldPos.y)]){ worldPos = CGPointMake(revertPos.x, worldPos.y); } else { worldPos = revertPos; } }
  Now that we know what the new position will be, we should recalculate the way our sprite is facing and update the animation sequence:
  //calculate current direction NSString* facing = nil; if(dx != 0 || dy != 0){ if(fabs(dx) > fabs(dy)){ if(dx < 0) { facing = @"walkright"; } else { facing = @"walkleft"; } } else { if(dy < 0){ facing = @"walkup"; } else { facing = @"walkdown"; } } } else { facing = @"idle"; } //if the direction has changed, we also change our animation if(![sprite.sequence isEqualToString:facing]) { sprite.sequence = facing; }
  Finally, we tell our sprite to update:
  [sprite update:time]; }
  gsEmuLevel
  Now that we have filled out our entity, we can plug it into our level. We
  already have the
  m_tom variable
  defined in the header; all we need to do is use it.
  First, we initialize it inside the
  setupWorld: function by loading the
  animation, initializing an instance of Tom, and assigning it to our
  m_tom variable. Then we add
  m_tom to
  tileWorld’s list of entities to render and
  we set the camera to point at
  m_tom’s
  worldPos:
  //gsEmuLevel.m Animation* tomanim = [[Animation alloc] initWithAnim:@"tom_walk.png"]; m_tom = [[Tom alloc] initWithPos:CGPointMake(100, 100) sprite:[Sprite spriteWithAnimation:tomanim]]; [tileWorld addEntity:m_tom]; [tileWorld setCamera:m_tom->worldPos];
  Next, inside
  Update:, we call
  m_tom’s
  update: function.
  We don’t need to add
  m_tom to
  the
  Render: function because
  TileWorld will have already taken care of it
  because we added him to the list of entities to render.
  Finally, we need to be sure to call
  release on
  m_tom in the destructor.
  User input
  The user input code is very simple. We will receive a call to
  touchesEnded: when the player taps the
  screen.
  We can get the position (in screen coordinates) of the tap by
  calling
  [self touchPosition:touch].
  We then convert that position from screen coordinates to world
  coordinates using the
  worldPosition:
  helper function we wrote in the
  TileWorld class.
  Finally, we set the new destination (in world coordinates):
  [m_tom moveToPosition:[tileWorld worldPosition:[self touchPosition:touch]]];
  Emu Chicks
  Now we’ll let loose the sprite for emu chicks that we introduced in
  “Animation.” We attach that to a new class called
  Emu which will also inherit from the base
  Entity class. This will allow us to
  write the AI code that controls the emus inside the
  Emu entity.
  We want our emu chicks to flock together. There are many ways to implement a flocking algorithm, but they should all exhibit some common behavior:
  Don’t stay too close to neighbors at short range.
  Calculate a velocity based on the average velocity of neighbors.
  From here, based on the type of flocking behavior you wish to exhibit, you can add more rules. For example, if you wish to keep a tight cloud-shaped grouping, you may add:
  Try to move toward the center of mass of the flock.
  In addition to the flocking behavior, we also want to assert two more rules:
  If the main character is nearby, run away from him.
  If the emu mother is nearby, run toward her.
  When we write this code in the
  Emu entity and create multiple instances, we
  should see emergent behavior from the entities in the form of a flock of
  emus that will run away from the main character, and run toward their
  mother if they are in close proximity.
  These rules may seem to be in conflict with each other, and in some cases they are; but resolving that conflict is actually rather easy. Since each rule dictates a velocity, we can consider them to all be impulses being added to the emu entity’s current velocity. When we calculate all of them and add them together, we get a single velocity that represents the direction in which the emu entity should move.
  Entity
  The
  Emu entity class will
  need a velocity vector to represent its speed and
  direction. When we calculate our flocking, however, we want to count
  only the current velocity before applying any new velocities based on
  the result of the flocking algorithm; therefore, we need to keep a
  temporary velocity used for flocking calculation, which we name
  nextVel.
  Finally, we also want to add a velocity to individual emus to
  keep them away from the other emus. That is what
  collision_tweak is for:
  @interface Emu : Entity { CGPoint velocity; CGPoint nextVel; CGPoint collision_tweak; } - (id) initWithPos:(CGPoint) pos sprite:(Sprite*)spr; - (void) flockAgainst:(Emu**) others count:(int)count; - (void) avoidPlayer:(Entity*) mainChar; - (void) runToGoal:(Entity*) emuMother; - (void) update:(CGFloat) time; @end
  Notice that we have three separate AI functions here, in
  addition to the update function. The first one,
  flockAgainst, definitely needs to be
  separate from
  update: because it
  needs to be called on each entity separately before the individual
  entity behavior should take place.
  The next two,
  avoidPlayer:
  and
  runToGoal:, could technically
  be wrapped into
  update: if we
  needed them to be. However, they can each act individually, and
  therefore make a cleaner design as separate functions; if we were to
  simply omit a call to one of them, you would still see all of the
  other set of behaviors you expected to see.
  The implementation of
  flockAgainst: is a bit complicated. To
  start, we will utilize 95% of our current velocity in our next
  velocity. This represents the fact that things such as changes in
  flock movement do not happen immediately, but gradually over time.
  Otherwise, you might see a flock
  of birds moving together in the sky immediately freeze, then drop to
  the ground and land:
  //Emu.m - (void) flockAgainst:(Emu**) others count:(int)count { nextVel = scale(velocity, 0.95f); //carry inertia int neighbor_count=0, vel_count = 0; CGPoint avg_pos = CGPointMake(0,0), avg_vel=CGPointMake(0,0); collision_tweak = CGPointMake(0,0);
  Next, we will perform calculation of short-range repulsion of neighbors. Rather than repulsing against all emus, we find the nearest and move away from only that one.
  For each emu in the flock (not counting ourselves), we first grab the distance between the current emu and the one being flocked against. We keep track of which emu in the flock is closest to this emu, and we also count the average velocity of all flock members that are within two tiles of the current emu. In addition, we count the average position of all flock members within four tiles of the current emu.
  By considering only average velocity and positions of emus within a certain radius, we can separate our emus into smaller groups that still flock independently of each other. If those two subflocks were to get close to each other, they would merge into one giant flock again:
  float nearest_dist=0.0f; Emu* nearest_emu; float velCutoff = 2*TILE_SIZE; float posCutoff = 4*TILE_SIZE; for(int i=0;i<count;i++){ if(self == others[i]) continue; float distSQ = distsquared(worldPos, others[i]->worldPos); if(distSQ < nearest_distSQ || nearest_dist == 0) { nearest_distSQ = distSQ; nearest_emu = others[i]; } if(distSQ < velCutoff*velCutoff) { vel_count++; avg_vel = add(avg_vel, others[i]->velocity); } if(distSQ < posCutoff*posCutoff) { neighbor_count++; avg_pos = add(avg_pos, others[i]->worldPos); } }
  The preceding code updates our knowledge of the flock as seen from the point of view of the current entity:
  We know which chick is closest, storing it from the
  othersarray into
  next_emu.
  We know the number of velocities to count from other chicks (storing it in
  vel_count) and the average velocity of all the nearby chicks (in
  avg_vel).
  We know the number of chicks in the range we need to consider (storing it in
  neighbor_countfor positions and
  vel_countfor velocities) and the average of all their positions and velocities (in
  avg_posand
  avg_vel).
  Next, we calculate a velocity that will push us away from the nearest emu so that we maintain a comfortable distance:
  float emuradius = TILE_SIZE/2; if(nearest_dist < emuradius*emuradius) { CGPoint away = toward(nearest_emu->worldPos, worldPos); float overlap = emuradius - sqrt(nearest_dist); collision_tweak = scale(away, overlap*0.25f); }
  Then we try to move toward the average flock position. Technically, we were calculating only the total position and velocity earlier. We still need to divide it by the number of neighbors to get the average. Once we know where the average position is, we look at our distance from that point and ignore it if we’re too close or too far from the flock center. Otherwise, we add a velocity toward the flock center, but we scale it based on how far away it is so that if the emu is close it only waddles, but if the emu is far away it hurries to catch up:
  //make the sum into an actual average. avg_pos = scale(avg_pos, 1.0f/neighbor_count); CGPoint to = toward(worldPos, avg_pos); float distToFlock = sqrt(distsquared(worldPos, avg_pos)); if(distToFlock > 34 && distToFlock < 128){ CGPoint accel = scale(to, sqrt(distToFlock −32)); //using a sqrt(distance) relationship here... // (distance) was a bit too exaggerated. nextVel = add(nextVel, accel); }
  Finally, we want our velocity to emulate the overall flock
  velocity. However, we want it to represent only a portion of our
  calculated velocity, so we apply it as a ratio with one part average
  velocity and 10 parts currently calculated velocity (the repulsion and
  attraction values we put into
  nextVel, respectively):
  //attract to neighbors' velocity if(vel_count > 0) { avg_vel = scale(avg_vel, 1.0f/vel_count); //average velocity. //take weighted average between avg_vel and nextVel. CGPoint sum = add(scale(avg_vel, 1.0f), scale(nextVel, 10.0f)); nextVel = scale( sum, 1.0f/11.0f ); } }
  Note that this is all stored in
  nextVel—we don’t actually apply it to our
  real velocity yet because this function is about to be called on the
  rest of the emus in the level and we want them to perform their
  calculations based on our current velocity, not the new one we just
  calculated for ourselves.
  Next, we look at the logic used to run away from the main character. After the previous function, this should seem familiar:
  - (void) avoidPlayer:(Entity*) mainChar { CGPoint away = toward(mainChar->worldPos, worldPos); float dist = sqrt(distsquared(mainChar->worldPos, worldPos)); if(dist < 64){ //vision radius? CGPoint accel = scale(away, 300.0f/dist); nextVel = add(nextVel, accel); } }
  The function to attract the emu toward the emu mother should also be easy to understand now. There is, however, a slight twist: if we are within 30 units of the goal, we also add a repulsive velocity. This makes the emus stand near the emu mother, but not directly on top of her:
  - (void) runToGoal:(Entity*) emuMother { CGPoint away = toward(emuMother->worldPos, worldPos); float dist = sqrt(distsquared(emuMother->worldPos, worldPos)); if(dist < 30){ //vision radius CGPoint accel = scale(away, 300.0f/dist); nextVel = add(nextVel, accel); } if(dist > 34 && dist < 128){ CGPoint accel = scale(away, −0.5f*(dist-32)); nextVel = add(nextVel, accel); } }
  Finally, we get to the
  update: function. This generally looks a lot
  like Tom’s
  update: function, except
  that when the emu encounters an
  UNWALKABLE tile, it chooses a random
  direction in which to wander off. We also speed up or slow down the
  walk animation based on the magnitude of the velocity:
  - (void) update:(CGFloat) time{ CGPoint revertPos = worldPos; velocity = nextVel; worldPos = add(worldPos, scale(velocity, time)); worldPos = add(worldPos, collision_tweak); float dx = -velocity.x, dy = -velocity.y; Tile* overtile = [world tileAt:worldPos]; if(overtile == nil || (overtile->flags & UNWALKABLE) != 0){ //can't move here. worldPos = revertPos; float dir = (random() % 360) / 180.0f * PI; float mag = TILE_SIZE*2; velocity = PointFromPolarCoord(PolarCoordMake(dir, mag)); } if(dx != 0 || dy != 0){ NSString* facing = nil; if(fabs(dx) > fabs(dy)){ if(dx < 0) { facing = @"walkright"; } else { facing = @"walkleft"; } } else { if(dy < 0){ facing = @"walkup"; } else { facing = @"walkdown"; } } if(![sprite.sequence isEqualToString:facing]) { //NSLog(@"facing %@", facing); sprite.sequence = facing; } } float pixelsmoved = sqrt(dx*dx+dy*dy); //using distance-based animation for emus instead of time-based [sprite update:pixelsmoved/1000.0f]; }
  gsEmuLevel
  We already defined a list of
  Emu entities
  named
  flock. We can now insert the
  emus into the level. We begin implementing the flock by initializing
  this array in
  setupWorld::
  //gsEmuLevel.m flock_len = 10; Emu** emus; flock = malloc(flock_len *sizeof(Emu*)); for(int i=0;i< flock_len;i++) { emus[i] = [[Emu alloc] initWithPos:CGPointMake(200, 15*TILE_SIZE) sprite:[Sprite spriteWithAnimation:emuanim]]; [tileWorld addEntity: emus[i]]; }
  In the preceding code, we decided to use 10 emus to represent
  the flock, so we allocate 10
  Emu
  pointers—but notice that we used
  malloc(). That is a C function, not an
  Objective-C function. What
  gives?
  As we mentioned before, you can mix C, C++, and Objective-C if
  you know what you’re doing. In this case, we will need to remember to
  use
  free() to release this array of
  pointers when
  gsEmuLevel is
  deallocated. However, we used
  alloc
  for the
  Emu objects, so first we
  will iterate through that list and call
  release on each one.
  Next, we iterate through the list and initialize 10 new
  Emu objects, remembering to add them to
  TileWorld to be rendered. In
  Update:, we first call
  flockAgainst: on each flock member. Once we
  complete that, we can call
  avoid:,
  goal:, and
  update: to finalize their AI calculations
  and update their position and sprites:
  for(int i=0;i<flock_len;i++){ [flock[i] flockAgainst:flock count:flock_len]; } bool winning = true; for(int i=0;i<flock_len;i++){ [flock[i] avoid:m_tom]; [flock[i] goal:mama]; [flock[i] update:time]; }
  Again, we do not need to add anything to
  Render: because
  TileWorld will already render all of the
  emus for us.
  Emu Mother
  The goal of the level is to lead the emu chicks to the nest where their mother is waiting for them. We will represent the mother as an entity and limit her movements to the area of the nest; therefore, we will not need an additional entity to represent the nest. Simply colliding with the mother will mean the emu chick is over the nest.
  All we need spritewise is for the emu mother to walk to the left and right and occasionally perform an idle animation such as looking about (see Figure 4-5).
  The entity AI will be just as simple as that:
  typedef enum EmuMotherState { EM_WALKING = 0, EM_IDLING, } EmuMotherState; //EmuMother is a subclass of Emu, because we want // to reuse Emu's update method. @interface EmuMother : Emu{ EmuMotherState state; float state_timeout; CGRect bounds; //where we will wander around. } @end
  We define an enumeration to represent the two possible states the
  emu mother can be in: walking and idling. We also inherit from
  Emu rather than
  Entity because we want to reuse the
  Emu’s update function.
  We will use the
  state_timeout
  variable to determine when to switch between walking and idling.
  The bounds
  CGRect will
  represent an area the emu mother can walk in (notice that this is being
  used for AI and not for physics detection).
  We initialize the emu mother with a hardcoded bounds area:
  - (id) initWithPos:(CGPoint) pos sprite:(Sprite*)spr { [super initWithPos:pos sprite:spr]; bounds = CGRectMake(144,687, 192, 64); return self; }
  Next, we need an
  update:
  function that can move the emu mother between idle and walking states,
  and keep her within the bounds rectangle if she is walking.
  Because we inherit from
  Emu we
  can use all of the same variables for AI, including
  velocity and
  nextVel. We begin by setting
  nextVel to our current velocity and calling
  the
  Emu class
  update: function. This will move our entity
  forward, stopping if it hits any
  UNWALKABLE tiles (such as the fences on the
  left and right sides of the level), and set our walking animation
  sequence with the appropriate facing. Then we make sure we didn’t walk
  out of bounds, and snap our position back if we did.
  Next, we check to see whether time has expired on our current AI state; if so, we toggle to the next one. If we were previously idling, we choose a random direction and start walking. If we were walking, we start idling:
  - (void) update:(CGFloat) time { nextVel = velocity; CGPoint revertPos = worldPos; [super update:time]; state_timeout -= time; if(worldPos.x < bounds.origin.x || worldPos.x > bounds.origin.x+bounds.size.width || worldPos.y < bounds.origin.y || worldPos.y > bounds.origin.y + bounds.size.height ){ //wandered too far. worldPos = revertPos; state_timeout = 0.0f; } if(state_timeout <= 0.0f){ switch (state) { case EM_IDLING: { //pick a random direction for wandering. float dir = (random() % 360) / 180.0f * PI; float mag = TILE_SIZE*4; velocity = PointFromPolarCoord(PolarCoordMake(dir, mag)); if(fabs(velocity.x) < fabs(velocity.y)){ //make primary movement horizontal, because we don't //have up/down walk cycles for the emu mother. float tmp = velocity.x; velocity.x = velocity.y; velocity.y = tmp; } state = EM_WALKING; state_timeout = (random() % 1000) / 1000.0f * 1.5f + 0.5f; } break; case EM_WALKING: //idle a while. velocity = CGPointMake(0,0); state = EM_IDLING; state_timeout = (random() % 1000) / 1000.0f * 1.5f + 0.5f; break; } } }
  This behavior looks pretty convincing when we run it.
  Game Logic
  Now that we have all of the elements necessary, we can write game
  logic to figure out when the player reaches the win state. In the case
  of
  gsEmuLevel, the win state is
  achieved when all of the
  Emu entities
  are near the
  EmuMother.
  We can implement this check in the
  Update: function of
  gsEmuLevel by hijacking the
  Emu
  update loop and checking the distance from mama for each emu:
  bool winning = true; for(int i=0;i<flock_len;i++){ [flock[i] update: timeElapsed]; if( distsquared(mama.position, flock[i].position) > EMU_WIN_DISTANCE*EMU_WIN_DISTANCE) { winning = false; } }
  We begin by assuming the win state is true and proceed by
  determining whether any of the 10
  Emu
  entities are not close enough to the
  EmuMother. We do this by checking to see
  whether the distance between the mama and the current emu is larger than
  some number
  EMU_WIN_DISTANCE.
  Notice we are doing something strange, though; we’re actually
  checking whether the distance squared is greater than
  EMU_WIN_DISTANCE squared. This is a common
  speed optimization that becomes apparent when you look at the math
  involved in calculating distance.
  The formula for the distance C between points A and B is:
  Because we are trying to get C, the code would look like this:
  int resultC = sqrt( (A.x - B.x)* (A.x - B.x) + (A.y - B.y)* (A.y - B.y) );
  That formula requires four subtractions, two multiplications, one
  addition, and a call to the
  sqrt()
  function. Without getting into CPU architecture, addition and
  subtraction operations are very fast and a multiplication operation
  takes only slightly longer, but
  sqrt() is very slow in comparison.
  Fortunately, with a little math magic, we can get rid of the call
  to
  sqrt(). The trick is that we don’t
  care about the actual value of the distance; we care only about whether it is greater than some comparison
  value. If we simply remove the call to
  sqrt(), we get C squared, which is fine for
  our purposes if the value we compare it to is also squared! Therefore, simply comparing
  distSquared(...) > EMU_WIN_DISTANCE *
  EMU_WIN_
  DISTANCE will
  provide the same results in half the time.
  If any of the
  Emu objects are
  too far away, our
  checkWinState will
  get set to
  false and when we get out
  of the loop, we’ll know not to set the
  win condition to
  true:
  if(winning && ! win) { win = true; m_tom.celebrating = true; }
  If we have found a winning state and we haven’t already declared
  ourselves the winner, we set
  win to
  true and tell Tom to start
  celebrating.
  Sound
  The main sound effect we want for this level is to hear the emu chicks peeping as they search for their mother. Unfortunately, we don’t have an emu farm nearby, so we’re going to have to use a chicken sound instead.
  Fortunately, our players aren’t likely to be able to tell the difference. We don’t need an accurate sound; we just need one that makes the player feel like she is chasing a flock of avian babies.
  We have added a sound called trimsqueak.mp3 to our project and we will
  play it looped in the background music for this level. In gsEmuLevel.m, add the following lines of code
  to the bottom of
  setupWorld:
  [g_ResManager stopMusic]; [g_ResManager playMusic:@"trimsqueak.mp3"];
  The first line will stop whatever music may have been playing before (from the Main menu), and the second plays the sound we want for this level.
  Level 2 Implementation
  The goal for Level 2 is for the player to navigate to the back of a cave of sleeping lions, pick up the McGuffin, and return alive.
  This will require the following resources:
  A tile level showing the inside of a cave.
  The same main character sprite from before, with added death animations.
  A lion sprite, including sleeping, napping, waking, and attacking animations. We will include both lion and lioness for visual variety, but they will behave the same.
  A McGuffin object, such as a coin or a star.
  The game logic will include determining when the player has reached the McGuffin (“level complete”) or has been eaten by a hungry lion (“level failed”).
  AI logic will consist of a
  lion
  entity. The lions will be strewn across the cave floor while sleeping,
  forcing the player to weave between them to get to her goal. Occasionally,
  they may wake up and look around. If they spot the main character, they
  will swipe at him; and if he gets hit, the player loses.
  User input will be the same as Level 1, with no changes.
  gsLionLevel
  If our levels were basically the same as far as game logic and user
  input, with only the tile level and entities changing, we would reuse
  the same
  GLESGame
  State class for each level.
  But because each of our levels will exhibit strongly different game logic, and in some cases user input, the best way to represent them is by separating each into its own class.
  Therefore, we will create a new class named
  gsLionLevel that inherits from
  GLESGameState. We can
  copy and paste the
  TileWorld and user
  input code from
  gsEmuLevel. When
  initializing the
  Tom entity, we can
  use the same code, just modifying the start position to the bottom of
  our level:
  @interface LionLevel : GLESGameState { TileWorld* tileWorld; Tom* m_tom; Lion** m_lions; int lions_length; Entity* m_mcGuffin; }
  The
  Tom class here is exactly the same as the last level.
  Lion is a new entity class we’ll create to
  represent the lions in the level. And the rest of this code should be
  easily recognizable by now, so we’ll move on.
  TileWorld
  The level we create for our
  TileWorld class
  should represent a cave full of lions with a space in the back for the
  McGuffin to lie, something similar to Figure 4-6.
  Again you can see the unique tile strip that will be loaded (see Figure 4-7).
  Here are the contents of the lvl2_idx.txt file which includes the tile indexes and physics flags:
  12x20 10,11,9,10,8,9,8,11,10,11,8,10 10,8,9,8,10,11,9,10,8,9,11,8 11,9,11,8,1,1,0,1,10,10,9,8 11,8,8,14,0,0,0,1,15,11,11,10 10,14,1,1,0,1,0,0,1,1,15,9 8,1,1,0,0,0,1,0,0,1,1,8 9,1,1,2,1,2,2,1,0,1,1,11 11,1,1,2,1,1,2,1,0,1,1,8 11,1,1,1,2,1,1,1,0,1,1,10 8,1,2,1,1,2,1,1,2,1,1,8 11,1,2,1,1,1,2,2,1,1,1,11 10,12,2,2,1,1,1,1,1,1,13,10 10,8,12,2,2,1,1,2,1,13,11,9 11,9,8,12,2,2,2,1,13,10,9,11 9,11,10,10,1,2,2,1,9,11,10,8 11,8,7,6,7,6,7,6,7,6,10,8 8,10,4,3,3,4,3,3,3,3,8,11 10,4,5,3,3,3,3,3,3,3,3,8 5,3,3,3,5,3,3,3,3,3,5,3 3,3,4,3,3,3,3,3,5,3,3,3,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,1 1,1,1,0,0,0,0,0,0,1,1,1 1,1,1,1,0,0,0,0,1,1,1,1 1,1,1,1,0,0,0,0,1,1,1,1 1,1,0,0,0,0,0,0,0,0,1,1 1,1,0,0,0,0,0,0,0,0,1,1 1,0,0,0,0,0,0,0,0,0,0,1 0,0,0,0,0,0,0,0,0,0,0,0 0,0,0,0,0,0,0,0,0,0,0,0
  McGuffin
  As we described earlier, the McGuffin is a very simple object. It merely sits in
  place until the player comes along to pick it up. To that end, we can
  simply use the
  Entity class
  initialized with the McGuffin sprite.
  Since there is no AI to deal with, just game logic for detecting player-to-McGuffin collision, we’ll move on.
  Main Character
  Tom will move and act the same as in the first level, but he’ll need additional code to handle being attacked by lions. We add the ability to play back a dying animation sequence, and some flags to disable moving around while the character is dead.
  The
  dying flag is simply a
  Boolean, added to Tom.h:
  bool dying; //flag for when death animations are playing. // set in dieWithAnimation, used in update to modify movement // and facing code. Does not reset.
  We then need a method to start the dying process:
  //used on player death. // Caller can pass in special death animations per situation, // such as drowning or mauling. - (void) dieWithAnimation:(NSString*) deathanim { if(!dying){ //You only die once. dying = true; sprite.sequence = deathanim; } }
  Next, we need to modify Tom’s update method. We need to make sure
  the character doesn’t move around while dying, and the easiest way to do
  that is to just reset
  speed to
  0 while in the dying state:
  - (void) update:(CGFloat) time { float xspeed = 200*time, yspeed=200*time; //pixels per second. if(dying){ //you're not a zombie, hold still. xspeed = yspeed = 0; }
  We also need to make sure the dying animation isn’t clobbered by other character animations. We do this near the end of the update method:
  if(dying){ //let the dying animation proceed as specified in the animation.plist. } else { if(![sprite.sequence isEqualToString:facing]) { sprite.sequence = facing; } } [sprite update:time]; }
  Lion Entities
  The challenge of sneaking around the sleeping lions is the whole point of this level. To make this fun, we need to ensure that the player will have to go near the lions, while also making sure to give the player plenty of visual feedback about the current state of each lion.
  Specifically, we will give the lions the following AI states:
  - Sleeping
  While sleeping, the lions will not be awakened no matter how close the main character gets to them. After a random period of time, the lions will move to the Napping state.
  - Napping
  If the player gets close to a napping lion, the lion will move to the Alert state. If the lion is not alerted after a period of time, it will return to the Sleeping state.
  - Alert
  The lion is still lying down, but it has both eyes open and its head up, looking around for the main character. If he is in the lion’s cone of vision, the lion will move to the Attacking state.
  - Attacking
  The lion has spotted the character and is swiping at him. If the lion is near the character, the character will become incapacitated and the player will fail the level.
  We will arrange the lions on the level so that there is only a little space between them for the player to creep through. The player will have to wait and watch for the right time to move, being careful not to walk onto any of the lions, or get too close to a napping lion, or be within the field of vision of an alert lion.
  The lions do not move, and although they are several tiles wide, they will be facing only left or right (so we do not need “up” and “down” facing frames, and we can mirror the frames, so we really need only one set of “left” facing animations). Notice again that the sprite is wrapped at 1,024 pixels (see Figure 4-8).
  Entity
  The
  Lion class will inherit from
  Entity. We will define an enumeration that
  specifies all of the different AI states the lion can be in. We will
  also use a
  float to count the
  amount of time the lion has been in a state (so that we can decide
  when to switch from sleeping to napping, etc.) and an integer to keep
  track of how much the player has disturbed the lion’s sleep by being
  too close. Finally, the lions can be facing left or right, which we
  will keep track of with the
  flip
  bool (if
  flip is
  true, they are facing to the right):
  typedef enum LionState { ASLEEP = 0, WAKING, ALERT, ATTACKING } LionState; @interface Lion : Entity { LionState state; float state_time; int m_rage; bool flip; } - (id) initWithPos:(CGPoint) pos sprite:(Sprite*)spr; - (void) wakeAgainst:(Entity*) other; - (void) obstruct:(TileWorld*)world; @end
  Notice the
  wakeAgainst: and
  obstruct functions. In each frame,
  we will check the distance between the player and the lion to see
  whether it should wake up, invoking the
  wakeAgainst: function if it should.
  In addition, we want to make sure the player does not walk
  through the lions. If these were entities that could move about, that
  would require entity-to-entity collision detection between the player
  and all of the lions. However, since the lions are stationary, we can
  cheat: each time a lion is created, it will access the
  TileWorld to get the two tiles it is on top
  of, and set their physics flags to
  UNWALKABLE. That is what the
  obstruct function will do.
  Let’s look at the implementation of this class. First, we have a
  utility function that gives us the
  AnimationSequence
  names that should be used for each state the lion can be in. If the
  lion is flipped we need to call the corresponding
  -flip sequences:
  - (NSString*) animForState:(LionState) s { NSString* states[] = { @"asleep", @"waking", @"alert", @"attacking", }; NSString* states_flip[] = { @"asleep-flip", @"waking-flip", @"alert-flip", @"attacking-flip", }; return (flip?states_flip:states)[s]; }
  Next, we have a useful function for switching states. Because we
  want to make sure to change not only the state variable but also the
  sprite animation, and additionally reset the
  state_time and rage counter whenever we
  change state, we tuck all of those operations into one tidy
  function:
  - (void) setState:(LionState) s { state = s; sprite.sequence = [self animForState:s]; state_time = 0; m_rage = 0; }
  The initialization function is short and sweet. We accept a new
  position, sprite, and facing, and set our initial AI state to
  ASLEEP. Remember that the
  setState: function sets our sprite sequence,
  state_time, and rage counters for
  us so that we don’t need to worry about initializing them:
  - (id) initWithPos:(CGPoint) pos sprite:(Sprite*)spr facingLeft:(bool)faceleft { [super initWithPos:pos sprite:spr]; flip = !faceleft; [self setState:ASLEEP]; return self; }
  The update function is the heart of our lion AI code. It begins
  by counting up the elapsed
  state_time. We then branch through a
  switch statement based on our
  current state. If we are asleep, we will periodically and randomly
  check to see whether we should wake up or keep sleeping. If we are
  waking, we will periodically and randomly decide to switch to the
  ALERT state, back to the
  ASLEEP state, or neither. If we are alert
  but our rage counter is empty, we will fall back to the
  WAKING state after two seconds have elapsed.
  And finally, if we are attacking but our rage counter is empty, we
  will return to the
  ALERT state
  after two seconds:
  - (void) update:(CGFloat) time { state_time += time; switch (state) { case ASLEEP: //5 times a second, take a 2% chance to wake up. if(state_time > 0.2f){ if(random() % 1000 < 20){ [self setState:WAKING]; } else { state_time = 0; } } break; case WAKING: if(state_time > 0.2f){ if(random() % 1000 < 20){ [self setState:ALERT]; } else if(random() % 1000 < 20){ //narcoleptic lions. [self setState:ASLEEP]; } else { state_time = 0; } } break; case ALERT: if(state_time > 2.0f && m_rage == 0){ [self setState:WAKING]; } break; case ATTACKING: if(state_time > 2.0f && m_rage == 0){ [self setState:ALERT]; } break; default: break; } [sprite update:time]; }
  But none of this code has anything to do with the player’s proximity to the lion yet. To check the proximity between the lion and the player, we need a pointer to the player. Therefore, we have another function that will be called on each loop (in addition to the update function) to perform that duty:
  - (bool) wakeAgainst:(Entity*) other { //each state has a different triggering radius... // you can get closer to sleeping lions, // and should stay further from alert lions. float vision[] = { TILE_SIZE, TILE_SIZE*2, TILE_SIZE*3, TILE_SIZE*3, }; float distSQ = distsquared(self.position, other.position); if(dist < vision[state]*vision[state]){ m_rage++; } else { m_rage--; if(m_rage < 0) m_rage = 0; } //how much rage is needed to advance to the next rage state int ragemax[] = { 1, 20, 20, 20, }; if(m_rage > ragemax[state]){ if(state == ATTACKING){ m_rage = ragemax[state]; //can't rage more. } else { [self setState:state+1]; } } //attack hit detection against the player. if(state == ATTACKING){ CGPoint attackoffset = CGPointMake(-TILE_SIZE, 0); if(flip){ attackoffset.x = - attackoffset.x; } attackoffset = add(worldPos, attackoffset); float dist = distsquared(attackoffset, other.position); if(dist < 64*64){ //kill player. return true; } } return false; }
  First, we have an array called
  vision with values that represent the
  distance at which the lion can sense the character’s presence,
  corresponding to which state the lion is in. When sleeping, he will be
  disturbed only by the character standing right next to him, but when
  he is alert, he will see the character from three squares away.
  Next, we calculate the distance (squared) from the character and compare it to the appropriate vision level (again squared). If the character is in range, the rage counter increases; otherwise, it decreases. We need to be sure to cap it at 0 on the low end, but we don’t have to cap the high end here.
  When the rage counter increases past a certain point, we will
  change states. That point is based on our current state, so just like
  vision, we will use another array
  of values to represent this. When the lion is
  ASLEEP, the player needs to be within range
  for only one update loop before the lion switches to
  WAKING. If the lion is
  WAKING, it will wait for the rage counter to
  reach 20 before switching to
  ALERT,
  and the same from
  ALERT to
  ATTACKING. If the lion is already
  ATTACKING, it has no greater recourse, so we
  will cap the rage counter.
  If the lion is attacking, we need to check whether the player is
  within attack range. We check the player’s position against the front
  of the lion. We have to take the
  flip variable into account here, and modify
  the check position if the lion is flipped. When the player is within
  attack range, we simply return
  true
  from
  wakeAgainst; it will be up to
  the caller to actually kill the player.
  Note
  The values in
  vision and
  ragemax directly affect game
  play. If we were to increase the vision range of the lion or
  decrease the rage limits between states, we would make the game much
  harder for the player. Conversely, changing these values in the
  opposite direction would make it easier. These are the kinds of
  values game designers want to be able to play with to “tweak the
  game”; tuning them to get the feel just right.
  In fact, if we wanted to add a “difficulty level” feature to allow the player to choose between playing the game in “easy mode” or “hard mode,” we could tweak these values one way or the other based on the difficulty the player selected.
  And lastly, the
  obstruct:
  function simply grabs the two tiles the lion is hovering over and sets
  their flags to
  UNWALKABLE, like
  so:
  - (void) obstruct:(TileWorld*) world { Tile* t = [world tileAt:worldPos]; //right square t->flags |= UNWALKABLE; //left square, so offset x by -TILE_SIZE t = [world tileAt:CGPointMake(worldPos.x-TILE_SIZE, worldPos.y)]; t->flags |= UNWALKABLE; }
  gsLionLevel
  Integrating the
  Lion
  class into our
  gsLionLevel requires that we modify the
  setupWorld: and
  update: functions.
  In
  setupWorld:, we begin by
  hardcoding the position and direction of our lions. If we were reusing
  this state for multiple levels, it would be worth it to load these
  values from a resource file instead of hardcoding them. But we’re not,
  so we don’t.
  Next, we allocate the
  Animation and an
  array of lion pointers. We then iterate that array and create and
  initialize each
  Lion entity similar
  to the way we did the
  Emu entities
  from the previous level, but with a twist. We are using the hardcoded
  positions and facings and are randomly selecting between the male and
  female lion textures when we initialize our
  Sprite for each lion.
  Wrapping up, we add each
  Lion
  entity to the
  TileWorld for
  rendering purposes and then call the
  obstruct: function from before to make sure
  the player can’t walk over them:
  int lion_positions[] = { 2,11, 4,13, 4,14, 5,11, 5,9, 6,12, 8,14, 8,8, 9,13, 9,9, 10,12, }; bool lion_faceleft[] = { false, true, true, false, true, false, true, true, false, true, true, }; int lioncount = 11; Animation* lionanim = [[Animation alloc] initWithAnim:@"lion.png"]; Lion** lions; lions = malloc(lioncount*sizeof(Lion*)); for(int i=0;i<lioncount;i++) { Lion* otherlion = [[Lion alloc] initWithPos: CGPointMake((left+lion_positions[i*2+0])*TILE_SIZE, (bottom+lion_positions[i*2+1])*TILE_SIZE) sprite:[Sprite spriteWithAnimation:random()%100<25?lionanim:lioness] facingLeft:lion_faceleft[i]]; [tileWorld addEntity:otherlion]; [otherlion obstruct:tileWorld]; lions[i] = otherlion; } [lionanim autorelease]; m_lions = lions; lions_length = lioncount;
  The change to the update function is very simple; just call the
  wakeAgainst: and
  update: functions and we’re finished
  plugging in the lions:
  for(int i=0;i<lions_length;i++){ if([m_lions[i] wakeAgainst:m_tom]){ [self onFail]; [m_tom dieWithAnimation:@"dying"]; } [m_lions[i] update:time]; }
  Game Logic
  Unlike the previous level, we have two end states in Level 2. If a lion swipes the main character, we will go to the level failed state. However, if the main character reaches the McGuffin and returns to the bottom of the level, we will advance to the level completed state.
  First, we must determine when the player touches the McGuffin and remove it from the
  TileWorld render list to represent that the
  player has “picked it up.” Next, we determine when the player is back at
  the cave mouth and start celebrating.
  You can find this code in the
  Update: function of the
  gsLionLevel
  class:
  if(m_goal && distsquared(m_tom.position, m_goal.position) < 32*32){ //grab the macguffin and rush for the door. [tileWorld removeEntity:m_goal]; [m_goal release]; m_goal = nil; } if(m_goal == nil && m_tom.position.y < 8*TILE_SIZE){ //clear to the door, so win. m_tom.celebrating = true; }
  Sound
  When the lion takes a swipe at the player, we want to play a sound
  effect. We will inject this code directly into the
  setState: function of the
  Lion
  class:
  - (void) setState:(LionState) s { state = s; sprite.sequence = [self animForState:s]; state_time = 0; m_rage = 0; if(state == ATTACKING){ [g_ResManager playSound:@"tap.caf"]; } }
  Level 3 Implementation
  The third level is a puzzle. The main character is located in a room at the top of the level that contains three buttons. He will accomplish his goal or fail while staying in that room. The rest of the level consists of nine cells connected with gates that are controlled by the buttons in the upper room. Inside the cells are a mouse, a cat, and cheese. The cat is located in the upper-left cell, the mouse in the upper right, and the cheese in the bottom left.
  The player must move the main character to press the buttons in a way that will open gates for the mouse, allowing it to reach the cheese while avoiding the cat. Each time the player opens a gate next to the cell occupied by the mouse, the mouse will move into the open cell. Afterward, the cat will move into a random cell as well. If the cat and mouse end up in the same cell, the cat will eat the mouse and the player will lose. If the mouse ends up in the cell with the cheese, the player will win.
  This will require the following resources:
  A tile level showing the upper room and the nine cells, divided by walls. The walls between each cell should include an open space because the gates will be entities that are drawn on top of the walls, not a part of the tile level.
  The Tom sprite we used before.
  The button entities, with unpressed and pressed frames. There will be three buttons, each a different color.
  The gates (one vertically and one horizontally aligned), open and closed, with colored lights to indicate which buttons they will work with.
  The mouse sprite.
  The cat sprite.
  The cheese sprite.
  Since this level is a puzzle, the game logic here will be more
  complex than previous levels. There is still only one way to enter the win
  state (when the mouse reaches the cheese) and only one way to enter the
  lose state (when the cat captures the mouse), but we must also control the
  buttons and gates from the
  GameState
  level.
  The AI logic will consist of the cat and the mouse. The mouse is simple: when a door opens, the mouse will move to the next cell. The cat will decide a random cell to move to and will open its own gate.
  User input will be similar to Level 2, but now we will add a context-sensitive action. When the user touches the screen above the player’s avatar, we will check to see whether the touch is near a button; that context determines whether we react. Specifically, if the avatar is near a button, the resulting game change will be activated.
  gsMazeLevel
  Again, we will create a new
  GLESGameState
  class to represent our level:
  #define buttons_length 3 @interface MazeLevel : GLESGameState { TileWorld* tileWorld; Tom* m_tom; Tom *cat, *mouse; Entity* cheese; MazeDoor* door[2][2][3]; MazeButton* buttons[buttons_length]; MazeState state; //todo; mcGuffin* cheese; } @end
  As you can see, there are many types of entities in this level. As
  usual, we have
  Tom acting as the
  player’s avatar. We also have our trusty
  TileWorld behind the scene and the
  McGuffin entity (in
  this case, named
  cheese). Our new
  actors will be the
  CAT and
  MOUSE along with the
  MazeDoor and
  MazeButton.
  There will be one
  CAT and one
  MOUSE. They will each start in
  different corners of the “maze” portion of the level, and they will be
  controlled directly by the game logic.
  There will be three
  MazeButtons, all
  accessible to the player at the top of the level in the “control room”
  area. When the player performs a context-sensitive action, it will
  trigger game logic that sets off one of the buttons and the
  corresponding
  MazeDoor.
  There are nine cells, and there is a
  MazeDoor between each cell.
  TileWorld
  Figure 4-9 shows the
  TileWorld for
  Level 3.
  The level map doesn’t have any simple duplicated tiles, so it gets sliced up into the tile strip in Figure 4-10.
  Here is the lvl3_idx.txt tile index and physics information (“1” represents impassable and “0” represents normal tiles):
  10x physics flags define only the control room area the player will be bounded by. There is nothing to represent the cell walls that the cat and mouse must stay in. This is because their positions will be entirely defined by game logic; we will not need entity-to-world collision detection for them.
  Buttons
  The
  MazeButton entity
  itself is very simple. It merely shows an on or off state.
  It will render the states using the appropriate color for each button,
  which are all placed in one sprite for the usual efficiency in image
  loading. Because the buttons will be the target of a context-sensitive
  action, we will also need a way to determine whether each button is
  eligible for an action based on its proximity to the player’s
  avatar:
  //MazeButton.h @interface MazeButton : Entity { int color; } - (id) initWithPos:(CGPoint) pos sprite:(Sprite*)spr color:(int)col; //used to trigger the pressed animation. - (void) press; //used to determine if this button is under the specified entity - (bool) under:(Entity*)other; @end
  The integer will store which color the button represents; this will be useful both for displaying the appropriate sprite and for letting the game logic know which button was pressed during a context-sensitive action:
  MazeButton.m @implementation MazeButton - (id) initWithPos:(CGPoint) pos sprite:(Sprite*)spr color:(int)col { [super initWithPos:pos sprite:spr]; color = col; [self press]; //probably should start with up image. using down for now... return self; } static const NSString* down[] = { @"down", @"down-green", @"down-red", }; - (void) press { sprite.sequence = down[color]; } - (bool) under:(Entity*)other { return distsquared(worldPos, other.position) < TILE_SIZE*TILE_SIZE; } @end
  Initialization consists of storing the color and calling the
  press function.
  The
  press function sets the
  sprite to render the appropriate color animation. Based on the sprite
  data in the .plist for buttons.png, the “down” animation will
  automatically reset to the “up” animation once it is complete.
  Finally, the
  under: function
  will return
  true if the player is
  within one tile of the button when the function is called.
  Doors
  Similar to the
  MazeButton
  class,
  MazeDoors will also be very simple:
  @interface MazeDoor : Entity { } - (id) initWithPos:(CGPoint) pos sprite:(Sprite*)spr ; @end
  This entity holds the sprite that represents the door at various states of being opened, closed, opening, closing, and closed with a colored light. However, since the door entity itself does not control these states (rather, the game logic will), we do not need special code in this class beyond initialization:
  - (id) initWithPos:(CGPoint) pos sprite:(Sprite*)spr { [super initWithPos:pos sprite:spr]; sprite.sequence = @"closed"; return self; }
  The initialization function simply sets the first animation of the door as closed and leaves the rest to our game logic.
  Cat and Mouse
  The
  cat and
  mouse entities reuse the
  Tom class because the animation sequences are the same. Meanwhile,
  they have no direct AI. The AI is located in the game logic and merely
  commands the entities in the same way as user input commands Tom.
  We could have named the
  Tom
  class the “externally controlled avatar” to make it more general. But
  that just sounds silly.
  User Input
  This level’s context-sensitive action will take place inside the
  touchesEnded: function. This time, instead of
  immediately calling
  moveToPosition on
  Tom when we receive a single tap, we
  will check the distance between the tap and each button. If it is within
  a certain distance, we will interpret the input as a command to activate
  the action associated with the button. Therefore, we search on the three
  buttons and perform the appropriate game logic if we find one near
  enough to the touch:
  - (void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { UITouch* touch = [touches anyObject]; CGPoint touchpos = [tileWorld worldPosition:[self touchPosition:touch]]; float dist = distsquared(touchpos, m_tom.position); if(dist < 30*30){ if(state != WAITING_FOR_PLAYER) return; //figure out if the player has stomped a button. int buttonpressed = −1; for(int i=0;i<buttons_length;i++){ if([buttons[i] under:m_tom]){ [buttons[i] press]; buttonpressed = i; break; } } if(buttonpressed == −1) return; /// code snipped - performing game logic } } else { [m_tom moveToPosition:touchpos]; } }
  Game Logic
  Most of the code in this level is going to be about game logic. In fact, apart from the random relocation of
  the
  cat entity, there is no AI. The
  game proceeds purely on user input and the rules of the maze
  game.
  To begin, we’ll explicitly list all of the states the maze game can be in:
  typedef enum MazeState { MOVING_MOUSE=0, MOVING_CAT, WAITING_FOR_PLAYER, GOT_CHEESE, //winning state MOUSE_KILLED, //losing state } MazeState;
  We start in the
  WAITING_FOR_PLAYER state as we are waiting for
  the player to walk over to a button and activate it. Once the player has
  made her selection, we will enter the
  MOVING_MOUSE state as we open the
  corresponding door and move the mouse into the next cell.
  When that is complete, we must determine whether the mouse has
  reached the cheese, in which case, we can move to the
  GOT_CHEESE state.
  Otherwise, the game will automatically advance to the
  MOVING_CAT state, where we will randomly open
  a door and move the cat into a new cell.
  Lastly, we determine whether the cat has entered the same cell as
  the mouse, and if so, we enter the
  MOUSE_KILLED state; otherwise, we return to
  the
  WAITING_FOR_PLAYER state and the
  cycle begins anew.
  Initialization
  Armed with this simple state machine, we can initialize our game. As usual, we
  place our initialization code into the
  setupWorld: function.
  We begin by allocating the
  TileWorld. Because
  this level was designed to fit the screen exactly, without the need to
  scroll, we can set the camera to (0, 0) and not have to update it
  again.
  Next, we allocate the main character as usual, placing him in the top control room area:
  - (void) setupWorld { tileWorld = [[TileWorld alloc] initWithFrame:self.frame]; [tileWorld loadLevel:@"lvl3_idx.txt" withTiles:@"lvl3_tiles.png"]; [tileWorld setCamera:CGPointMake(0, 0)]; Animation* tomanim = [[Animation alloc] initWithAnim:@"tom_walk.png"]; m_tom = [[Tom alloc] initWithPos:CGPointMake(5.0f*TILE_SIZE, 12*TILE_SIZE) sprite:[Sprite spriteWithAnimation:tomanim]]; [tileWorld addEntity:m_tom]; [tomanim autorelease];
  The main character is followed by the
  cat,
  mouse, and McGuffin entities:
  Animation* catanim = [[Animation alloc] initWithAnim:@"cat.png"]; cat = [[Tom alloc] initWithPos:CGPointMake(2*TILE_SIZE, 8*TILE_SIZE) sprite:[Sprite spriteWithAnimation:catanim]]; [tileWorld addEntity:cat]; [catanim release]; Animation* mouseanim = [[Animation alloc] initWithAnim:@"mouse.png"]; mouse = [[Tom alloc] initWithPos:CGPointMake(8*TILE_SIZE, 8*TILE_SIZE) sprite:[Sprite spriteWithAnimation:mouseanim]]; [tileWorld addEntity:mouse]; [mouseanim autorelease]; Animation* goalanim = [[Animation alloc] initWithAnim:@"mcguffin.png"]; cheese = [[Entity alloc] initWithPos:CGPointMake(2*TILE_SIZE, 2*TILE_SIZE+0.1f) sprite:[Sprite spriteWithAnimation:goalanim]]; [tileWorld addEntity:cheese]; [goalanim autorelease];
  Now we begin adding the cell doors. As you may notice from the level mockup, the cells are all drawn without doors. This lets us overlay the door sprites onto the scene, allowing them to show the unobstructed passages underneath when they are opened.
  There are nine cells, with a door between every two adjacent cells. That means two rows of three vertically aligned doors, and two columns of three horizontally aligned doors.
  The central square has one door that doesn’t open. We’ve made this compromise in the design so that we need only three buttons; it’s not worth adding a fourth button just so that one cell can have a fourth door:
  Animation* vertdoor = [[Animation alloc] initWithAnim:@"mazedoor.png"]; for(int x=0;x<2;x++){ for(int y=0;y<3;y++) { door[0][x][y] = [[MazeDoor alloc] initWithPos: CGPointMake((3*x+3.5f)*TILE_SIZE, (3*y+1.0f)*TILE_SIZE) sprite:[Sprite spriteWithAnimation:vertdoor]]; [tileWorld addEntity:door[0][x][y]]; } } [vertdoor autorelease]; Animation* horizdoor = [[Animation alloc] initWithAnim: @"mazedoor-horizontal.png"]; for(int x=0;x<3;x++){ for(int y=0;y<2;y++) { door[1][y][x] = [[MazeDoor alloc] initWithPos: CGPointMake((3*x+2.0f)*TILE_SIZE, (3*y+3.5f)*TILE_SIZE) sprite:[Sprite spriteWithAnimation:horizdoor]]; [tileWorld addEntity:door[1][y][x]]; } } [horizdoor autorelease];
  Three buttons are located within the control room area. They need to be spaced out enough so that only one button is ever eligible for a context-sensitive action at any given time:
  Animation* buttonanim = [[Animation alloc] initWithAnim:@"buttons.png"]; for(int i=0;i<buttons_length;i++){ buttons[i] = [[MazeButton alloc] initWithPos: CGPointMake((2.5f+2.5f*i)*TILE_SIZE,12*TILE_SIZE) sprite:[Sprite spriteWithAnimation:buttonanim] color:i]; [tileWorld addEntity:buttons[i]]; } [buttonanim release];
  Finally, because we are starting in the
  WAITING_FOR_PLAYER state, we need to show
  colored lights above the doors leading out of the mouse’s current
  cell. We are going to have to perform this action a lot, so let’s
  create a function named
  decorateDoors to handle this feature:
  state = WAITING_FOR_PLAYER; [self decorateDoors]; }
  Context action
  We already showed the code in
  touchesEnded that detects when players have
  performed a context-sensitive action; now we will examine the game
  logic that gets triggered when they do:
  if(state != WAITING_FOR_PLAYER) return; //figure out if the player has stomped a button. int buttonpressed = −1; for(int i=0;i<buttons_length;i++){ if([buttons[i] under:m_tom]){ [buttons[i] press]; buttonpressed = i; break; } } if(buttonpressed == −1) return; //perform action. NSArray* moves; int angle; moves = [self possibleMoves:mouse]; if(buttonpressed < [moves count]){ angle = [[moves objectAtIndex:buttonpressed] intValue]; //open the chosen door [self doorFrom:mouse.position inDirection:angle].sprite.sequence = @"opening"; //un-decorate the other possible doors. for(NSNumber* n in moves){ int i = [n intValue]; if (i==angle) continue; [self doorFrom:mouse.position inDirection:i].sprite.sequence = @"closed"; } //move the char through the open door. CGPoint mousepos = mouse.position; //int angle = random() % 4; mousepos.y += TILE_SIZE*3*cheapsin[angle]; mousepos.x += TILE_SIZE*3*cheapcos[angle]; [mouse moveToPosition:mousepos]; //wait for the mouse to stop moving. state = MOVING_MOUSE; }
  After determining which button the player pressed, we call the
  press function on that button to
  show graphically that the button has been pressed.
  Remember, each cell has either two or three doors. (One wall in the central cell has a door that doesn’t open, as we explained earlier.) That means corner cells, which have only two adjacent cells, will have only two possible moves, whereas the side and center cells will have three.
  After retrieving the list of possible moves the player could
  have made, we check to see whether the button that was pressed
  corresponds to one of those moves. If the mouse is in a corner cell
  and the third button is pressed, it would fail this test and we would
  not open any doors. If you look ahead, you will see that the state is
  set to
  MOVING_MOUSE only if this test passed.
  That means the player can press the wrong button and see it depressed,
  but no door will open and she will still be able to choose a different
  button.
  Next, we retrieve the direction of the door from the
  moves array and use it to open the
  corresponding door. Retrieving the right door is slightly complicated,
  so we place it inside a subfunction that can return a door based on a
  cell and a direction from that cell:
  - (MazeDoor*) doorFrom:(CGPoint) pos inDirection:(int) angle { int x = gamepos(pos.x); int y = gamepos(pos.y); if(angle == 1 || angle == 3){ //moving left/right will find horizontal column doors only if(angle == 1) x--; return door[0][x][y]; //3 columns of 2 rows } else { //moving up/down will find verticle row doors only if(angle == 2) y--; return door[1][y][x]; //x and y are reversed for horizontal doors, //so that we can squeeze 2x3 of them in. } }
  Once we have a door opening, we should remove the colored lights from the other possible doors by setting their animations to “closed”.
  Finally, we determine the new mouse position and send it to the mouse actor, similar to the way we send the main character to a position when the player taps the screen.
  Update
  In the
  Update: function, we
  use a
  switch statement based on our current
  MazeState to perform the appropriate
  logic:
  //clockwise from straight up, in opengl coords. int cheapsin[] = { 1, 0, −1, 0 }; int cheapcos[] = { 0, −1, 0, 1 }; - (void) Update { float time = 0.033f; //move mouse, wait to stop. //move cat, wait to stop. //decorate doors, wait for button push. switch (state) { case MOVING_MOUSE:
  In the
  MOVING_MOUSE state,
  we will be waiting for the mouse to finish moving (it
  was already told to begin moving at the start of the state when the
  player pressed one of the buttons). Once the mouse has reached its
  destination cell, we will check to see whether it found the cheese and
  move to the
  GOT_CHEESE state if it
  has.
  Otherwise, we need to start moving the cat. We list the possible
  directions in which the cat can move and randomly select one. We then
  open the corresponding door and walk through it, just like we told the
  mouse to do in the
  touchesEnded:
  function:
  if([mouse doneMoving]){ if (distsquared(mouse.position, cheese.position) < 16) { //todo: win. state = GOT_CHEESE; NSLog(@"win condition triggered."); } else { //time to move the cat. NSArray* moves = [self possibleMoves:cat]; int angle = [[moves objectAtIndex:random()%[moves count]] intValue]; MazeDoor* door_tmp = [self doorFrom:cat.position inDirection:angle]; door_tmp.sprite.sequence = @"opening"; //move the char through the open door. CGPoint catpos = cat.position; //int angle = random() % 4; catpos.y += TILE_SIZE*3*cheapsin[angle]; catpos.x += TILE_SIZE*3*cheapcos[angle]; [cat moveToPosition:catpos]; state = MOVING_CAT; } } break; case MOVING_CAT:
  In the
  MOVING_CAT state, we
  are waiting for the cat to finish walking to its new cell. Once it
  has, we should determine whether the cat caught the mouse, in which
  case, we move to the
  MOUSE_KILLED
  state; otherwise, we return to
  WAITING_FOR_PLAYER:
  if([cat doneMoving]){ if(distsquared(mouse.position, cat.position) < 16){ //todo: lose. state = MOUSE_KILLED; NSLog(@"lose condition triggered."); } else { [self decorateDoors]; state = WAITING_FOR_PLAYER; } } break; default:
  The rest of the states do not require special processing in the
  update function.
  WAITING_FOR_PLAYER is just waiting for
  the player to activate a button, and the
  GOT_CHEESE and
  MOUSE_KILLED states are our win and lose
  states:
  //nothing interesting here... break; } [m_tom update:time]; [cat update:time]; [mouse update:time]; for(int i=0;i<2;i++){ for(int x=0;x<2;x++){ for(int y=0;y<3;y++){ [door[i][x][y] update:time]; } } } for(int i=0;i<buttons_length;i++){ [buttons[i] update:time]; } //[tileWorld setCamera:[m_tom position]]; //unnecessary for this level }
  Finally, we make sure to update all of our entities so that they can animate their sprites.
  Sounds
  When the player presses a button, we want to play a click sound effect. We
  will add the code into the
  function of the
  MazeButton
  class:
  static const NSString* down[] = { @"down", @"down-green", @"down-red", }; - (void) press { sprite.sequence = down[color]; [g_ResManager playSound:@"tap.caf"]; }
  Level 4 Implementation
  Level 4 features the timing of jumps across log platforms while avoiding crocodile enemies. Like before, we will be focusing on context-sensitive actions (the jump action will require the same user input as pressing buttons did in the previous level), but this time we use it to cross special water tiles.
  This level will require the following sprites:
  A tile level consisting of a series of solid ground patches separated by water tiles
  The same Tom sprite as before, adding the “jump” animation
  A log sprite with “floating” and “sinking” animations
  A crocodile sprite with “swimming” and “attacking” animations
  A plant sprite to make the level look like an overgrown swamp
  The game logic will include detecting loss and win states. The two ways to lose in this level are to fall into water or to get eaten by a crocodile. The way to win is to reach the end of the level and grab the McGuffin.
  The AI will consist of logs that sink when the character lands on them and crocodiles that jump out of the water and snap at the character if the character jumps over them.
  We will need to update the
  Tom class to support the
  new jump behavior. He is allowed to jump upward (in the positive direction
  on the y-axis) as long as either solid ground or a
  log is within three tiles in that direction.
  User input will be the same as in the preceding level, except that instead of searching for buttons, we will search for the edge of a platform or a log to jump to.
  gsRiverLevel
  As in the previous levels, we will create a new class that inherits from
  GLESGameState:
  @interface RiverLevel : GLESGameState { TileWorld* tileWorld; Tom* m_tom; Rideable* log[log_length]; Croc *croc[croc_length]; Entity* m_goal; int state; }
  This level reuses the
  TileWorld,
  Tom, and
  Entity classes from earlier. We also introduce
  the
  Rideable class for
  floating log platforms and
  Croc to
  represent the crocodiles.
  TileWorld
  The tile world should look like Figure 4-11.
  Figure 4-12 shows the unique tiles.
  And here is the lvl4_idx.txt file:
  12x30 11,11,11,11,11,11,11,11,11,11,11,11 16,17,5,6,6,6,6,6,6,7,11,11 11,11,3,3,3,3,3,3,3,3,11,12 11,11,9,9,9,9,9,9,9,9,16,11 11,11,11,11,11,11,11,11,11,11,11,11 6,6,6,6,6,7,11,11,11,11,11,11 3,3,3,3,3,3,11,5,6,6,6,6 9,9,9,9,9,9,11,3,3,3,3,3 11,11,11,11,11,11,11,9,9,9,9,9 11,12,11,11,11,11,11,11,11,11,11,11 11,13,5,6,6,6,6,6,7,15,11,11 11,2,1,1,1,1,1,1,1,4,11,17 11,8,3,3,3,3,3,3,3,10,11,11 11,11,9,9,9,9,9,9,9,11,16,17 11,11,11,11,11,11,11,11,11,11,17,11 16,11,11,11,11,11,11,11,11,11,11,11 11,11,11,11,11,12,11,11,11,11,11,11 11,11,11,12,11,11,11,11,11,12,11,11 11,11,11,11,11,11,11,11,11,12,11,11 6,6,6,6,6,6,6,6,6,6,6,6 1,1,1,1,1,1,1,1,1,1,1,1 3,3,3,3,3,3,3,3,3,3,3,3 9,9,9,9,9,9,9,9,9,9,9,9 11,11,11,11,11,11,11,11,11,11,11,11 17,11,12,11,11,11,11,11,11,11,16,17 11,16,12,11,11,11,11,11,11,12,11,11 6,6,6,6,6,6,6,6,6,6,6,6 1,1,1,1,1,1,1,1,1,1,1,1 1,1,1,1,1,1,1,1,1,1,1,1 1,1,1,1,1,1,1,1,1,1,1,1 3,3,3,3,3,3,3,3,3,3,3,3 3,3,0,0,0,0,0,0,0,0,3,3 3,3,0,0,0,0,0,0,0,0,3,3 3,3,3,3,3,3,3,3,3,3,3,3 3,3,3,3,3,3,3,3,3,3,3,3 0,0,0,0,0,0,3,3,3,3,3,3 3,3,3,3,3,3,3,0,0,0,0,0 3,3,3,3,3,3,3,3,3,3,3,3 3,3,3,3,3,3,3,3,3,3,3,3 3,3,3,3,3,3,3,3,3,3,3,3 3,3,0,0,0,0,0,0,0,3,3,3 3,0,0,0,0,0,0,0,0,0,3,3 3 level, we use the physics flag
  3 to represent water tiles.
  Crocodiles
  The crocodile includes three frames of swimming, three frames of beginning the attack, and six frames of jumping out of the water to snap at the player’s character. This is unrealistic behavior for a crocodile, but at the risk of misrepresenting nature, it gives the player an exciting explanation for why she just lost.
  The definition of the
  Croc
  class includes an integer that tracks the direction of the crocodile (it
  flips between 1 and –1 as the crocodile turns around at the edge of the
  level) and a
  CGRect representing the
  bounds of the croc that the player must avoid:
  @interface Croc : Entity { int direction; //-1 or 1, for pacing back and forth across the level. CGRect bounds; //used for hit detection on jumping player. } - (bool) under:(CGPoint)point; - (void) attack:(Entity*) other; @end
  The interesting functions in the
  Croc class include
  under:,
  attack:, and
  update:. The
  under: function will return
  true if the given point collides with the
  crocodile’s bounds (offset by its current position):
  - (bool) under:(CGPoint)point { if( point.x > self.position.x + bounds.origin.x && point.x < self.position.x + bounds.origin.x + bounds.size.width && point.y > self.position.y + bounds.origin.y && point.y < self.position.y + bounds.origin.y + bounds.size.height ) return true; return false; }
  The
  attack: function is used
  when the crocodile is found underneath the player’s character. We are
  using the current sprite to represent the AI state of the
  Croc. Only if it is in the “idle” or
  “idle-flip” animation is the crocodile ready to attack. If it is, we
  snap its position to be directly underneath the player’s character and
  then start the attacking animation:
  - (void) attack:(Entity*) other { if([sprite.sequence isEqualToString:@"idle"] || [sprite.sequence isEqualToString:@"idle-flip"]) { worldPos.x = other.position.x; sprite.sequence = direction==1?@"attack-flip":@"attack"; } }
  The
  update: function simply
  moves the crocodile to the left or right, depending on its direction,
  being sure to flip around if it reaches either side of the world:
  - (void) update:(CGFloat) time { [super update:time]; if([sprite.sequence isEqualToString:@"idle"] || [sprite.sequence isEqualToString:@"idle-flip"]) { //pace back and forth. float speed = time*200; //in pixels per second. float nextx = speed*direction + worldPos.x; if(nextx < 0 || nextx > world.world_width*TILE_SIZE){ direction = -direction; //-1 is @"idle", 1 is @"idle-flip" sprite.sequence = direction==1?@"idle-flip":@"idle"; } else { worldPos.x = nextx; } } }
  Logs
  The
  Rideable class is like the
  Croc class, but it is even simpler as it doesn’t move about:
  @interface Rideable : Entity { CGRect bounds; } - (bool) under:(CGPoint) point; - (void) markRidden:(Entity*) rider; @end
  Just like the
  Croc class, the
  Rideable class keeps track of a
  physics boundary. The
  under: function
  here is the same as
  Croc’s.
  The
  markRidden: function will
  cause the log to start sinking if a pointer to an entity is passed or to
  begin rising if the entity is null:
  - (void) markRidden:(Entity*) rider { if(rider){ sprite.sequence = @"sinking"; } else { sprite.sequence = @"rising"; } }
  Tom
  For this level, we will add new functionality to the
  Tom class—two new
  member variables and two functions to the header file:
  //Tom.h @interface Tom : Entity { CGPoint destPos; //tom-specific bool dying; bool inJump; Rideable* riding; } //... code snipped - (void) jump; @property (nonatomic, readonly) bool inJump;
  The
  inJump bool will be
  true when we are currently jumping,
  false otherwise. The game logic can
  use this during the physics simulation to know whether we should check
  for collision with water.
  The new
  Rideable pointer will
  be empty most of the time, but when the player is on a log, it will
  point to that entity. This will allow us to walk to the left or right
  while on a
  Rideable platform.
  First, we modify the
  walkable:
  function to consider whether we are jumping, walking, or riding:
  - (bool) walkable:(CGPoint) point { if(inJump) return true; if(riding == nil) return [world walkable:point]; else return [riding under:point]; }
  We also modify the
  moveToPosition: function to prevent new
  destinations from being input while in the middle of a jump
  action:
  - (void) moveToPosition:(CGPoint) point { if(inJump) return; //freeze input while already jumping. destPos = point; }
  Next, we add a check in our
  update: function just above
  NSString* facing = nil; to handle the
  transition from jumping to landing on a platform:
  if(inJump){ if([self doneMoving]){ [riding markRidden:self]; inJump = false; } }
  And finally, we implement the
  jump function itself:
  - (void) jump { if(inJump) return; //freeze input while already jumping. if(![world walkable:worldPos]){ //search for a walkable tile in the +y direction to jump to. // take the first within 2 tiles. for(int i=1;i<=2;i++){ if([world walkable:CGPointMake(worldPos.x, worldPos.y+i*TILE_SIZE)]) { inJump = true; destPos = CGPointMake(worldPos.x, worldPos.y+i*TILE_SIZE); if(riding){ [riding markRidden:nil]; riding = nil; } return; } } } NSArray* nearby = [world entitiesNear:worldPos withRadius:4*TILE_SIZE]; for(Rideable* e in nearby){ if(e == riding) continue; //ignore the log we are already on. //ignore things we can't ride on. if(![e isKindOfClass:[Rideable class]]) continue; CGPoint pos = CGPointMake(worldPos.x, e.position.y-1); if(![e under:pos]) { //NSLog(@"not lined up to make jump."); continue; } float dy = e.position.y - worldPos.y; if( dy > TILE_SIZE*3 //too far to jump || dy < 0 //ignore logs behind us, only jump forward. ){ //NSLog(@"too far to jump"); continue; } inJump = true; if(riding){ [riding markRidden:nil]; riding = nil; } riding = e; destPos = pos; return; } NSLog(@"jump failed to initiate"); }
  First, we make sure the
  jump
  function reacts only if we are not already jumping. Next, we search for
  a jump position. The current implementation of
  jump allows movement only in the up direction,
  so we only need to search upward (otherwise, we would search based on
  the direction we are currently facing).
  First, we search upward for a passable tile to land on. If we find
  one, we set it as our destination position, turn on our
  inJump flag, and if we are currently attached
  to a
  Rideable platform, we detach it
  and return from our function.
  If we didn’t find solid ground to land on, we begin searching for
  Rideable entities. From within the
  jump function, we don’t have access
  to
  RiverLevel’s
  Log array, but we do have a pointer to the
  TileWorld with which we can call
  entitiesNear: to retrieve a list of
  entities within a given radius of the player.
  To weed out the entities that are not eligible platforms, we use
  the
  isKindOfClass function on each
  entity to determine whether they are instances of
  Rideable.
  If they are
  Rideable, we
  project a vertical line to determine whether jumping straight up would
  land on the log. If not, we continue our search. Otherwise, we tune our
  search further and determine whether the object is within jumping
  distance (three tiles).
  If the object is too far, we continue our search. If it’s within range, we mark our avatar as jumping, set our new destination, detach from the previous platform (if any), and attach to the new platform.
  User Input
  For this level, we want the context-sensitive action to call the new
  jump function on Tom:
  -(void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { UITouch* touch = [touches anyObject]; CGPoint touchpos = [tileWorld worldPosition:[self touchPosition:touch]]; float dist = distsquared(touchpos, m_tom.position); if(dist < 30*30){ [m_tom jump]; } if(![m_tom inJump]) { [m_tom moveToPosition:[tileWorld worldPosition: [self touchPosition:touch]]]; } }
  Notice that rather than simply using an if-else pair here, we
  check to see whether Tom is
  inJump
  after telling him to jump. This is because calling
  jump does not always result in a jump. If Tom
  is not in a place where he can jump, he won’t. In those cases, we still
  want to allow the player to move even small amounts (otherwise, we could
  wind up in a situation where Tom is two pixels away from a place he can
  jump from, but the player can’t get him to move there because the code
  is trying to interpret the move request as a jump action).
  Game Logic
  This level’s game logic will be heavily focused on physics. If Tom finds himself
  standing on a water tile, he will fall and drown; the player loses the
  level. This can occur only when standing on a log for too long because
  our
  jump function doesn’t allow the
  player to jump into the water. Tom faces another hazard while jumping
  across water, however: if he jumps over a croc, he will get chomped and
  lose the level.
  Furthermore, while the character is jumping, we should avoid
  collision tests against the level for the
  water flag. Also, if the character has landed,
  we still need to check collisions for
  Rideable platforms before we decide he has
  collided with water.
  Initialization
  Our initialization continues as usual with the setup of
  TileWorld and our various entities (with
  hardcoded positions) inside the
  setupWorld function:
  - (void) setupWorld { tileWorld = [[TileWorld alloc] initWithFrame:self.frame]; [tileWorld loadLevel:@"lvl4_idx.txt" withTiles:@"lvl4_tiles.png"]; Animation* tomanim = [[Animation alloc] initWithAnim:@"tom_walk.png"]; m_tom = [[Tom alloc] initWithPos:CGPointMake(100, 100) sprite:[Sprite spriteWithAnimation:tomanim]]; [tileWorld addEntity:m_tom]; [tomanim autorelease]; int log_position[log_length*2] = { 4,5, 3,7, 5,13, 2,15, 8,15, 7,16, 4,21, 9,22, 2,23, 7,26, }; Animation* loganim = [[Animation alloc] initWithAnim:@"log.png"]; for(int i=0;i<log_length;i++){ log[i] = [[Rideable alloc] initWithPos: CGPointMake( log_position[i*2+0]*TILE_SIZE, log_position[i*2+1]*TILE_SIZE ) sprite:[Sprite spriteWithAnimation:loganim]]; [tileWorld addEntity:log[i]]; log[i].sprite.sequence = @"idle"; } [loganim autorelease]; int croc_position[croc_length*2] = { 8,5, 5,13, 9,16, 4,21, 10,26, }; Animation* crocanim = [[Animation alloc] initWithAnim:@"croc.png"]; for(int i=0;i<croc_length;i++){ croc[i] = [[Croc alloc] initWithPos: CGPointMake( croc_position[i*2+0]*TILE_SIZE, croc_position[i*2+1]*TILE_SIZE+11) sprite:[Sprite spriteWithAnimation:crocanim]]; [tileWorld addEntity:croc[i]]; } [crocanim autorelease]; int bush_position[] = { 16,16, 16,112, 16,272, 16,304, 16,336, 16,784, 48,592, 304,592, 304,912, 320,752, 368,48, 368,272, 368,304, 368,336, }; int bush_count=14; Animation* bushanim = [[Animation alloc] initWithAnim:@"plant.png"]; for(int i=0;i<bush_count;i++){ Entity* bush = [[Entity alloc] initWithPos: CGPointMake( bush_position[i*2+0], bush_position[i*2+1]) sprite:[Sprite spriteWithAnimation:bushanim]]; [tileWorld addEntity:[bush autorelease]]; } [bushanim release]; Animation* goalanim = [[Animation alloc] initWithAnim:@"mcguffin.png"]; m_goal = [[Entity alloc] initWithPos:CGPointMake(192,896) sprite:[Sprite spriteWithAnimation:goalanim]]; [tileWorld addEntity:m_goal]; [goalanim autorelease]; [tileWorld setCamera:m_tom.position]; }
  If you’ve studied the previous three levels, this code should be familiar.
  Update
  Now we need to update the player and the logs to determine whether the player’s character has fallen in the water. We also need to check the crocodiles to determine whether the character has been attacked and the McGuffin to determine whether the player has completed the level successfully:
  - (void) Update { float time = 0.033f; if(endgame_state != LOSING) [m_tom update:time]; for(int i=0;i<log_length;i++){ [log[i] update:time]; } for(int i=0;i<croc_length;i++){ [croc[i] update:time]; if(m_tom.inJump && [croc[i] under:m_tom.position]){ [croc[i] attack:m_tom]; //NSLog(@"lose, crikey!"); [m_tom dieWithAnimation:@"dying"]; [super onFail]; } } if(m_tom.riding && [m_tom.riding.sprite.sequence isEqualToString:@"sunk"]) { [m_tom dieWithAnimation:@"dying"]; [super onFail]; //NSLog(@"lose"); } if(m_goal && distsquared(m_tom.position, m_goal.position) < 32*32){ [super onWin]; //NSLog(@"win"); } [m_goal update:time]; [tileWorld setCamera:[m_tom position]]; }
  This code ends by updating the camera.
  Sounds
  When the player jumps, we want to play a “boing” sound effect. We add the code into
  the
  jump function of the
  Tom
  class:
  - (void) jump { if(inJump) return; //freeze input while already jumping. if(![world walkable:worldPos]){ for(int i=1;i<=2;i++){ if([world walkable:CGPointMake(worldPos.x, worldPos.y+i*TILE_SIZE)]) { inJump = true; [g_ResManager playSound:@"tap.caf"]; destPos = CGPointMake(worldPos.x, worldPos.y+i*TILE_SIZE); //worldPos = desPos; if(riding){ [riding markRidden:nil]; riding = nil; } return; } } } //... code snipped }
  Game State Serialization
  Now that we have implemented our levels, we need a way to show the progression between them and a way to jump between levels and the menu. This presents us with three tasks:
  We need to store an integer value that represents how many levels have been unlocked.
  When the player completes a level for the first time, we need to increment that value.
  From our Main menu, we list all of the playable levels; we want to gray out the links to levels that are currently locked.
  Initialize Storage
  We begin by adding the integer to our persistent storage in the
  Game2DAppDelegate class. Therefore, we will
  add the following to our
  applicationDidFinishLaunching:
  function:
  //Game2DAppDelegate.m //setup default save data. if([g_ResManager getUserData:ProgressionSavefile] == nil){ //progression will store the unlocked level count, from 0..3. [g_ResManager storeUserData:[NSNumber numberWithInt:0] toFile:ProgressionSaveFile]; }
  where
  ProgressionSaveFile is
  defined as:
  #define ProgressionSaveFile @"progression"
  End Game
  Within our
  GLESGameStates, we
  have already written code to determine win and loss states. Now we will write some
  functions that all of our levels can use to handle user input and
  rendering for the end states (win or lose) and increment the progression
  integer.
  We modify the
  GLESGameState
  class:
  @interface GLESGameState : GameState { int endgame_state; @private float endgame_complete_time; } //... code snipped #define ENDGAME_GAME_IN_PROGRESS 0 #define ENDGAME_WINNING 1 #define ENDGAME_LOSING 2 #define ENDGAME_HOLD_TIME 2.0f - (void) onWin:(int)level; - (void) onFail; - (void) renderEndgame; - (void) updateEndgame:(float)time; - (void) touchEndgame; @end
  We want to keep track of whether the
  GameState is still in progress, or has
  completed with a win or loss condition. We will also keep a time counter
  so that we can automatically return to the Main menu after displaying
  the win or loss result for a number of seconds as defined by
  ENDGAME_HOLD_TIME.
  First, we must initialize our new variables:
  -(id) initWithFrame:(CGRect)frame andManager:(GameStateManager*)pManager; { if (self = [super initWithFrame:frame andManager:pManager]) { // Initialization code [self bindLayer]; endgame_state = ENDGAME_GAME_IN_PROGRESS; endgame_complete_time = 0; } return self; }
  Next, we implement the
  onWin
  function, where we actually try to increment the progression variable
  (making sure to do so only if the level has not yet been
  completed):
  - (void) onWin:(int)level{ if(endgame_state == ENDGAME_GAME_IN_PROGRESS){ int unlocked = [[g_ResManager getUserData:ProgressionSavefile] intValue]; if(level >= unlocked){ //when we beat a level, unlock the next level. unlocked = level+1; //note: we could do something special on unlocked==4 [g_ResManager storeUserData:[NSNumber numberWithInt:unlocked] toFile:ProgressionSavefile]; } endgame_state = ENDGAME_WINNING; [g_ResManager stopMusic]; [g_ResManager playMusic:@"march.mp3"]; } }
  We grab the value from our
  ResourceManager with
  getUserData: and convert it to an
  integer. Then we determine whether the current level has not yet been
  passed; if so, we increment the value and save it back into persistent
  storage. Regardless, we set our
  endgame_state to
  ENDGAME_WINNING and play a triumphant winning
  march.
  If the level completes with a fail, we simply set the
  endgame_state to
  ENDGAME_LOSING:
  - (void) onFail{ if(endgame_state == ENDGAME_GAME_IN_PROGRESS){ endgame_state = ENDGAME_LOSING; } }
  In our
  render function, we
  perform a spiffy zoom and rotation effect on either the “level complete”
  or “level failed” image. Once a time period has passed, we also display
  a “tap to continue” message to cue the player that tapping the screen
  will now return her to the Main menu. We expect the various
  GameStates to call
  this function on their own, at the end of their own render functions,
  for this to be overlaid in front of their contents:
  - (void) renderEndgame{ if(endgame_state == WINNING) { [[g_ResManager getTexture:@"levelcomplete.png"] drawAtPoint: CGPointMake(self.frame.size.width/2, self.frame.size.height/2 - 64) withRotation:min(endgame_complete_time, 0.25f)*4*2*180 withScale:min(endgame_complete_time, 0.25f)*4]; } else if (endgame_state == LOSING) { [[g_ResManager getTexture:@"levelfailed.png"] drawAtPoint: CGPointMake(self.frame.size.width/2, self.frame.size.height/2 - 64) withRotation:min(endgame_complete_time, 0.25f)*4*2*180 withScale:min(endgame_complete_time, 0.25f)*4]; } if(endgame_complete_time > 2.0f){ [[g_ResManager getTexture:@"taptocontinue.png"] drawAtPoint: CGPointMake(self.frame.size.width/2, self.frame.size.height/2 - 128) ]; } }
  The update and touch functions will count the amount of time that has passed since we entered an end condition and allow the player to change state only if that time has passed:
  - (void) updateEndgame:(float) time{ if(endgame_state != ENDGAME_GAME_IN_PROGRESS){ endgame_complete_time += time; } } - (void) touchEndgame{ if(endgame_complete_time > ENDGAME_HOLD_TIME){ [m_pManager doStateChange:[gsMainMenu class]]; } }
  This assumes that all
  GameState
  levels want to return to
  gsMainMenu when they
  are done.
  Now we modify our levels to use these new functions. It will be the same process for each level, so we will review only one level.
  In LionLevel.m, we change the
  Update: function to call
  onFail if we lost or
  onWin: if we have won the level:
  - (void) Update { //... code snipped for(int i=0;i<lions_length;i++){ if([m_lions[i] wakeAgainst:m_tom]){ [self onFail]; //NEW PROGRESSION CODE } [m_lions[i] update:time]; } //... code snipped if(m_goal == nil && m_tom.position.y < 8*TILE_SIZE){ //clear to the door, so win. [self onWin:1]; //NEW PROGRESSION CODE } //... code snipped [super updateEndgame:time]; //NEW PROGRESSION CODE }
  In the
  Render function, we
  simply add a call to
  renderEndgame
  just above
  swapBuffers:
  - (void) Render { //clear anything left over from the last frame, and set background color. glClearColor(0xff/256.0f, 0x66/256.0f, 0x00/256.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); [tileWorld draw]; [super renderEndgame]; //NEW PROGRESSION CODE //you get a nice boring white screen if you forget to swap buffers. self swapBuffers]; }
  And finally, in
  touchesEnded:,
  we call
  touchEndgame at the bottom of
  the function:
  -(void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { UITouch* touch = [touches anyObject]; [m_tom moveToPosition: [tileWorld worldPosition:[self touchPosition:touch]]]; [super touchEndgame]; }
  Modify UI
  Now that we have a variable to represent progression, we need to enforce it. We
  already have an Interface Builder-based
  GameState with buttons that link to each
  level. All we need to do is check which ones should be locked and
  disable them in the initialization of that state.
  Inside
  gsMainMenu.m
  initWithFrame:, we add the following:
  -(gsMainMenu*) initWithFrame:(CGRect)frame andManager:(GameStateManager*)pManager { if (self = [super initWithFrame:frame andManager:pManager]) { [[NSBundle mainBundle] loadNibNamed:@"MainMenu" owner:self options:nil]; [self addSubview:subview]; } //NEW PROGRESSION CODE UIButton* lvls[] = { lvl1, lvl2, lvl3, lvl4 }; int unlocked = [[g_ResManager getUserData:ProgressionSavefile] intValue]; for(int i=0;i<4;i++){ lvls[i].enabled = i <= unlocked; } return self; }
  Conclusion
  Congratulations, you have created your first iPhone game! You could make many improvements to this game, including adding new levels and modifying the game play of the current levels. But don’t get too distracted because next up is a 3D game!
  Get iPhone Game Development now with the O’Reilly learning platform.
  O’Reilly members experience live online training, plus books, videos, and digital content from nearly 200 publishers.