This chapter is under construction. Thanks for your patience.
In this chapter, you will learn how to:
- implement multiple states
- change the current state
Wanted dead or alive
Now TankGame has all the characters - the Tank, CannonBalls, Enemies, and AimingSight. It launches. It explodes. So what do we need?
Actually we still need some stuff to make this game playable. There is no "game over." There is no score, no high score, and much more. Yes, they want to fight each other. Enemies want your tank dead or alive, so let them fight!
Let Them Fight
Let me explain a little bit about what we will create before we get started.
- There are three tanks available
- The game shows "Game Over" when no tank is left
- The tank is dead when an enemy reaches the border line
- An enemy is destroyed when it is blown by a cannonball
- You can go to the next stage when all the tank are destroyed
- The game shows the stage number at the beginning of each stage.
- Enemies move a little faster at the next stage
Okay, here we go!
Change the rules - by updating the Model
Initialization routines
class TankGameModel
MAX_LIFE = 3
# invoked at the beginning of each stage
def reset()
@tank = Tank.new()
@enemies = []
@cannons = []
@cleared = false
@dead = false
Enemy::MAX_ENEMIES.times do |i|
@enemies << Enemy.new(i, @speed)
end
end
# invoked when this game is selected
def init()
@highScore = Preferences.valueForKey('HighScore').to_i
@score = 0
@stage = 1
@life = MAX_LIFE
@speed = 4
end
Checking dead or alive
def moveCharacters()
reset() if (@dead || @cleared)
@borderline = 200
@cannons.each do |cannon|
cannon.move()
cannon.checkCollision(@enemies)
@cannons.delete(cannon) if (!cannon.active)
end
@enemies.each do |enemy|
enemy.move()
if (!enemy.active)
@enemies.delete(enemy)
@score += 100
@highScore = @score if (@score > @highScore)
end
die() if (enemy.reached?(@borderline))
end
clearStage() if (@enemies.size == 0)
end
def die()
@life -= 1
@dead = true
@gameOver = true if (@life <= 0)
end
def clearStage()
@cleared = true
@speed += 1
@stage += 1
end
Recording High Scores
def recordHighScore()
Preferences.setValue('HighScore', @highScore)
end
Let Them Begin
To show the stage number at the beginning of each stage.....
class TankGameStartState < State
GAMESTART_TIMER = 1002
def enter()
super()
@model.reset()
@timer = TimerEvent.new(3.0, false, GAMESTART_TIMER)
end
def tick(id=nil)
changeState(TankGamePlayingState)
return true
end
end
Let Them end the fight
class TankGameOverState < State
GAMEOVER_TIMER = 1001
def enter()
super()
@timer = TimerEvent.new(3.0, false, GAMEOVER_TIMER)
end
def tick(id=0)
@model.recordHighScore()
quit()
return true
end
end
Draw everything we need
Draw the score and available tanks
class TankGameView < View
# updated
def drawStatus()
image = @model.tank.image
x = @bounds.size.width - image.size.width / 4 - 15
y = @bounds.size.height - image.size.height / 4 - 15
@model.life.times do |i|
destRect = NSRect.new(x, y,
image.size.width / 4, image.size.height / 4)
srcRect = NSRect.new([0,0], image.size)
image.drawInRect(destRect, :fromRect, srcRect,:operation,
NSCompositeSourceOver, :fraction, 1.0)
x -= image.size.width / 4 + 5
end
x = 20
y = @bounds.size.height - 50
drawText(sprintf('%05d', @model.score), 40, NSColor.blackColor, [x,y])
end
def drawRect()
NSColor.whiteColor.colorWithAlphaComponent(0.9).set
NSRectFill(@bounds)
# Using shadow can be a performance bottleneck
# setShadow(NSColor.lightGrayColor, [6.0, -6.0], 3.7, 0.6)
NSColor.blackColor.set
path = NSBezierPath.bezierPath
point = NSPoint.new(0, @model.borderline)
path.moveToPoint(point)
point.x = Field.width
path.lineToPoint(point)
path.stroke
drawStatus() # <------ updated
@model.tank.draw()
(@model.enemies + @model.cannons).each {|obj| obj.draw()}
@model.tank.aiming.draw()
end
end
View classes for new states
TankGameStartView
class TankGameStartView < TankGameView
def drawRect()
NSColor.whiteColor.colorWithAlphaComponent(0.9).set
NSRectFill(@bounds)
# setShadow(NSColor.lightGrayColor, [6.0, -6.0], 3.7, 0.6)
drawCenterizedText("Stage #{@model.stage}", 100, NSColor.blackColor)
drawStatus()
end
end
TankGameOverState
class TankGameOverView < TankGameView
def drawRect()
NSColor.whiteColor.set
NSRectFill(@bounds)
# setShadow(NSColor.lightGrayColor, [6.0, -6.0], 3.7, 0.6)
drawStatus()
drawCenterizedText("GAME OVER", 100, NSColor.blackColor)
score = @model.score
highScore = @model.highScore
if (score == highScore)
drawCenterizedText("You made a high score", 50, NSColor.redColor,
NSPoint.new(0, -200))
else
drawCenterizedText("High Score: #{highScore}", 50, NSColor.orangeColor,
NSPoint.new(150, -200))
end
end
end
Bind everything again
class TankGame < Game
def initialize()
# +-- updated
# v
@bindings = [[TankGamePlayingState, TankGameModel, TankGameView],
[TankGameStartState, TankGameModel, TankGameStartView],
[TankGameOverState, TankGameModel, TankGameOverView]]
@initialState = TankGameStartState
end
Check it out
What we've created so far
is the file that we made so far. Here are the steps to run the game.
- Untar the tar.gz file at the project folder
- Add a group named "TankGame" to the "Resources" group at the left pane of the Xcode project window. (if not exists)
- Right click the group and select "Add" -> "Existing Files" and add all the tiff files (Add only TankGame.tiff if you already added other images)
- Run the application by pressing the "Build & Run" button










