Part Three: What About Enemies - SanCasia/sancasia_zero-base GitHub Wiki
Part Three: What About Enemies?
In this next step we will create a simple artificial opposing player. Three new classes and a couple of small changes to the Battlefield
will be needed to achieve this.
As before we will implement a factory to handle the creation of our enemies. The EnemyFactory
is very similar to the above shown PlayerFactory
and is thus emitted. You can find its source at demo/hello_world/part3/enemies/enemy_factory.ts
.
Second a system to control the spawning is needed. It will extend SystemBase
and override the process
function. The function will then - based on some arbitrary logic - decide how and where the enemies should spawn.
export class EnemySpawnSystem extends sczCore.SystemBase
{
private game: sczCore.Game;
private sceneId: number;
private enemyFactory: EnemyFactory;
private lanes: Array<{position: number, cooldown: number}>;
constructor(
game: sczCore.Game,
sceneId: number,
enemyFactory: EnemyFactory)
{
super(
// define what we expect enemies to consist of
[TranslateComponent],
game.getEventBus(),
// define when this system should be executed
sczCore.EngineEvent.PreComputation);
this.game = game;
this.sceneId = sceneId;
this.enemyFactory = enemyFactory;
// define possible lanes
this.lanes = [
{position: 50, cooldown: 0},
{position: 150, cooldown: 0},
{position: 250, cooldown: 0},
{position: 350, cooldown: 0}];
}
public process = (deltaTime: number): void =>
{
if(this.entities.size < 10)
{
// spawn chance: in avarage once a second
if(Math.random() * 1000 < deltaTime)
{
// spawn enemy
this.spawn();
}
}
// continue processing registered entities
for(let entity of this.entities.values())
{
let cache = <[TranslateComponent]> entity.getCache(this);
this.processEntity(deltaTime, cache, entity.getId());
entity.updateCache(this, cache);
};
}
protected processEntity(
_: number,
[translate]: [TranslateComponent],
entityId?: number): void
{
// check if out of view
if(translate.position.y > 900)
{
// despawn enemy
this.despawn(entityId);
}
}
}
The spawning logic is implemented to choose a random lane as well as to respect a certain "safety distance".
...
// spawns an enemy in a random lane
private spawn(): void
{
// lane selection
let laneNumber = Math.round(Math.random() * 3);
let lane = this.lanes[laneNumber];
// cooldown: max one spawn per lane per second
let now = new Date().getTime()
let delta = now - lane.cooldown;
if(delta > 1000)
{
// reset cooldwon counter
lane.cooldown = now;
// create enemy using a dedicated factory
let id = Math.floor(Math.random() * 10**12);
let enemy = this.enemyFactory.create(
id, {x: lane.position, y: -100});
// add the enemy to the game
this.game.addEntity(enemy);
// register enemy to the appropriate systems
this.game.registerEntity(
this.sceneId,
EnemySpawnSystem,
enemy.getId());
this.game.registerEntity(
this.sceneId,
EnemyMovementSystem,
enemy.getId());
this.game.registerEntity(
this.sceneId,
CanvasRenderSystem,
enemy.getId());
}
}
// despawns the enemy
private despawn(entityId: number): void
{
// deregister enemy from its systems
this.game.deregisterEntity(
this.sceneId,
EnemySpawnSystem,
entityId);
this.game.deregisterEntity(
this.sceneId,
EnemyMovementSystem,
entityId);
this.game.deregisterEntity(
this.sceneId,
CanvasRenderSystem,
entityId);
// remove enemy from the game
this.game.removeEntity(entityId);
}
}
It is considered best practice to implement one systems per responsibility, each dedicated to their single and preferably simple task.
Following this practice a system concerned with the enemies movement is needed.
export class EnemyMovementSystem extends sczCore.SystemBase
{
constructor(eventBus: sczCore.EventBus)
{
super(
[TranslateComponent],
eventBus,
sczCore.EngineEvent.Computation);
}
protected processEntity(
deltaTime: number,
[translate]: [TranslateComponent]): void
{
// move enemy by 100 pixel per second
translate.positionY += deltaTime * 0.1;
}
}
Finally the scene has to be updated to reflect these changes.
...
// add the render system to the scene
this.addSystem(renderSystem);
// create the enemy movement system and add it to the scene
let enemyMovementSystem = new EnemyMovementSystem(game.getEventBus());
this.addSystem(enemyMovementSystem);
// create the enemy factory
let enemyFactory = new EnemyFactory(
"enemies/enemy.svg",
{x: 200, y:200});
// create the enemy spawn system
// which is responsible for spawning enemies
let enemySpawnSystem = new EnemySpawnSystem(
game,
this.getId(),
enemyFactory);
this.addSystem(enemySpawnSystem);
// create the player factory
...
With these few lines of code inserted into the Battlefield
class we have successfully implemented everything we wanted in this step and are ready to test the progress.