Asteroids with FRB
part 4
Now we get into the really the inner workings of the game. We add a scoring feature, lives, and a silent ending. I just want to show you how easy it is to do these things, later we work it into a game over screen. Also, when you hit all the rocks, we spawn more rocks! Not only that but over time the rocks get faster. I use the score, I think that may be how the original does it. I know, still no UFO, or textures. Textures come last, always last. It is always best to get the foundation done first. When you build a house, do you out the house up, then make the foundation? I think not. I know its not pretty, but I hope you are learning as you follow along. The UFO gets added in the next chapter.
Step One
Open Rocks.cs first, we are going to start there.
Add this to the Properties region:
Add this to the Properties region:
public int Speed { get { return iRockSpeed; } set { iRockSpeed = value; } } |
This is how we are going to control the rock speed, I thought I would show you another way of doing something. We could have passed it into the initialize function as well. There is always more then one way to do something. So this is how you can control and read something through a class property interface. This way, we are all done here, easy as that.
Step Two
Open the PlayerShip.cs file, we have more work to do here.
Add this line to the Fields region:
Add this line to the Fields region:
private int iLives = 3; |
Lets keep track of player lives in the PlayerShip class. This way we can control utterly how it gets changed. Also keeping things separated, and organized makes for easier debugging. Because it is private it can only be changed and accessed through interfaces inside this class. Of course we start out with three lives.
Add these functions to the Private Methods region:
Add these functions to the Private Methods region:
private void Reset() { XVelocity = 0; YVelocity = 0; XAcceleration = 0; YAcceleration = 0; X = 0; Y = 0; RotationZ = 0; } private void Dead() { pCollision.Visible = false; } |
This is for when you get hit, and when you die. We want to reset all the variables back to zero when you get hit, so you go back to center screen. Later we will even check to see if it is clear when you spawn, we save that for next chapter. When you die, we also make you invisible. For now, we do that for end game. We change that in another chapter.
Add these functions to the Public Methods region:
Add these functions to the Public Methods region:
public void BulletHit(int Bullet) { ShapeManager.Remove(mBullets[Bullet]); } public void Hit() { Reset(); if (iLives > 0) iLives--; if (iLives == 0) Dead(); } public void NewLife() { iLives++; } public void NewGame() { Reset(); iLives = 3; } |
Here we make it so your bullet is removed when you hit a rock, we use a call from the GameScreen class to do that. Remember its best to use interfaces. When you are hit, first we use that Reset function we added before, then we make sure we don't go negative, then subtract one from iLives. If you have run out, then you become invisible. When you earn a new life, we run this function from the GameScreen class to add one to iLives. When we start a new game we use the Reset function we added earlier, then set the iLives to 3 again. We wont be using the NewGame function until another Chapter, but I thought I would add it now. Good to think ahead.
Step Three
Now we open the GameScreen.cs once more, this is where the meat gets added.
Add these lines to the Fields region:
Add these lines to the Fields region:
private int iScore; private int iNewLives = 1; private Text textScore = TextManager.AddText(""); private Text textLives = TextManager.AddText(""); |
Here we have our Score tally, we use iNewLives so we know when you get a new life, it is used to calculate when you get another new life later. We use two text objects, because its just easier this way, sense Asteroids has the score in the top left corner then little ships for number of lives, for now though we use text. That will get changed in another chapter.
Add this function to the Private Methods region:
Add this function to the Private Methods region:
private void NewText() { textScore.Scale = 10; textScore.Spacing = 10.7f; textLives.Scale = 10; textLives.Spacing = 10.7f; textScore.X = -SpriteManager.Camera.RelativeXEdgeAt(0) + 10; textScore.Y = SpriteManager.Camera.RelativeYEdgeAt(0) - 10; textLives.X = -SpriteManager.Camera.RelativeXEdgeAt(0) + 10; textLives.Y = SpriteManager.Camera.RelativeYEdgeAt(0) - 30; } |
We need this to setup the text objects. They are automaticly updated in the SpriteManager as well. (Go here to read more on Text) Basically we set them up with how big they are, the spacing between letters, and where its going. We want the scale to be about 10 pixels that works, and the spacing just a little more then that. Again we use the Relative Edge At functions of the Camera object. As you may see, this puts them in the top left corner, the lives right under the score.
Add this to the Initialize public method:
Add this to the Initialize public method:
// New for Text NewText(); |
Here we activate the function we just added.
Next lets change our PlayerShotvsRocks function to this:
Next lets change our PlayerShotvsRocks function to this:
// Our first private method for the screen class. We need to go in reverse // because if one is removed, they are shifted up. These are lists. private void PlayerShotvsRocks() { bool HitRock = false; int RockHit = 0; int BulletHit = 0; for (int Bullet = PlayerSprite.Bullets.Count - 1; Bullet > -1; Bullet--) { for (int Rock = RockSprites.Collisions.Count - 1; Rock > -1; Rock--) { if (PlayerSprite.Bullets[Bullet].CollideAgainst(RockSprites.Collisions[Rock])) { RockHit = Rock; BulletHit = Bullet; HitRock = true; break; } } } if (HitRock) { RockSprites.AsteroidHit(RockHit); PlayerSprite.BulletHit(BulletHit); iScore = iScore + 10; if (iScore / 1000 * iNewLives == 1) { PlayerSprite.NewLife(); iNewLives++; } } } |
This will keep it safe, for when we remove a rock, and bullet, we don't want to still try to count those in the for loops. So we remove them after. That means adding three things, the bool to turn true if one is hit, and two integers for there list numbers. We use that switch in our if statement to activate both our Hit functions. Good thing we added that BulletHit function awhile back. Then we add to the score and its a good place to put the formula to see if you earn a new life. This way the formula is only calculated when a rock is hit. Later we will be moving this to its own function so we can reuse it.
Add this function to our Private Methods region:
Add this function to our Private Methods region:
private void RockvsPlayer() { for (int Rock = RockSprites.Collisions.Count - 1; Rock > -1; Rock--) { if (PlayerSprite.Collision.CollideAgainst(RockSprites.Collisions[Rock])) { RockSprites.AsteroidHit(Rock); PlayerSprite.Hit(); break; } } } |
This makes it so you the player can now be hit. We use the same AsteroidHit function we made way back for shotting one again. Reuse, good for us! We use our PlayerSprite.Hit function we made awhile back. We break out of the for loop, we got hit this frame, no need to continue. Also sense we removed a rock, we could cause an error counting it again.
Now add this function to our Private Methods region:
Now add this function to our Private Methods region:
private void KeepTrackOfRocks() { if (RockSprites.Collisions.Count < 1) { iNumberOfRocks++; if (iScore > 5 * RockSprites.Speed && RockSprites.Speed < 150) RockSprites.Speed = (int)(RockSprites.Speed * 1.5f); RockSprites.Initialize(iNumberOfRocks); } } |
We need to keep track of our rocks, that is how many there our, how many to spawn and how fast they should go. We only calculate the speed once every time we spawn the rocks. Or course we also add a rock each time. This adds challenge to the game as it goes. This will change as we go on, but this gives you an idea of what is to come.
Add this to the Activity public method:
Add this to the Activity public method:
KeepTrackOfRocks(); |
Now at last we add this function to our Private Methods region:
private void UpdateText() { textScore.DisplayText = "Score: " + iScore; textLives.DisplayText = " Lives: " + PlayerSprite.Lives; } |
We need to keep the score and lives updated. In another chapter we are going to make some changes to make the game more effecient, that will include splitting this up so we only update what is needed, when its needed. Not every frame.
Add this line to the Activity public method:
Add this line to the Activity public method:
UpdateText(); |
This runs the function we just added every frame.
Add and make these changes to the Activity public method:
Add and make these changes to the Activity public method:
if (PlayerSprite.Lives > 0) { PlayerSprite.Activity(); RockvsPlayer(); } |
For now, we are going to have the player ship vanish, and what rocks are left will float around the screen. These functions we put inside the if statement to run every frame only if you still have lives left. We add the one function for updating text in here too. If you were shooting when you lose your last ship, you may notice that your shots live on, and still hit something. So it is possible to get one last kill after you die. Those methods are still running. Something we want to do is wait until its all over before we do the GameOver screen later. We still have more to do to get ready for that, in another chapter, we will leave that.
Time to hit play, and play your game! Look its actually playable! Keeps your score, and stops when your run out of lives. Still more to do though before its done.
If you have any issues, as always you can download the project so far. Asteroids Tutor Chapter Four.
If you have any issues, as always you can download the project so far. Asteroids Tutor Chapter Four.