This is a post by iOS Tutorial Team Member Allen Tan, an iOS developer and co-founder at White Widget. You can also find him on Google+ andTwitter.
Welcome back to the second (and final) part of our Beat Em Up game tutorial series!
If you followed the first part, then you’ve already created the hero and have the D-pad controller present in the game.
You will pick up where you left off, and by the end will have completed your very own Beat Em Up game.
This part is exciting, in that you will see the results of much of what you do on the screen. Just to mention a few items: you will add movement, scrolling, collision, enemies, AI, and some polish with music and sound effects!
Before you start, make sure that you have a copy of the project from Part 1, either by going through the first tutorial, or by downloading the finished project.
Don’t forget to grab a copy of the resource kit if you haven’t already, as it contains some stuff that you haven’t used yet.
Let’s get back to doing what we do best – beating up on androids! :]
In the last section of Part 1, you created a D-pad controller and displayed it onscreen. But at the moment, pressing the D-pad crashes the game instead of moving the player. Let’s remedy this quickly!
The first step is to create a movement state for the hero.
Go to Hero.m and add the following:
|
This should be familiar to you by now. You add new frames for the walking animation, and create the walk action in a similar way to how you created the idle action.
Switch to ActionSprite.m and add the following method:
|
This checks to see if the previous action was idle, then it changes the action to walk, and runs the walk animation, but if the previous action was already a walk action, then it simply changes the velocity of the sprite based on the walkSpeed value.
The method also checks the left/right direction of the sprite, and flips the sprite accordingly by switching the value of scaleX between -1 and 1.
To connect the hero’s walk action to the D-pad, you must turn to the delegate of the D-pad: GameLayer.
Switch to GameLayer.m and implement the methods enforced by the SimpleDPadDelegate protocol:
|
You trigger the hero’s move method every time the SimpleDPad sends a direction, and trigger the hero’s idle method every time the touch on SimpleDPad stops.
Build and run, and try moving the hero using the D-pad.
All right, he’s walking! Wait a minute… he’s not actually moving… what gives?
Take a look walkWithDirection: again, and you’ll notice that it doesn’t do anything except change the velocity of the hero. Where is the code for changing the hero’s position?
Changing the hero’s position is the responsibility of both ActionSprite and GameLayer. An ActionSprite never really knows where it is located on the map. Hence, it doesn’t know when it has reached the map’s edges. It only knows where it wants to go – the desired position. It is GameLayer’s responsibility to translate that desired position into an actual position.
You already declared a CGPoint named desiredPosition for ActionSprite. This is the only position value that ActionSprite should be working with.
Go to ActionSprite.m and add the following method:
|
This method is called every time the game updates the scene, and it updates the desired position of the sprite only when it is in the walking state. It adds the value of velocity to the current position of the sprite, but before that, velocity is multiplied by delta time so that the time interval is factored into the equation.
Multiplying by delta time makes the hero move at the same rate, no matter the current frame rate. Position + Velocity * Delta Time really just means move x and y (velocity) points each second (1 dt).
Note: This way of integrating position is called Euler’s integration. It’s known for being an approach that’s easy to understand and implement, but not one that is extremely accurate. But since this isn’t a physics simulation, Euler’s integration is close enough for your purposes.
Switch to GameLayer.m and do the following:
|
You schedule GameLayer’s update method, which acts as the main run loop for the game. Here you will see how GameLayer and ActionSprite cooperate in setting ActionSprite’s position.
At every loop, GameLayer asks the hero to update its desired position, and then it takes that desired position and checks if it is within the bounds of the Tiled Map’s floors by using these values:
GameLayer also makes a lot of references to ActionSprite’s two measurement values, centerToSides, andcenterToBottom, because if ActionSprite wants to stay within the scene, its position shouldn’t go past the actual sprite bounds. (Remember that the canvas for the sprites you’re using is much bigger than the actual sprite area, in at least some cases.)
If the position of ActionSprite is within the boundaries that have been set, GameLayer gives the hero its desired position. If not, GameLayer asks the hero to stay in its current position.
Note: The MIN function compares two values, and returns the lower value, while the MAX function returns the higher of two values. Using these two in conjunction clamps a value to a minimum and maximum number. Cocos2D also comes with a convenience function for CGPoints that is similar to what you just did: ccpClamp.
Build and run, and you should now be able to move your hero across the map.
You’ll soon find, though, that there’s one more issue that needs attention. The hero can walk past the right edge of the map, such that he vanishes from the screen.
You can set the tiled map to scroll based on the hero’s position by plugging in the method found in thetile-based game tutorial.
Still in GameLayer.m, do the following:
|
This code centers the screen on the hero’s position, except in cases where the hero is at the edge of the map.
For a complete explanation of how this works, please refer to the previously mentioned tile-based game tutorial.
Build and run. The hero should now be visible at all times.
While being able to walk around is fun, walking around a huge empty corridor can become pretty boring, pretty fast for your hero. It’s up to you to give him some company. :]
You already have a base model for the sprite: ActionSprite. You can reuse that to make computer-controlled characters for the game.
This part of the tutorial will move rather quickly, since it’s very similar to how the hero was created.
Hit Command-N and create a new file with the iOS\Cocos2D v2.x\CCNode Class template. Make it a subclass of ActionSprite and name it Robot.
Go to Robot.h and add this line at the top:
|
Switch to Robot.m and add this method:
|
As with the hero, the above code creates a robot with three actions: idle, attack and walk. It also fills in the two measurement values: centerToBottom and centerToSides.
Notice that the robot’s attributes are weaker compared to the hero. This is logical, since otherwise your hero will never be able to defeat the robots. :]
Let’s jump straight to filling the game with a lot of these robots. You want a brawl, don’t you?
Switch to GameLayer.h and add the following property:
|
Now switch to GameLayer.m and do the following:
|
You just did the following:
Build and run, and walk the hero around until you see robots on the map.
Try walking around a bit in an area with robots, and you’ll notice that there’s something really wrong with how the robots are drawn. According to the current perspective, if the hero is below a robot, then he should be drawn in front of the robot, not the other way around.
For things to be drawn in the right sequence, you need to explicitly tell the game which objects to draw first. You should already know how to do this – think back to how you made the tile map appear behind everything else.
Figured it out? If your answer was z-order/z-value, then you’re 100% correct!
To refresh your memory, take a look at how the sprite batch node and tile map were added to the scene in GameLayer.m:
|
Now take a look at how the hero and the robots were added:
|
There are two differences:
To fix the broken drawing sequence, you need to handle the z-order dynamically. Every time a sprite moves across the screen vertically, its z-order should be changed. The higher a sprite is on the screen, the lower its z-value should be.
Still in GameLayer.m, do the following:
|
Every time the sprite positions are updated, this method makes the CCSpriteBatchNode reorder the z-value of each of its children, based on how far the child is from the bottom of the map. As the child goes higher, the resulting z-value goes down.
Note: Each CCNode has its own property named zOrder, but changing this won’t give you the same effect as calling reorderChild from the parent. It’s the parent’s responsibility to draw its children in order, so it should also be the parent’s responsibility to set the order of its children.
Build and run, and the drawing sequence should now be correct.
Well, your hero’s now got company. But there’s still nothing much for him to do. And if this is a Beat Em Up game, then he should be able to beat somebody up, right? Time to lay down the hurt on these robots!
For the hero to be able to punch and actually hit the robots, you need to implement a way of detecting collisions. And for this particular tutorial, you will create a very simple collision detection system using rectangles. In this system, you will define two rectangles/boxes for each character:
If the attack box of one ActionSprite collides with the hit box of another, then a collision occurs. This distinction between the two rectangles will help you decide who hit whom.
You already defined a structure for your collision box in Defines.h. For reference, here it is again:
|
Each bounding box has two rectangles: the actual, and the original.
Go to ActionSprite.h and add the following:
|
The above creates two bounding boxes for ActionSprite: the hit box, and the attack box, as discussed above. It also sets up a factory method for a bounding box, which simply creates a BoundingBox structure given an origin and size.
Switch to ActionSprite.m and add the following methods:
|
The first method creates a new BoundingBox and is there to assist subclasses of ActionSprite in creating their own bounding boxes.
The second method, transformBoxes, updates the origin and size of the actual measurements of each bounding box, based on the sprite’s position and scale, and the local origin and size of the bounding box. You take the scale into consideration because it determines the direction the sprite is facing. A box located on the right side of the sprite will flip to the left side when the scale is set to -1.
Switch to Hero.m and create the new bounding boxes:
|
Likewise, switch to Robot.m and add the following:
|
You now have the hit and attack boxes for the hero and the robots. If you were to visualize the boxes, they would look something like this:
Whenever an attack box (red) intersects with a hit box (blue), a collision occurs.
Before writing the code that checks for this intersection of bounding boxes, you must first make sure that ActionSprite can react properly to being hit. You’ve already coded the idle, attack, and walk actions, but still haven’t created the hurt and death actions.
Time to bring on the pain! Go to ActionSprite.m and add the following methods:
|
As long as the sprite is not dead, getting hit will switch its state to hurt, execute the hurt animation, and subtract the right amount of damage from the sprite’s hit points. If the hit points fall below 0, then the knocked out (death) action occurs.
To complete these two actions, you still have to retrofit both the Hero and Robot classes with their respective hurt and death actions.
Go to Hero.m first and add the following inside init (below the existing animation blocks):
|
Switch to Robot.m and add the following inside init (again, below the existing animation blocks):
|
It’s the same old stuff. You create the hurt and death actions in the same way as you created the other actions. The hurt action transitions to the idle action when it finishes, and the death action makes the sprite blink after it animates. (In classic Beat Em’ Up style.)
Switch over to GameLayer.m to officially add collision handling:
|
The above code detects collisions in three easy steps:
If all of these conditions are passed, then a collision occurs, and the robot’s hurt action is executed. The hero’s damage value is passed in as a parameter, so that the method knows just how many hit points it has to subtract.
Build and run, and get punching!
You’re punching, punching and they all fall down! But they never attack back? What fun is that?
Completing the game should require both a winning and losing condition. Currently, you can kill all the robots on the map, and nothing will happen. You want the game to end when either all the robots are wiped out, or the hero dies.
You certainly won’t have the hero dying if the robots just stand around acting like punching bags. :] To make them move and use the actions that you created for them, you need to develop a simple AI (Artificial Intelligence) system.
The AI that you will create is based on decisions. You will give each robot a chance to decide on a course of action at specific time intervals. The first thing that they need to know is when they get make this choice.
Go to Robot.h and add this property:
|
Switch to Robot.m and initialize this property:
|
This property is named to indicate its purpose – it holds the next time at which the robot can make a decision.
Switch to GameLayer.m and add the following:
|
Now that is one long snippet of code! Don’t worry, soon it will all be clear.
Let’s take the above code section-by-section. For each robot in the game:
Every time a robot makes a decision, its next decision time is set to a random time in the future. In the meantime, he continues executing whatever actions he started running in the last decision time.
Still in GameLayer.m, do the following:
|
Here, you make sure that the Robot AI method you created earlier is called every game loop. It also loops through each robot and moves them based on their desired position.
Build and run, and face the robotic menace from down the corridor!
Play the game until you beat all the robots, or until the hero dies, and you’ll see that the game gets stuck. If you followed my previous tutorial on making a game like Fruit Ninja, you probably know that I like to end tutorial games by simply showing a button that allows you to restart everything. So let’s do that here as well!
Still in GameLayer.m, do the following:
|
The first method creates and shows a Restart button that, when pressed, triggers the second method. The latter just commands the director to replace the current scene with a new instance of GameScene.
Look back at updateRobots: above, and you will see that I left two placeholder comments in there, like this:
|
Do the following in updateRobots::
|
Both of these if statements check for game-ending conditions. The first one checks if the hero is still alive right after having been hit by a robot. If he’s dead, then the game ends.
The second one checks if all the robots are dead. If they are, then the game also ends.
There’s one funky check happening here, where the HudLayer looks to see if it has a child with a tag value of 5. And you might be wondering – what is that all about?
Look back at endGame above, and you will see that the End Game menu has a tag value of 5. Since this checker runs in a loop, it needs to make sure that the End Game menu has not previously been created. Otherwise, it will keep on creating new End Game menu items every chance it gets.
Build and run. Have fun beating up those pesky robots!
I was down for the count before I could take a screen shot! X_X
The game plays pretty nicely, but it isn’t very satisfying when you sock it to a robot without audio feedback. And some background music might not hurt either. I can assure you, I’m not going to leave you hanging without some fun music and sound effects!
For this game, you’ll be using background music made by Kevin MacLeod of Incompetech, and also some 8-bit sound effects that I created using the neat bfxr utility.
You can add the music and sound effects into the game in just a few simple steps. :]
Drag the Sounds folder from the resource kit (the one you downloaded and extracted in Part 1 of this tutorial) into the Resources group of your project. Make sure that Copy items into destination group’s folder is checked, and that Create groups for any added folders is selected.
Switch to GameLayer.m and do the following:
|
Switch over to ActionSprite.m and do the following:
|
Switch to Hero.m, and do the following:
|
Finally, switch to Robot.m and do the following:
|
The above code preloads the music and sound effects in GameLayer, and plugs in the appropriate sound effect for each action event.
You’re done! Build, run, and play until you drop!
Here is the final project with all the code from the complete tutorial.
You may have been through both parts of this tutorial and have the bodies hitting the floor, but you’ve only seen the tip of the iceberg! There is still a lot more ground to be covered when it comes to making a full-fledged Beat Em Up Game.
If you’re craving more Beat Em Up action, I have good news for you – I will soon be releasing a Beat Em Up Starter Kit here at raywenderlich.com!
You’re probably wondering just how awesome it will be. As a teaser, here’s a list of what you can expect to find and learn in the kit:
Whoa, that’s a lot of stuff! If you’re interested in the Beat Em Up Game Starter Kit, make sure you’re signed up for our monthly newsletter – we’ll announce it there when it comes out! :]
Update 4/8/13: Good news – the Beat Em Up Game Starter Kit is now available! Check it out on theraywenderlich.com store.
It’s gonna be epic – but in the meantime, if you have any questions, comments, or suggestions about this tutorial, please join the forum discussion below :]