Empty Your Mind Tutorial 8
From SwinGame
This tutorial will teach you how to use the SwinGameSDK to develop a simple danmaku game which looks cool. At the end of this tutorial you should be able to use pascal to implement your own space invaders or scrolling shooter style game which makes use of Vectors, animated Sprites, SoundEffects, and Music.
Contents |
Enemy Scheduler
We will need a procedure that will create the enemies. In this tutorial, the enemies' data will be hardcoded. The following procedure should be called at the start of the LoadGame procedure. The procedure will create three enemies. You can add more enemies if you wish.
procedure LoadEnemies(var game : GameData); const ENEMIES = 3; begin SetLength(game.enemies, ENEMIES); game.enemies[0] := CreateEnemy(0, 15, LeftToRight); game.enemies[1] := CreateEnemy(200, 650, RightToLeft); game.enemies[2] := CreateEnemy(100, 1200, LeftToRight); end;
The second parameter for the CreateEnemy function is a time that the enemy enters the screen. This will allow us to schedule the time that the enemies enter. We will need to have a game timer in order to get the enemy scheduler to work. Therefore, we will need to add a new entry to the GameData structure which will be incremented every frame. The new implementation of the GameData will be…
GameData = record player : ShipData; images : Array of Sprite; bullets : Array of BulletData; sounds : Array of SoundEffect; enemies : Array of ShipData; gameTimer : Integer; music : Music; end;
The game timer must be initialised when loading the game.
We have loaded the enemies but we do not have a routine to update the enemy position. The following steps must be taken to update the enemies. These steps must be taken for all enemies.
- Go to the next step if the enemy is alive
- Go to the next step if the enemy should be in the screen (compare the game timer and the enemy's time)
- Move the enemy ship
- Kill the enemy and skip the rest of the steps if it is not on the screen
- Draw the enemy
- Update the sprite animation frame
The implementation of the steps:
procedure UpdateEnemies(var game : GameData); var i : Integer; begin for i := 0 to High(game.enemies) do begin if (game.enemies[i].time <= game.gameTimer) and game.enemies[i].alive then begin game.enemies[i].theSprite.xPos := game.enemies[i].theSprite.xPos + game.enemies[i].speed; if IsSpriteOffscreen(game.enemies[i].theSprite) then begin game.enemies[i].alive := false; continue; end; DrawSprite(game.enemies[i].theSprite); UpdateSpriteAnimation(game.enemies[i].theSprite); end; end; end;
As you can see from the implementation, the ship can only move horizontally at the moment. This routine should be called after updating the player's ship.
Enemy Bullets
The implementation of the enemy bullet pattern is very easy. We will be reusing the code from the ShootPlayerBullet procedure. The difference between the ShootPlayerBullet routine and the ShootEnemyBullet is the resultant bullet pattern. The implementation of the ShootEnemyBullet routine should produce a simple bullet pattern like this.
The implementation of ShootEnemyBullet with the example bullet pattern:
procedure ShootEnemyBullet(var enemyToProcess : ShipData; var game : GameData); const BULLETSPEED = 3; DAMAGE = 1; var tempBullet : BulletData; i : Integer; begin if enemyToProcess.currentMagazine <= 0 then enemyToProcess.currentMagazine := enemyToProcess.currentMagazine - 1; if enemyToProcess.currentDelay > 0 then enemyToProcess.currentDelay := enemyToProcess.currentDelay - 1; if enemyToProcess.currentMagazine < 1 then begin if enemyToProcess.currentMagazine = -1 * enemyToProcess.reloadDelay then enemyToProcess.currentMagazine := enemyToProcess.maxMagazine; end else if enemyToProcess.currentDelay = 0 then begin enemyToProcess.currentMagazine := enemyToProcess.currentMagazine - 1; enemyToProcess.currentDelay := enemyToProcess.shootDelay; //Enemy bullet pattern for i := 0 to 18 do begin tempBullet := CreateBullet('EnemyBullet', enemyToProcess, DAMAGE, Normal, Enemy, GetVectorFromAngle(60 + 20 * i + enemyToProcess.offset, BULLETSPEED)); DeployBullet(tempBullet, game.bullets); PlaySoundEffect(game.sounds[2]); end; enemyToProcess.offset := enemyToProcess.offset + 10; end; end;
The image I have created for this example can be found here. Do not forget to initialise the image in Game Resources. My example bullet pattern will shoot 19 bullets at a time. The offset value is used to change the angle of the bullets. This will allow us to program the bullet pattern with more flexibility. This routine should be called every time you update an enemy. The new implementation of UpdateEnemies:
procedure UpdateEnemies(var game : GameData); var i : Integer; begin for i := 0 to High(game.enemies) do begin if (game.enemies[i].time <= game.gameTimer) and game.enemies[i].alive then begin game.enemies[i].theSprite.xPos := game.enemies[i].theSprite.xPos + game.enemies[i].speed; if IsSpriteOffscreen(game.enemies[i].theSprite) then begin game.enemies[i].alive := false; continue; end; ShootEnemyBullet(game.enemies[i], game); DrawSprite(game.enemies[i].theSprite); UpdateSpriteAnimation(game.enemies[i].theSprite); end; end; end;
Summary
In this tutorial, I have gone through:
- Enemy data management
- Enemy scheduling
- Enemy bullet pattern
- Simple enemy movement
The current project files can be downloaded from here.


