This tutorial is based on the Child Dragon Mountain Man translation cocos2d iPhone tutorial, rewrite with cocos2d-x for xNa engine, plus some of my processing and production. In the tutorial, most of the text images are from the original author and the author of the translation, Long Shan, and many others are my own understandings and processing. I would like to thank the original author for his tutorial and the translation of Zilong shangren. This tutorial is only for learning and communication purposes. Do not conduct commercial communications.
IPhone tutorial address: http://www.cnblogs.com/andyque/articles/1997966.html
IPhone tutorial original address: http://www.raywenderlich.com/782/harder-monsters-and-more-levels
In the previous tutorial, we had a turret that could be rotated, a monster that could be shot, and great sound effects.
However, our turret thinks this is too simple. These monsters only need to take a shot, and now there is only one level! It hasn't warmed up yet!
In this tutorial, I will expand our projects, add monsters of different types and difficulties, and then implement multiple levels.
For fun, let's create two different types of monsters: one that is not very smooth, but fast to move, and one that is very resistant (tank level), but very slow to move! To enable gamers to differentiate these two types of monsters, download modified monster images and add them to the project. At the same time, download the explosion sound effects I made and add them to the content project. Add images to the images folder, and add sound effects to the resource folder.
Let's create a monster class. There are many ways to model the monster class, But we chose the simplest method, that is, the monster class as a subclass of ccsprite. At the same time, we will create two sub-classes of the monster class: one for our weak and fast monsters, and the other for our strong and slow monsters.
Add a class to the classes folder. Name it monster. CS. And inherit from ccsprite
Next, replace the code in monster. CS with the following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using cocos2d;
namespace cocos2dSimpleGame.Classes
{
class Monster: CCSprite
{
private int _curHp;
private int _minMoveDuration;
private int _maxMoveDuration;
public int hp {get {
return _curHp;
}
set {
_curHp = value;
}
}
public int minMoveDuration {
get {
return _minMoveDuration;
}
set {
_minMoveDuration = value;
}
}
public int maxMoveDuration {
get {
return _maxMoveDuration;
}
set {
_maxMoveDuration = value;
}
}
}
class WeakAndFastMonster: Monster
{
public static WeakAndFastMonster monster ()
{
WeakAndFastMonster monster = new WeakAndFastMonster ();
if (monster.initWithFile (@ "images / Target"))
{
monster.hp = 1;
monster.minMoveDuration = 3;
monster.maxMoveDuration = 5;
}
return monster;
}
}
class StrongAndSlowMonster: Monster
{
public static StrongAndSlowMonster monster ()
{
StrongAndSlowMonster monster = new StrongAndSlowMonster ();
if (monster.initWithFile (@ "images / Target2"))
{
monster.hp = 3;
monster.minMoveDuration = 6;
monster.maxMoveDuration = 12;
}
return monster;
}
}
}
Here is very straightforward: we derive a Monster class from CCSprite, and then add some member variables to record the state of the monster. Then, we derive two different monster subclasses from the Monster class. The code here is very simple, only we add a static method for each class, used to return an instance of this class. Then the initial HP and the time required to move were initially enabled.
Then, return to GamePlayLayer and modify the addTarget method to construct an instance of our newly created class instead of directly creating a sprite. Replace the line of spriteWithFile as follows:
Monster target = null;
if (random.Next ()% 2 == 0)
target = WeakAndFastMonster.monster ();
else
target = StrongAndSlowMonster.monster ();
There will be a 50% chance of different types of monsters appearing. Of course, we moved the speed definition of the monster into the class, so we need to modify the min / max movement interval and change it to the following:
float minDuration = target.minMoveDuration; // 2.0f;
float maxDuration = target.maxMoveDuration; // 4.0f;
Finally, make some changes in the updates method. First, add a boolean value before iterating over all _targets, that is, before foreach (CCSprite target in _targets)
bool monsterHit = false;
Then, in CCRectIntersetsRect, the object is not immediately added to targetsToDelete, but changed to the following:
//targetToDelete.Add(target);
monsterHit = true;
Monster monster = (Monster) target;
monster.hp--;
if (monster.hp <= 0)
{
targetToDelete.Add (target);
}
break;
Here, instead of killing the monster immediately, we reduce its HP, and only kill it when its health is less than zero. Note that if projectile hits a monster, we jump out of the loop, which means that a frisbee can only hit one monster at a time.
Finally, we put this code for projectilesToDelete test:
if (targetToDelete.Count> 0)
{
projectilesToDelete.Add (projectile);
}
Change to the following:
if (monsterHit)
{
projectilesToDelete.Add (projectile);
SimpleAudioEngine.sharedEngine (). PlayEffect ("resource / explosion");
}
Compile and run the code. If all goes well, then you will see two different types of monsters flying on the screen-this makes our turret life more challenging!
Multiple levels
In order for the game to support multiple levels, first we need to refactor. This refactoring work is very simple, but there is a lot of work to be done in this project. If you put all the content on this post, it will be a long and tedious post.
On the contrary, I will talk about what I have done from a higher perspective and provide a fully functional sample project.
Abstract a Level class. At present, the concept of "level" is hard-coded in the HelloWorldScene class, such as which type of monster to transmit and how often to transmit. Therefore, our first step is to extract this information and put it in a Level class. In this way, in HelloWorldScene we can reuse the same logic for different levels.
Reuse the scene. At present, every time we convert a scene (scene), we re-create a new scene class. One disadvantage here is the efficiency issue. Every time you load resources in the init method of the scene object, this will affect the game frame.
Because we are a simple game, all we need to do is create an instance of each scene and provide a reset method to clear any old state (such as the frisbee or monster in the previous level).
Use application delegation as a springboard. At present, we do not have any global status, such as: which level we are in or what the current level settings are. Each scene is just hard-coded who is the next scene it needs to jump to.
We will modify these contents and use App Delegate to store pointers to some global state (such as level information). Because, all scenes (scene) can easily get the delegate object. We will also put some methods in the App Delegate class to achieve centralized control of switching between different scenes. And reduce the interdependence between scenes.
Okay, the above is the main refactoring content I made. Remember, this is just one of the ways to realize the function. If you have other better ways to organize scenes and game objects, please share it here!
The design of multiple levels above is the original author's words. But for beginners, it is still not possible to talk about so many theories. . .
Below I am not afraid that the post is long and lacking, let's implement multiple levels thoroughly. Although the design may not be very good, it still works. . .
Now let's take a look at our game logic implementation. If we want to reconstruct Level. So what elements does the level class contain, Monster's hp, speed. And the number of hits to be completed for each level. We decided to use the Level class to complete the Monster acquisition of the current level.
Then modify the code in Monster.cs as follows:
class WeakAndFastMonster: Monster
{
public static WeakAndFastMonster monster (int _hp, int _minMoveDuration, int _maxMoveDuration)
{
WeakAndFastMonster monster = new WeakAndFastMonster ();
if (monster.initWithFile (@ "images / Target"))
{
monster.hp = _hp;
monster.minMoveDuration = _minMoveDuration; // 3;
monster.maxMoveDuration = _maxMoveDuration; // 5;
}
return monster;
}
}
class StrongAndSlowMonster: Monster
{
public static StrongAndSlowMonster monster (int _hp, int _minMoveDuration, int _maxMoveDuration)
{
StrongAndSlowMonster monster = new StrongAndSlowMonster ();
if (monster.initWithFile (@ "images / Target2"))
{
monster.hp = _hp; // 3;
monster.minMoveDuration = _minMoveDuration; // 6;
monster.maxMoveDuration = _maxMoveDuration; // 12;
}
return monster;
}
}
We took speed and hp as parameters.
Then we add a new class to Classes. Name it Level.cs. The code of Level class is as follows:
class Level
{
int _level;
int _levelCount;
public int levelCount {get {return _levelCount;}}
public int level {get {return _level;}}
public Level ()
{}
/// <summary>
/// There are 7 levels by default
/// </ summary>
/// <param name = "l"> </ param>
public Level (int l)
{
if (l <= 0 || l> 7)
_level = 1;
else
_level = l;
_levelCount = GetLevelCount (_level);
}
/// <summary>
/// Get the number of hits to complete for each level
/// </ summary>
/// <param name = "level"> </ param>
/// <returns> </ returns>
private int GetLevelCount (int level)
{
switch (level)
{
case 1:
return 10;
case 2:
return 10;
case 3:
return 35;
case 4: return 50;
case 5: return 55;
case 6: return 60;
case 7: return 65;
default:
return 30;
}
}
/// <summary>
/// Jump to the next level
/// </ summary>
public void NextLevel ()
{
_level ++;
if (_level> 7)
{
_level = 1;
}
_levelCount = GetLevelCount (_level);
}
/// <summary>
/// There is Level to generate monsters. The monsters in each level are different.
/// </ summary>
/// <returns> </ returns>
public Monster GetMonster ()
{
Monster monster;
Random random = new Random ();
switch (level)
{
case 1: monster = WeakAndFastMonster.monster (1, 5, 8); break;
case 2: monster = WeakAndFastMonster.monster (1, 4, 7); break;
case 3: monster = WeakAndFastMonster.monster (1, 3, 5); break;
case 4:
{
if (random.Next ()% 7 == 0)
monster = StrongAndSlowMonster.monster (3, 6, 12);
else
monster = WeakAndFastMonster.monster (1, 3, 6);
break;
}
case 5:
{
if (random.Next ()% 5 == 0)
monster = StrongAndSlowMonster.monster (3, 6, 12);
else
monster = WeakAndFastMonster.monster (1, 3, 6);
break;
}
case 6:
{
if (random.Next ()% 4 == 0)
monster = StrongAndSlowMonster.monster (3, 6, 12);
else
monster = WeakAndFastMonster.monster (1, 2, 6);
break;
}
case 7:
{
if (random.Next ()% 3 == 0)
monster = StrongAndSlowMonster.monster (3, 6, 12);
else
monster = WeakAndFastMonster.monster (1, 3, 6);
break;
}
default:
monster = WeakAndFastMonster.monster (1, 3, 7); break;
}
return monster;
}
}
Next, modify the GamePlayLayer class. Add two declarations in the class:
Level level = new Level (1);
int life = 40;
If I downloaded the project code of my first two tutorials, I found that I added a Label to the GamePlayLayer as an information display, if your current project is not added. Then add a declaration to the class:
CCLabelTTF label;
Add the initialization of label in init below:
string msg = String.Format ("Count: {0}, life: {1}, Level: {2}", projectilesDestroyed, life, level.level);
label = CCLabelTTF.labelWithString (msg, "Arial", 24);
label.position = new CCPoint (label.contentSize.width / 2, screenHeight-label.contentSize.height / 2);
addChild (label);
This label is used here to display the number of enemy kills, the remaining health of the cannon, and the current level.
Then, modify the addTarget method to construct an instance of our newly created class, and use Level to create the Monster.
// CCSprite target = CCSprite.spriteWithFile (@ "images / Target");
Monster target = null;
// if (random.Next ()% 2 == 0)
// target = WeakAndFastMonster.monster ();
// else
// target = StrongAndSlowMonster.monster ();
target = level.GetMonster ();
Then modify the spriteMoveFinished method.
if (sprite.tag == 1) // target
{
_targets.Remove (sprite);
life--;
string msg = String.Format ("Count: {0}, life: {1}, Level: {2}", projectilesDestroyed, life, level.level);
label.setString (msg);
if (life <= 0)
{
GameOverScene pScene = new GameOverScene (false);
CCDirector.sharedDirector (). ReplaceScene (pScene);
}
}
The judgment above was modified. When the life value is 0, jump to GamOverScene.
The victory judgment is modified below. Find the foreach (CCSprite target in targetToDelete) in the updates method here. Modify as follows:
foreach (CCSprite target in targetToDelete)
{
_targets.Remove (target);
projectilesDestroyed ++;
string msg = String.Format ("Count: {0}, life: {1}, Level: {2}", projectilesDestroyed, life, level.level);
label.setString (msg);
if (projectilesDestroyed> = level.levelCount)
{
GameOverScene pScene = new GameOverScene (true);
CCDirector.sharedDirector (). ReplaceScene (pScene);
}
this.removeChild (target, true);
}
The victory judgment above is obvious. Not much to say.
At this point, the logic has been modified, and Level's reconstruction is complete.
You should notice that the call to GameOverScene above has been changed. I will talk about how to modify it later.
The next scene to reuse, scene reuse, we must retain the original scene, but in the WP7 program, how to save global variables, we use PhoneApplicationService to save.
First add two references. Microsoft.Phone.dll and System.Windows.dll are two references.
To reuse scenes, remove the sprites and other elements that need to be removed from the scene. We add a method to GamePlayLayer.
/// <summary>
/// Clear any old state
/// </ summary>
/// <param name = "replay"> Whether to replay the current level </ param>
public void Reset (bool replay)
{
foreach (var item in _targets)
{
this.removeChild (item, true);
}
foreach (var item in _projectiles)
{
this.removeChild (item, true);
}
_targets.Clear ();
_projectiles.Clear ();
projectilesDestroyed = 0;
nextProjectile = null;
if (replay)
life = 40;
else
level.NextLevel ();
this.schedule (gameLogic, 1.0f);
this.schedule (updates);
string msg = String.Format ("Count: {0}, life: {1}, Level: {2}", projectilesDestroyed, life, level.level);
label.setString (msg);
}
Note that once the scene jumps, those schedule events are invalid after returning to the scene. So reset it. The logic set here is the next level without replay. Here, the main state we clear is the sprites in _targets and _projectiles. After removing, clear these two lists.
So, we want to save this GamePlayScene.
The GamePlayScene class is modified as follows:
class GamePlayScene: CCScene
{
public GamePlayScene ()
{
CCLayerColor colorLayer = CCLayerColor.layerWithColor (new ccColor4B (255, 255, 255, 255));
this.addChild (colorLayer);
GamePlayLayer pLayer = (GamePlayLayer) GamePlayLayer.node ();
pLayer.tag = 3;
this.addChild (pLayer);
PhoneApplicationService.Current.State ["PlayScene"] = this;
}
}
In order to get the game layer, we added a tag element to it. And in the constructor, save this class to PhoneApplicationService.
The next modification is GameOverScene. We want to make this GameOverScene scene a springboard. To jump through levels.
Here is the victory interface:
With three options, replay, return to the menu, and the next level. Then we need some pictures. You can download it here: http://dl.dbank.com/c0g3z4wmma, and add the picture to the images directory of the Content project.
PS; the picture is a bit big, too lazy to be adjusted, so I will use it.
The GameOverScene class is modified as follows:
class GameOverScene: CCScene
{
public CCLabelTTF label;
public GameOverScene ()
{}
public GameOverScene (bool isWin)
{
CCLayerColor colorLayer = CCLayerColor.layerWithColor (new ccColor4B (255, 255, 255, 255));
this.addChild (colorLayer);
CCSize winSize = CCDirector.sharedDirector (). GetWinSize ();
string msg;
if (isWin)
msg = "YOU WIN";
else
msg = "YOU LOSE";
label = CCLabelTTF.labelWithString (msg, "Arial", 32);
label.Color = new ccColor3B (0, 0, 0);
label.position = new CCPoint (winSize.width / 2, winSize.height / 2 + 100);
this.addChild (label);
//this.runAction(CCSequence.actions(CCDelayTime.actionWithDuration(3), CCCallFunc.actionWithTarget (this, gameOverDone)));
var itemReplay = CCMenuItemImage.itemFromNormalImage (@ "images / reload", @ "images / reload", this, replay);
var itemMainMenu = CCMenuItemImage.itemFromNormalImage (@ "images / mainmenu", @ "images / mainmenu", this, mainMenu);
var itemNextLevel = CCMenuItemImage.itemFromNormalImage (@ "images / nextlevel", @ "images / nextlevel", this, nextLevel);
if (! isWin)
itemNextLevel.visible = false;
var menu = CCMenu.menuWithItems (itemReplay, itemMainMenu, itemNextLevel);
menu.alignItemsHorizontally ();
menu.position = new CCPoint (winSize.width / 2, winSize.height / 2-100);
this.addChild (menu);
}
void nextLevel (object sender)
{
GamePlayScene pScene;
if (PhoneApplicationService.Current.State.ContainsKey ("PlayScene"))
{
pScene = (GamePlayScene) PhoneApplicationService.Current.State ["PlayScene"];
GamePlayLayer pLayer = (GamePlayLayer) pScene.getChildByTag (3);
pLayer.Reset (false);
}
else
pScene = new GamePlayScene ();
CCDirector.sharedDirector (). ReplaceScene (pScene);
}
void mainMenu (object sender)
{
CCScene pScene = CCScene.node ();
pScene.addChild (cocos2dSimpleGame.Classes.MainMenu.node ());
CCDirector.sharedDirector (). ReplaceScene (pScene);
}
void replay (object sender)
{
GamePlayScene pScene;
if (PhoneApplicationService.Current.State.ContainsKey ("PlayScene"))
{
pScene = (GamePlayScene) PhoneApplicationService.Current.State ["PlayScene"];
GamePlayLayer pLayer = (GamePlayLayer) pScene.getChildByTag (3);
pLayer.Reset (true);
}
else
pScene = new GamePlayScene ();
CCDirector.sharedDirector (). ReplaceScene (pScene);
}
void gameOverDone ()
{
CCScene pScene = CCScene.node ();
pScene.addChild (cocos2dSimpleGame.Classes.MainMenu.node ());
CCDirector.sharedDirector (). ReplaceScene (pScene);
}
}
The basic logic estimates above can be understood. Three menu options have been added. In the replay and next level, first get the scene, then get to the game layer, call Reset to complete the replay or next level setting. Then the scene jumps.
Here, no matter what, we have a very good game-a rotating turret, thousands of different types of enemies, multiple levels, win / lose scenes, and of course, great Sound effects!
Download this project: http://dl.dbank.com/c0c1vbow72
Continue to learn: make a simple windows with cocos2d-x
phone 7 game: tombstone mechanism and finishing work (end)