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.

This page contains a Tutorial. Tutorials are designed to walk you through the development of a small game.
Warning: You must have completed the previous tutorial(s) to go through this tutorial

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.

  1. Go to the next step if the enemy is alive
  2. Go to the next step if the enemy should be in the screen (compare the game timer and the enemy's time)
  3. Move the enemy ship
  4. Kill the enemy and skip the rest of the steps if it is not on the screen
  5. Draw the enemy
  6. 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.

Example bullet pattern

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.

List Of Tutorials

  1. Introduction
  2. Player Ship
  3. Background
  4. Bullet pt.1
  5. Bullet pt.2
  6. Music and SoundEffect
  7. Enemies pt.1
  8. Enemies pt.2
  9. Collision Detection
  10. Further Extensions