We have all sorts of enemies flying at our hero and if he hits them they disappear. Now we need to incentivize him to NOT run into all the bad guys.
Our next steps are to make enemies damage our hero when he runs into them, give him the ability to shoot the enemies before they reach him, and give the player points whenever they successfully destroy an enemy.
Health
Since all of our enemies and our player are Characters in our game, we can handle keeping track of their health in our Character class.
To do so we will add a health field and give each enemy and the player some starting health and a maximum amount of health.
Import fantomX | |
Class Character | |
Field box:ftObject | |
' Give characters health | |
Field current_health:Int | |
Field max_health:Int | |
Field is_player:Bool | |
' Modify the New method to include health and set health, we will default this to 1 | |
Method New(box:ftObject, health:Int=1, player:Bool=False) | |
Self.box = box | |
Self.current_health = health | |
Self.max_health = health | |
Self.is_player = player | |
End | |
' ............ | |
End |
Now we need to change our game creation code and our enemy generator to pass in health for our player and enemies.
Class Game Extends App | |
' ............. | |
Method OnCreate() | |
Self.engine = New CustomEngine | |
default_scene = engine.GetDefaultScene() | |
default_layer = engine.GetDefaultLayer() | |
Local box:ftObject = Self.engine.CreateBox(120, 20, engine.GetCanvasWidth()/2, engine.GetCanvasHeight()/2) | |
box.SetColor(0, 70, 70) | |
box.SetMaxSpeed(20.0) | |
box.SetMinSpeed(-20.0) | |
box.SetColGroup(PLAYER_GROUP) | |
box.SetColType(Self.engine.ctBox) | |
box.SetText("PLAYER") | |
' Give player 3 health | |
Self.player = New Character(box, 3, True) | |
Self.enemies = New List<Character>() | |
Self.last_enemy_time = Millisecs() | |
Self.next_enemy_interval = 3000 | |
Seed = Millisecs() | |
End | |
' .................. | |
Method CreateEnemy() | |
Local rand_width:Float = Rnd(3, 7) * 10 | |
Local rand_height:Float = Rnd(3, 7) * 10 | |
Local rand_y:Float = Rnd(rand_height, Self.engine.GetCanvasHeight()) | |
Local box:ftObject = Self.engine.CreateBox(rand_width, rand_height, Self.engine.GetCanvasWidth(), rand_y) | |
Local rand_color:Float = Rnd(0, 3) | |
' randomize enemy health to correspond to color | |
' red enemies have 1, green enemies have 2 | |
' and blue enemies have 3 | |
Local health = 1 | |
Local colors:Int[] = [0, 0, 0] | |
If (rand_color <= 1.0) | |
colors = [255, 0, 0] | |
Else If (rand_color > 1.0 And rand_color <= 2.0) | |
colors = [0, 255, 0] | |
health = 2 | |
Else | |
colors = [0, 0, 255] | |
health = 3 | |
End | |
box.SetColor(colors[0], colors[1], colors[2]) | |
box.SetSpeedX(Rnd(10, 30) * -1) | |
box.SetColGroup(ENEMY_GROUP) | |
box.SetColWith(PLAYER_GROUP, True) | |
box.SetText("ENEMY") | |
' give the enemy the health value based on their color | |
enemies.AddLast(New Character(box, health)) | |
Self.next_enemy_interval = Rnd(0, 3) * 1000 | |
End | |
' ................. | |
End |
Damage
Now that characters have health, we should give them the ability to take damage.
Let’s change the OnObjectCollisionMethod to cause the player and enemies to damage each other when they run into each other.
Import fantomX | |
Class CustomEngine Extends ftEngine | |
Method OnObjectCollision:Int(obj_one:ftObject, obj_two:ftObject) | |
If (obj_one.GetText() = "PLAYER" And obj_two.GetText() = "ENEMY") | |
' Change the text to be damaged and set it for both the player and the enemy | |
' this way players and enemies will only get damaged once per update | |
obj_two.SetText("DAMAGED") | |
obj_one.SetText("DAMAGED") | |
obj_two.SetColWith(obj_one.GetColGroup(), False) | |
Else If (obj_two.GetText() = "PLAYER" And obj_one.GetText() = "ENEMY") | |
obj_one.SetText("DAMAGED") | |
obj_two.SetText("DAMAGED") | |
obj_one.SetColWith(obj_two.GetColGroup(), False) | |
End | |
Return 0 | |
End | |
End |
In our character class we are going to add a method that handles units taking damage. Then we will call it from the character Update method.
Class Character | |
' ........................ | |
Method Update | |
' ...................... | |
' Every update, check to see if the character was damaged | |
If (Self.box.GetText() = "DAMAGED") | |
Self.TakeDamage(1) | |
End | |
End | |
' Add a method that handles lowering character health | |
' and tags characters with 0 health as destroyed | |
Method TakeDamage(amount:Int=0) | |
Self.current_health -= amount | |
If (Self.current_health <= 0) | |
Self.box.SetText("DESTROYED") | |
Else If (Self.is_player) | |
Self.box.SetText("PLAYER") | |
Else | |
Self.box.SetText("ENEMY") | |
End | |
End | |
End |
And in our OnUpdate method, we will make some changes to handle updating characters to damage them and destroy them.
Method OnUpdate() | |
Local time_delta:Float = Float(engine.CalcDeltaTime())/60.0 | |
If ((Millisecs() - Self.last_enemy_time) > Self.next_enemy_interval) | |
CreateEnemy() | |
Self.last_enemy_time = Millisecs() | |
End | |
player.Update(engine.GetCanvasWidth(), engine.GetCanvasHeight()) | |
For Local enemy:Character = Eachin Self.enemies | |
' Add an update to all of our enemies so we can check for damage | |
enemy.Update(engine.GetCanvasWidth(), engine.GetCanvasHeight()) | |
If (enemy.box.GetText() = "DESTROYED") | |
enemy.box.Remove() | |
Self.enemies.RemoveFirst(enemy) | |
End | |
End | |
If engine.GetPaused() = False | |
engine.Update(time_delta) | |
engine.CollisionCheck() | |
End | |
End |
Now when you run the game, when the player runs into an enemy it causes damage instead of outright destroying them. Enemies with more than 1 health won’t be destroyed. And after the player takes too much damage, they will no longer damage enemies. Eventually we will add a GAME OVER or lost life for this case but that will come later.
Projectiles
Now that we have characters taking damage, lets give our player the ability to shoot at enemies and destroy them. We will start by making a projectile class and a projectile type class. Create a new file called projectile.monkey that we can start making projectiles from. It will have a constructor and an update method.
Import fantomX | |
Import projectile_type | |
Class Projectile | |
Field box:ftObject | |
Field projectile_type:ProjectileType | |
Method New(box:ftObject, projectile_type:ProjectileType) | |
Self.box = box | |
Self.projectile_type = projectile_type | |
End | |
Method Update() | |
Local modifier:Float = 1 | |
If (Self.box.GetSpeedX() < 0) | |
modifier = -1 | |
End | |
If (Abs(Self.box.GetSpeedX()) <= Self.projectile_type.max_speed) | |
Self.box.SetSpeedX(Self.box.GetSpeedX() + (Self.projectile_type.acceleration * modifier)) | |
Else | |
Self.box.SetSpeedX(Self.projectile_type.max_speed * modifier) | |
End | |
End | |
End |
and a file called projectile_type.monkey
Import fantomX | |
Class ProjectileType | |
Field power:Int | |
Field range:Float | |
Field acceleration:Float | |
Field max_speed:Float | |
Method New(power:Int=1, range:Float=1000, acceleration:Float=0, max_speed:Float=200) | |
Self.power = power | |
Self.range = range | |
Self.acceleration = acceleration | |
Self.max_speed = max_speed | |
End | |
End |
Projectiles have a variety of attributes. First of all they have a box that can collide with enemies, and when we make enemies that shoot, with the player. Next they have power. This is done so that powerups can make the player’s projectiles stronger.
We also give the projectiles maximum speed, range, and acceleration. This is also for handling upgrades and how different weapons on different player ships or characters will work. We can have a short range, fast moving projectile. We can have a long range projectile that starts slow but speeds up over time which we handle with the Projectile’s Update method. Whatever we want.
The reason we separate the projectile type attributes out is so that we can store each character’s projectile type in their character class.
Firing
Now we want our player to be able to make some kind of projectile when the space bar is pressed. We are going to give the player character a type of projectile to fire and limit their fire rate. The idea is that whenever the fire key or button is pressed, we will true to the game engine and it will create a new projectile at the player’s location.
First we need to pass in a ProjectileType as part of the Character class constructor.
'......................... | |
Class Game Extends App | |
'......................... | |
Method OnCreate() | |
Self.engine = New CustomEngine | |
default_scene = engine.GetDefaultScene() | |
default_layer = engine.GetDefaultLayer() | |
Local box:ftObject = Self.engine.CreateBox(120, 20, engine.GetCanvasWidth()/2, engine.GetCanvasHeight()/2) | |
box.SetColor(0, 70, 70) | |
box.SetMaxSpeed(20.0) | |
box.SetMinSpeed(-20.0) | |
box.SetColGroup(PLAYER_GROUP) | |
box.SetColType(Self.engine.ctBox) | |
box.SetText("PLAYER") | |
' create a projectile type for our player | |
Local projectile_type:ProjectileType = New ProjectileType() | |
' and add it to the creation call | |
Self.player = New Character(box, 3, True, projectile_type) | |
Self.enemies = New List<Character>() | |
Self.last_enemy_time = Millisecs() | |
Self.next_enemy_interval = 3000 | |
Seed = Millisecs() | |
End | |
'...................... | |
Method CreateEnemy() | |
Local rand_width:Float = Rnd(3, 7) * 10 | |
Local rand_height:Float = Rnd(3, 7) * 10 | |
Local rand_y:Float = Rnd(rand_height, Self.engine.GetCanvasHeight()) | |
Local box:ftObject = Self.engine.CreateBox(rand_width, rand_height, Self.engine.GetCanvasWidth(), rand_y) | |
' create a base projectile type for our enemies | |
Local projectile_type:ProjectileType = New ProjectileType() | |
Local rand_color:Float = Rnd(0, 3) | |
Local health = 1 | |
Local colors:Int[] = [0, 0, 0] | |
If (rand_color <= 1.0) | |
colors = [255, 0, 0] | |
Else If (rand_color > 1.0 And rand_color <= 2.0) | |
colors = [0, 255, 0] | |
health = 2 | |
Else | |
colors = [0, 0, 255] | |
health = 3 | |
End | |
box.SetColor(colors[0], colors[1], colors[2]) | |
box.SetSpeedX(Rnd(10, 30) * -1) | |
box.SetColGroup(ENEMY_GROUP) | |
box.SetColWith(PLAYER_GROUP, True) | |
box.SetText("ENEMY") | |
' give enemies a projectile type | |
enemies.AddLast(New Character(box, health, False, projectile_type)) | |
Self.next_enemy_interval = Rnd(0, 3) * 1000 | |
End | |
End |
And modify the Character class to have a ProjectileType
Import fantomX | |
Import projectile_type | |
Class Character | |
Field box:ftObject | |
Field current_health:Int | |
Field max_health:Int | |
Field is_player:Bool | |
' Add projectile type | |
Field projectile_type:ProjectileType | |
Method New(box:ftObject, health:Int=1, player:Bool=False, projectile_type:ProjectileType) | |
Self.box = box | |
Self.current_health = health | |
Self.max_health = health | |
Self.is_player = player | |
' Set initial projectile type | |
Self.projectile_type = projectile_type | |
End | |
'................... | |
End |
Now we are going to add a projectile creation function for our game, and set up the SPACE bar to be our firing control.
#FantomX_UsePhysics = True | |
Import fantomX | |
Import character | |
' Import projectile | |
Import projectile | |
Import custom_engine | |
Class Game Extends App | |
Field engine:ftEngine | |
Field default_layer:ftLayer | |
Field default_scene:ftScene | |
Field player:Character | |
Field enemies:List<Character> | |
Field last_enemy_time:Float | |
Field next_enemy_interval:Float | |
' add collection to hold the games projectiles | |
Field projectiles:List<Projectile> | |
Const PLAYER_GROUP:Int = 1 | |
Const ENEMY_GROUP:Int = 2 | |
' Add projectile collision group | |
Const PROJECTILES:Int = 3 | |
Method OnCreate() | |
Self.engine = New CustomEngine | |
default_scene = engine.GetDefaultScene() | |
default_layer = engine.GetDefaultLayer() | |
Local box:ftObject = Self.engine.CreateBox(120, 20, engine.GetCanvasWidth()/2, engine.GetCanvasHeight()/2) | |
box.SetColor(0, 70, 70) | |
box.SetMaxSpeed(20.0) | |
box.SetMinSpeed(-20.0) | |
box.SetColGroup(PLAYER_GROUP) | |
box.SetColType(Self.engine.ctBox) | |
box.SetText("PLAYER") | |
Local projectile_type:ProjectileType = New ProjectileType() | |
Self.player = New Character(box, 3, True, projectile_type) | |
Self.enemies = New List<Character>() | |
Self.last_enemy_time = Millisecs() | |
Self.next_enemy_interval = 3000 | |
' initialize projectile list | |
Self.projectiles = New List<Projectile>() | |
Seed = Millisecs() | |
End | |
Method OnUpdate() | |
Local time_delta:Float = Float(engine.CalcDeltaTime())/60.0 | |
If ((Millisecs() - Self.last_enemy_time) > Self.next_enemy_interval) | |
CreateEnemy() | |
Self.last_enemy_time = Millisecs() | |
End | |
player.Update(engine.GetCanvasWidth(), engine.GetCanvasHeight()) | |
If (KeyDown(KEY_SPACE)) | |
CreateProjectile(player) | |
End | |
For Local enemy:Character = Eachin Self.enemies | |
enemy.Update(engine.GetCanvasWidth(), engine.GetCanvasHeight()) | |
If (enemy.box.GetText() = "DESTROYED") | |
enemy.box.Remove() | |
Self.enemies.RemoveFirst(enemy) | |
End | |
End | |
If engine.GetPaused() = False | |
engine.Update(time_delta) | |
engine.CollisionCheck() | |
End | |
End | |
Method CreateEnemy() | |
Local rand_width:Float = Rnd(3, 7) * 10 | |
Local rand_height:Float = Rnd(3, 7) * 10 | |
Local rand_y:Float = Rnd(rand_height, Self.engine.GetCanvasHeight()) | |
Local box:ftObject = Self.engine.CreateBox(rand_width, rand_height, Self.engine.GetCanvasWidth(), rand_y) | |
Local projectile_type:ProjectileType = New ProjectileType() | |
Local rand_color:Float = Rnd(0, 3) | |
Local health = 1 | |
Local colors:Int[] = [0, 0, 0] | |
If (rand_color <= 1.0) | |
colors = [255, 0, 0] | |
Else If (rand_color > 1.0 And rand_color <= 2.0) | |
colors = [0, 255, 0] | |
health = 2 | |
Else | |
colors = [0, 0, 255] | |
health = 3 | |
End | |
box.SetColor(colors[0], colors[1], colors[2]) | |
box.SetSpeedX(Rnd(10, 30) * -1) | |
box.SetColGroup(ENEMY_GROUP) | |
box.SetColWith(PLAYER_GROUP, True) | |
box.SetText("ENEMY") | |
enemies.AddLast(New Character(box, health, False, projectile_type)) | |
Self.next_enemy_interval = Rnd(0, 3) * 1000 | |
End | |
' Add a method to create new projectiles | |
Method CreateProjectile(character:Character) | |
Local projectile_box:ftObject = Self.engine.CreateBox(10, 20, character.box.GetPosX(), character.box.GetPosY()) | |
projectile_box.SetColGroup(PROJECTILES) | |
If (character.is_player) | |
projectile_box.SetSpeedX(10) | |
projectile_box.SetColor(0, 255, 255) | |
projectile_box.SetColWith(ENEMY_GROUP, True) | |
Else | |
projectile_box.SetSpeedX(-10) | |
projectile_box.SetColor(255, 255, 0) | |
projectile_box.SetColWith(PLAYER_GROUP, True) | |
End | |
projectile_box.SetText("PROJECTILE") | |
Self.projectiles.AddLast(New Projectile(projectile_box, character.projectile_type)) | |
End | |
Method OnRender() | |
engine.Clear(255, 255, 255) | |
engine.Render() | |
engine.SetColor(0, 70, 70) | |
engine.RenderFlush() | |
End | |
End | |
Function Main() | |
New Game() | |
End |
You should be able to “fire” bullets now. Currently we are not restricting how fast you can fire, which means you can use bullets to draw a line on the screen. Our next step is to limit the players fire rate. We will do that by giving characters a last fired time as well as giving projectile types a rate of fire.
Start by updating the ProjectileType class.
Import fantomX | |
Class ProjectileType | |
Field power:Int | |
Field range:Float | |
Field acceleration:Float | |
Field max_speed:Float | |
' Add rate of fire | |
Field rate_of_fire:Float | |
' and also add to constructor | |
Method New(power:Int=1, range:Float=1000, acceleration:Float=0, max_speed:Float=200, rate_of_fire:Float=2.0) | |
Self.power = power | |
Self.range = range | |
Self.acceleration = acceleration | |
Self.max_speed = max_speed | |
Self.rate_of_fire = rate_of_fire | |
End | |
End |
Then in the Character class, lets add a method that determines whether or not a character can fire.
Import fantomX | |
Import projectile_type | |
Class Character | |
Field box:ftObject | |
Field current_health:Int | |
Field max_health:Int | |
Field is_player:Bool | |
Field projectile_type:ProjectileType | |
' last projectile fired time | |
Field last_projectile_time:Float | |
Method New(box:ftObject, projectile_type:ProjectileType, health:Int=1, player:Bool=False) | |
Self.box = box | |
Self.current_health = health | |
Self.max_health = health | |
Self.is_player = player | |
Self.projectile_type = projectile_type | |
' Initialize last projectile time | |
Self.last_projectile_time = Millisecs() | |
End | |
'................................ | |
' Add method to determine whether or not this character can fire a projectile | |
Method FireProjectile:Bool() | |
If ((Millisecs() - Self.last_projectile_time) > (1000.0 / Self.projectile_type.rate_of_fire)) | |
Self.last_projectile_time = Millisecs() | |
Return True | |
Else | |
Return False | |
End | |
End | |
End |
Finally, add the check for whether the player character can fire or not in our games OnUpdate method. Also we are going to make some small modifications to the CreateProjectile method
' Game Class | |
Method OnUpdate() | |
Local time_delta:Float = Float(engine.CalcDeltaTime())/60.0 | |
If ((Millisecs() - Self.last_enemy_time) > Self.next_enemy_interval) | |
CreateEnemy() | |
Self.last_enemy_time = Millisecs() | |
End | |
player.Update(engine.GetCanvasWidth(), engine.GetCanvasHeight()) | |
' Add our character can fire check here | |
If (KeyDown(KEY_SPACE) And player.FireProjectile()) | |
CreateProjectile(player) | |
End | |
For Local enemy:Character = Eachin Self.enemies | |
enemy.Update(engine.GetCanvasWidth(), engine.GetCanvasHeight()) | |
If (enemy.box.GetText() = "DESTROYED") | |
enemy.box.Remove() | |
Self.enemies.RemoveFirst(enemy) | |
End | |
End | |
If engine.GetPaused() = False | |
engine.Update(time_delta) | |
engine.CollisionCheck() | |
End | |
End | |
' ............................ | |
Method CreateProjectile(character:Character) | |
Local projectile_box:ftObject = Self.engine.CreateBox(20, 10, character.box.GetPosX() + character.box.GetWidth()/2, character.box.GetPosY()) | |
projectile_box.SetColGroup(PROJECTILES) | |
If (character.is_player) | |
projectile_box.SetSpeedX(50) | |
projectile_box.SetColor(0, 255, 255) | |
projectile_box.SetColWith(ENEMY_GROUP, True) | |
Else | |
projectile_box.SetPosX(character.box.GetPosX() - character.box.GetWidth()/2) | |
projectile_box.SetSpeedX(-50) | |
projectile_box.SetColor(255, 255, 0) | |
projectile_box.SetColWith(PLAYER_GROUP, True) | |
End | |
projectile_box.SetText("PROJECTILE") | |
Self.projectiles.AddLast(New Projectile(projectile_box, character.projectile_type)) | |
End |
Your player’s character should now be firing little light blue bullets about every half second if they hold down the space bar.
Projectile Collision and Damage
Now that we can fire projectiles, we want them to actually hit the enemies and do damage to them. This means we need to modify our CustomEngine Class OnObjectCollision method to tell enemies to be damaged by projectiles and to set projectiles to destroyed when they hit enemies. While we do that, we will also refactor our other collision code to make it cleaner.
Import fantomX | |
Class CustomEngine Extends ftEngine | |
Method OnObjectCollision:Int(obj_one:ftObject, obj_two:ftObject) | |
' Refactor character collision checks to use method | |
If (obj_one.GetText() = "PLAYER" And obj_two.GetText() = "ENEMY") | |
CharacterCollision(obj_one, obj_two) | |
Else If (obj_two.GetText() = "PLAYER" And obj_one.GetText() = "ENEMY") | |
CharacterCollision(obj_two, obj_one) | |
End | |
' Check for projectile collision with enemies | |
If (obj_one.GetText() = "PROJECTILE" And obj_two.GetText() = "ENEMY") | |
ProjectileCollision(obj_one, obj_two) | |
Else If (obj_two.GetText() = "PROJECTILE" And obj_one.GetText() = "ENEMY") | |
ProjectileCollision(obj_two, obj_one) | |
End | |
Return 0 | |
End | |
' add projectile collison behavior | |
Method ProjectileCollision(projectile_box:ftObject, enemy_box:ftObject) | |
projectile_box.SetText("DESTROYED") | |
enemy_box.SetText("DAMAGED") | |
projectile_box.SetColWith(enemy_box.GetColGroup(), False) | |
End | |
' refactor character collision behavior to a method | |
Method CharacterCollision(player_box:ftObject, enemy_box:ftObject) | |
player_box.SetText("DAMAGED") | |
enemy_box.SetText("DAMAGED") | |
enemy_box.SetColWith(player_box.GetColGroup(), False) | |
End | |
End |
We also need to remove all projectiles that hit enemies from the game, so they don’t hit enemies twice. And since we are not currently calling the projectile’s Update method each loop, acceleration will not work yet. Let’s add that in as well as we loop over the projectiles.
' Game Class | |
Method OnUpdate() | |
Local time_delta:Float = Float(engine.CalcDeltaTime())/60.0 | |
If ((Millisecs() - Self.last_enemy_time) > Self.next_enemy_interval) | |
CreateEnemy() | |
Self.last_enemy_time = Millisecs() | |
End | |
player.Update(engine.GetCanvasWidth(), engine.GetCanvasHeight()) | |
If (KeyDown(KEY_SPACE) And player.FireProjectile()) | |
CreateProjectile(player) | |
End | |
' Remove destroyed projectiles from our game | |
' and call projectiles update method | |
For Local projectile:Projectile = Eachin Self.projectiles | |
projectile.Update() | |
If (projectile.box.GetText() = "DESTROYED") | |
projectile.box.Remove() | |
Self.projectiles.RemoveFirst(projectile) | |
End | |
End | |
For Local enemy:Character = Eachin Self.enemies | |
enemy.Update(engine.GetCanvasWidth(), engine.GetCanvasHeight()) | |
If (enemy.box.GetText() = "DESTROYED") | |
enemy.box.Remove() | |
Self.enemies.RemoveFirst(enemy) | |
End | |
End | |
If engine.GetPaused() = False | |
engine.Update(time_delta) | |
engine.CollisionCheck() | |
End | |
End |
When you run the game now, bullets that hit enemies should disappear and enemies that take enough damage should be destroyed (at this point red enemies take 1 damage, green take 2, and blue ones take 3).
Score
For the final part of this section, we are going to give the player points whenever he destroys an enemy. To do this we need to assign each enemy a point value, we don’t want those tougher enemies to be worth the same as the weak ones.
To do this, we are going to store a point value in the Character class. The player character won’t use it and that is just fine. We will also make a method for increasing the points earned. Then whenever an enemy with 0 health is destroyed we will give the player that many points. The reason we are checking the health is that soon we will be removing enemies that fly off the screen to keep game performance up.
Import fantomX | |
Import projectile_type | |
Class Character | |
Field box:ftObject | |
Field current_health:Int | |
Field max_health:Int | |
Field is_player:Bool | |
Field projectile_type:ProjectileType | |
Field last_projectile_time:Float | |
' Give characters a point value and points earned field | |
Field point_value:Int | |
Field points_earned:Int | |
' Add the point value in the constructor | |
Method New(box:ftObject, projectile_type:ProjectileType, point_value:Int=5, health:Int=1, player:Bool=False) | |
Self.box = box | |
Self.current_health = health | |
Self.max_health = health | |
Self.is_player = player | |
Self.projectile_type = projectile_type | |
Self.last_projectile_time = Millisecs() | |
' initialize the point value and points earned fields | |
Self.point_value = point_value | |
Self.points_earned = 0 | |
End | |
'.................... | |
' Simple method for increasing the score | |
Method IncreaseScore(points:Int=0) | |
Self.points_earned += points | |
End | |
End |
Now we need to modify the Enemy and player creation code to include point values in the constructors.
' Game Class | |
Method OnCreate() | |
Self.engine = New CustomEngine | |
default_scene = engine.GetDefaultScene() | |
default_layer = engine.GetDefaultLayer() | |
Local box:ftObject = Self.engine.CreateBox(120, 20, engine.GetCanvasWidth()/2, engine.GetCanvasHeight()/2) | |
box.SetColor(0, 70, 70) | |
box.SetMaxSpeed(20.0) | |
box.SetMinSpeed(-20.0) | |
box.SetColGroup(PLAYER_GROUP) | |
box.SetColType(Self.engine.ctBox) | |
box.SetText("PLAYER") | |
Local projectile_type:ProjectileType = New ProjectileType() | |
' Make the player worth 0 points | |
Self.player = New Character(box, projectile_type, 0, 3, True) | |
Self.enemies = New List<Character>() | |
Self.last_enemy_time = Millisecs() | |
Self.next_enemy_interval = 3000 | |
Self.projectiles = New List<Projectile>() | |
Seed = Millisecs() | |
End | |
'....................... | |
Method CreateEnemy() | |
Local rand_width:Float = Rnd(3, 7) * 10 | |
Local rand_height:Float = Rnd(3, 7) * 10 | |
Local rand_y:Float = Rnd(rand_height, Self.engine.GetCanvasHeight()) | |
Local box:ftObject = Self.engine.CreateBox(rand_width, rand_height, Self.engine.GetCanvasWidth(), rand_y) | |
Local projectile_type:ProjectileType = New ProjectileType() | |
Local rand_color:Float = Rnd(0, 3) | |
Local health = 1 | |
' make tougher enemies worth more points | |
Local points = 10 | |
Local colors:Int[] = [0, 0, 0] | |
If (rand_color <= 1.0) | |
colors = [255, 0, 0] | |
Else If (rand_color > 1.0 And rand_color <= 2.0) | |
colors = [0, 255, 0] | |
health = 2 | |
points = 25 | |
Else | |
colors = [0, 0, 255] | |
health = 3 | |
points = 45 | |
End | |
box.SetColor(colors[0], colors[1], colors[2]) | |
box.SetSpeedX(Rnd(10, 30) * -1) | |
box.SetColGroup(ENEMY_GROUP) | |
box.SetColWith(PLAYER_GROUP, True) | |
box.SetText("ENEMY") | |
' add the points in the constructor | |
enemies.AddLast(New Character(box, projectile_type, points, health)) | |
Self.next_enemy_interval = Rnd(0, 3) * 1000 | |
End |
Now lets add a check in OnUpdate when we destroy an Enemy to increase the player’s score if the enemy’s health is 0 or less.
' Game Class | |
Method OnUpdate() | |
Local time_delta:Float = Float(engine.CalcDeltaTime())/60.0 | |
If ((Millisecs() - Self.last_enemy_time) > Self.next_enemy_interval) | |
CreateEnemy() | |
Self.last_enemy_time = Millisecs() | |
End | |
player.Update(engine.GetCanvasWidth(), engine.GetCanvasHeight()) | |
If (KeyDown(KEY_SPACE) And player.FireProjectile()) | |
CreateProjectile(player) | |
End | |
For Local projectile:Projectile = Eachin Self.projectiles | |
projectile.Update() | |
If (projectile.box.GetText() = "DESTROYED") | |
projectile.box.Remove() | |
Self.projectiles.RemoveFirst(projectile) | |
End | |
End | |
For Local enemy:Character = Eachin Self.enemies | |
enemy.Update(engine.GetCanvasWidth(), engine.GetCanvasHeight()) | |
If (enemy.box.GetText() = "DESTROYED") | |
' If the player destroyed the enemy, increase the score | |
If (enemy.current_health <= 0) | |
Self.player.IncreaseScore(enemy.point_value) | |
End | |
enemy.box.Remove() | |
Self.enemies.RemoveFirst(enemy) | |
End | |
End | |
If engine.GetPaused() = False | |
engine.Update(time_delta) | |
engine.CollisionCheck() | |
End | |
End |
Now whenever we destroy and enemy, our players score will get updated. But we can’t see what the score is right now. Our next step will be to draw the player’s score on the screen. We will do that my adding a text draw to our OnRender method to draw the player’s score in the upper left hand corner of the screen.
' Game Class | |
Method OnRender() | |
engine.Clear(255, 255, 255) | |
engine.Render() | |
' Set the text color to black and draw the score on the screen | |
engine.SetColor(0, 0, 0) | |
engine.GetCanvas().DrawText("Player Score: " + Self.player.points_earned, engine.GetLocalX(10), engine.GetLocalY(10)) | |
engine.SetColor(0, 70, 70) | |
engine.RenderFlush() | |
End |
That wraps up projectiles and scoring for now. Our next step is to be able to actually end the game when the player takes too much damage and to add some other game states.