This commit is contained in:
Martin 2022-12-05 15:13:42 +01:00
parent 8aa7ee6f2e
commit d109519190
14 changed files with 172 additions and 31 deletions

View File

@ -1,5 +1,6 @@
import { GameTimer } from "../../../Services/GameTimer"; import { GameTimer } from "../../../Services/GameTimer";
import { roundToOneDecimal } from "../../../Services/Utils/MathUtils"; import { roundToOneDecimal } from "../../../Services/Utils/MathUtils";
import { EnemyMovementType } from "./EnemyMovementType";
import { EnemySpawner } from "./EnemySpawner"; import { EnemySpawner } from "./EnemySpawner";
import { EnemyType } from "./EnemyType"; import { EnemyType } from "./EnemyType";
@ -15,7 +16,7 @@ export class CircularEnemySpawner {
for (let i = 0; i < this.enemiesToSpawn; i++) { for (let i = 0; i < this.enemiesToSpawn; i++) {
const posX: number = roundToOneDecimal(Math.sin(angle * i)) * 500; const posX: number = roundToOneDecimal(Math.sin(angle * i)) * 500;
const posY: number = roundToOneDecimal(Math.cos(angle * i)) * 500; const posY: number = roundToOneDecimal(Math.cos(angle * i)) * 500;
this.enemySpawner.spawnNewEnemy(posX, posY); this.enemySpawner.spawnNewEnemy(posX, posY, EnemyMovementType.Follow);
} }
} }
} }

View File

@ -2,6 +2,7 @@ import { BoxCollider2D, Component, randomRange, Vec3, _decorator } from "cc";
import { ISignal } from "../../../Services/EventSystem/ISignal"; import { ISignal } from "../../../Services/EventSystem/ISignal";
import { Signal } from "../../../Services/EventSystem/Signal"; import { Signal } from "../../../Services/EventSystem/Signal";
import { UnitHealth } from "../UnitHealth"; import { UnitHealth } from "../UnitHealth";
import { EnemyMovementType } from "./EnemyMovementType";
const { ccclass, property } = _decorator; const { ccclass, property } = _decorator;
@ -9,17 +10,23 @@ const { ccclass, property } = _decorator;
export class Enemy extends Component { export class Enemy extends Component {
@property(BoxCollider2D) public collider: BoxCollider2D; @property(BoxCollider2D) public collider: BoxCollider2D;
private movementType: EnemyMovementType;
private health: UnitHealth = new UnitHealth(1); private health: UnitHealth = new UnitHealth(1);
private deathEvent: Signal<Enemy> = new Signal<Enemy>(); private deathEvent: Signal<Enemy> = new Signal<Enemy>();
private speed: number; private speed: number;
public setup(position: Vec3): void { public setup(position: Vec3, movementType: EnemyMovementType): void {
this.movementType = movementType;
this.health = new UnitHealth(1); this.health = new UnitHealth(1);
this.speed = randomRange(40, 90); this.speed = randomRange(40, 90);
this.node.setWorldPosition(position); this.node.setWorldPosition(position);
this.node.active = true; this.node.active = true;
} }
public get MovementType(): EnemyMovementType {
return this.movementType;
}
public get Collider(): BoxCollider2D { public get Collider(): BoxCollider2D {
return this.collider; return this.collider;
} }
@ -43,10 +50,10 @@ export class Enemy extends Component {
} }
} }
public moveBy(move: Vec3): void { public moveBy(move: Vec3, deltaTime: number): void {
const newPosition: Vec3 = this.node.worldPosition; const newPosition: Vec3 = this.node.worldPosition;
newPosition.x += move.x * this.speed; newPosition.x += move.x * this.speed * deltaTime;
newPosition.y += move.y * this.speed; newPosition.y += move.y * this.speed * deltaTime;
this.node.setWorldPosition(newPosition); this.node.setWorldPosition(newPosition);
} }

View File

@ -2,10 +2,14 @@ import { Component, Node, _decorator } from "cc";
import { XPSpawner } from "../../XP/XPSpawner"; import { XPSpawner } from "../../XP/XPSpawner";
import { CircularEnemySpawner } from "./CircularEnemySpawner"; import { CircularEnemySpawner } from "./CircularEnemySpawner";
import { Enemy } from "./Enemy"; import { Enemy } from "./Enemy";
import { EnemyMovementType } from "./EnemyMovementType";
import { EnemyMover } from "./EnemyMover"; import { EnemyMover } from "./EnemyMover";
import { EnemySpawner } from "./EnemySpawner"; import { EnemySpawner } from "./EnemySpawner";
import { EnemyType } from "./EnemyType"; import { EnemyType } from "./EnemyType";
import { FollowTargetEnemyMover } from "./FollowTargetEnemyMover";
import { InvididualEnemySpawner as IndividualEnemySpawner } from "./InvididualEnemySpawner"; import { InvididualEnemySpawner as IndividualEnemySpawner } from "./InvididualEnemySpawner";
import { LaunchToTargetEnemyMover } from "./LaunchToTargetEnemyMover";
import { WaveEnemySpawner } from "./WaveEnemySpawner";
const { ccclass, property } = _decorator; const { ccclass, property } = _decorator;
@ccclass("EnemyManager") @ccclass("EnemyManager")
@ -13,33 +17,40 @@ export class EnemyManager extends Component {
@property(EnemySpawner) private enemySpawner: EnemySpawner; @property(EnemySpawner) private enemySpawner: EnemySpawner;
@property(XPSpawner) private xpSpawner: XPSpawner; @property(XPSpawner) private xpSpawner: XPSpawner;
private enemyMover: EnemyMover; private movementTypeToMover: Map<EnemyMovementType, EnemyMover> = new Map<EnemyMovementType, EnemyMover>();
private individualEnemySpawner: IndividualEnemySpawner; private individualEnemySpawner: IndividualEnemySpawner;
private circularEnemySpawner: CircularEnemySpawner; private circularEnemySpawner: CircularEnemySpawner;
private waveEnemySpawner: WaveEnemySpawner;
public init(targetNode: Node): void { public init(targetNode: Node): void {
this.enemyMover = new EnemyMover(targetNode);
this.enemySpawner.init(targetNode); this.enemySpawner.init(targetNode);
this.enemySpawner.EnemyAddedEvent.on(this.onEnemyAdded, this); this.enemySpawner.EnemyAddedEvent.on(this.onEnemyAdded, this);
this.enemySpawner.enemyRemovedEvent.on(this.onRemoveEnemy, this); this.enemySpawner.enemyRemovedEvent.on(this.onRemoveEnemy, this);
this.individualEnemySpawner = new IndividualEnemySpawner(this.enemySpawner, EnemyType.Basic); this.individualEnemySpawner = new IndividualEnemySpawner(this.enemySpawner, EnemyType.Basic);
this.circularEnemySpawner = new CircularEnemySpawner(this.enemySpawner, 20, EnemyType.Basic); this.circularEnemySpawner = new CircularEnemySpawner(this.enemySpawner, 30, EnemyType.Basic);
this.waveEnemySpawner = new WaveEnemySpawner(this.enemySpawner, 30, EnemyType.Basic);
this.movementTypeToMover.set(EnemyMovementType.Follow, new FollowTargetEnemyMover(targetNode));
this.movementTypeToMover.set(EnemyMovementType.Launch, new LaunchToTargetEnemyMover(targetNode));
this.xpSpawner.init(); this.xpSpawner.init();
} }
public gameTick(deltaTime: number): void { public gameTick(deltaTime: number): void {
this.individualEnemySpawner.gameTick(deltaTime); //this.individualEnemySpawner.gameTick(deltaTime);
this.circularEnemySpawner.gameTick(deltaTime); //this.circularEnemySpawner.gameTick(deltaTime);
this.enemyMover.gameTick(deltaTime); this.waveEnemySpawner.gameTick(deltaTime);
for (const kvp of this.movementTypeToMover) {
kvp[1].gameTick(deltaTime);
}
} }
private onEnemyAdded(enemy: Enemy): void { private onEnemyAdded(enemy: Enemy): void {
enemy.DeathEvent.on(this.onEnemyDied, this); enemy.DeathEvent.on(this.onEnemyDied, this);
this.enemyMover.addEnemy(enemy); this.getEnemyMover(enemy).addEnemy(enemy);
} }
private onEnemyDied(enemy: Enemy): void { private onEnemyDied(enemy: Enemy): void {
@ -48,6 +59,14 @@ export class EnemyManager extends Component {
} }
private onRemoveEnemy(enemy: Enemy): void { private onRemoveEnemy(enemy: Enemy): void {
this.enemyMover.removeEnemy(enemy); this.getEnemyMover(enemy).removeEnemy(enemy);
}
private getEnemyMover(enemy: Enemy): EnemyMover {
if (this.movementTypeToMover.has(enemy.MovementType)) {
return this.movementTypeToMover.get(enemy.MovementType);
}
throw new Error("Does not have mover of type " + enemy.MovementType);
} }
} }

View File

@ -0,0 +1,4 @@
export enum EnemyMovementType {
Follow,
Launch
}

View File

@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "a3031fa5-181d-4873-b4e7-86f5a3b5c433",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@ -1,9 +1,10 @@
import { Node, Vec3 } from "cc"; import { Node } from "cc";
import { Enemy } from "./Enemy"; import { Enemy } from "./Enemy";
export class EnemyMover { export abstract class EnemyMover {
private targetNode: Node; protected targetNode: Node;
private enemies: Enemy[] = []; protected enemies: Enemy[] = [];
public constructor(targetNode: Node) { public constructor(targetNode: Node) {
this.targetNode = targetNode; this.targetNode = targetNode;
} }
@ -18,11 +19,5 @@ export class EnemyMover {
} }
} }
public gameTick(deltaTime: number): void { public abstract gameTick(deltaTime: number): void;
this.enemies.forEach((enemy) => {
let direction: Vec3 = new Vec3();
direction = Vec3.subtract(direction, this.targetNode.worldPosition, enemy.node.worldPosition);
enemy.moveBy(direction.normalize().multiplyScalar(deltaTime));
});
}
} }

View File

@ -4,6 +4,7 @@ import { Signal } from "../../../Services/EventSystem/Signal";
import { ObjectPool } from "../../../Services/ObjectPool"; import { ObjectPool } from "../../../Services/ObjectPool";
import { Enemy } from "./Enemy"; import { Enemy } from "./Enemy";
import { EnemyMovementType } from "./EnemyMovementType";
const { ccclass, property } = _decorator; const { ccclass, property } = _decorator;
@ccclass("EnemySpawner") @ccclass("EnemySpawner")
@ -30,20 +31,22 @@ export class EnemySpawner extends Component {
return this.enemyRemovedEvent; return this.enemyRemovedEvent;
} }
public spawnNewEnemy(positionX: number, positionY: number): void { public spawnNewEnemy(positionX: number, positionY: number, movementType: EnemyMovementType): Enemy {
const enemy = this.enemyPool.borrow(); const enemy = this.enemyPool.borrow();
const spawnPosition = new Vec3(); const spawnPosition = new Vec3();
spawnPosition.x = this.targetNode.worldPosition.x + positionX; spawnPosition.x = this.targetNode.worldPosition.x + positionX;
spawnPosition.y = this.targetNode.worldPosition.y + positionY; spawnPosition.y = this.targetNode.worldPosition.y + positionY;
enemy.setup(spawnPosition); enemy.setup(spawnPosition, movementType);
enemy.DeathEvent.on(this.returnEnemyToPool, this); enemy.DeathEvent.on(this.returnEnemy, this);
this.enemyAddedEvent.trigger(enemy); this.enemyAddedEvent.trigger(enemy);
return enemy;
} }
private returnEnemyToPool(enemy: Enemy): void { public returnEnemy(enemy: Enemy): void {
enemy.DeathEvent.off(this.returnEnemyToPool); enemy.DeathEvent.off(this.returnEnemy);
this.enemyPool.return(enemy); this.enemyPool.return(enemy);
this.enemyRemovedEvent.trigger(enemy); this.enemyRemovedEvent.trigger(enemy);

View File

@ -0,0 +1,12 @@
import { Vec3 } from "cc";
import { EnemyMover } from "./EnemyMover";
export class FollowTargetEnemyMover extends EnemyMover {
public gameTick(deltaTime: number): void {
this.enemies.forEach((enemy) => {
let direction: Vec3 = new Vec3();
direction = Vec3.subtract(direction, this.targetNode.worldPosition, enemy.node.worldPosition);
enemy.moveBy(direction.normalize(), deltaTime);
});
}
}

View File

@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "20a3f56b-402d-4639-9629-c90f70f55206",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@ -1,6 +1,7 @@
import { randomRange } from "cc"; import { randomRange } from "cc";
import { GameTimer } from "../../../Services/GameTimer"; import { GameTimer } from "../../../Services/GameTimer";
import { randomPositiveOrNegative } from "../../../Services/Utils/MathUtils"; import { randomPositiveOrNegative } from "../../../Services/Utils/MathUtils";
import { EnemyMovementType } from "./EnemyMovementType";
import { EnemySpawner } from "./EnemySpawner"; import { EnemySpawner } from "./EnemySpawner";
import { EnemyType } from "./EnemyType"; import { EnemyType } from "./EnemyType";
@ -12,7 +13,7 @@ export class InvididualEnemySpawner {
if (this.spawnTimer.tryFinishPeriod()) { if (this.spawnTimer.tryFinishPeriod()) {
const posX: number = randomRange(300, 600) * randomPositiveOrNegative(); const posX: number = randomRange(300, 600) * randomPositiveOrNegative();
const posY: number = randomRange(300, 600) * randomPositiveOrNegative(); const posY: number = randomRange(300, 600) * randomPositiveOrNegative();
this.enemySpawner.spawnNewEnemy(posX, posY); this.enemySpawner.spawnNewEnemy(posX, posY, EnemyMovementType.Launch);
} }
} }
} }

View File

@ -0,0 +1,36 @@
import { Vec3 } from "cc";
import { Enemy } from "./Enemy";
import { EnemyMover } from "./EnemyMover";
export class LaunchToTargetEnemyMover extends EnemyMover {
private enemyToDirection: Map<Enemy, Vec3> = new Map<Enemy, Vec3>();
private lastTargetPosition: Vec3 = new Vec3();
private lastDirection: Vec3 = new Vec3();
public addEnemy(enemy: Enemy): void {
let direction: Vec3 = new Vec3();
// if the enemy is added soon enough, move as a single group towards one direction
if (Vec3.distance(this.lastTargetPosition, this.targetNode.worldPosition) < 10) {
direction = this.lastDirection;
} else {
direction = Vec3.subtract(direction, this.targetNode.worldPosition, enemy.node.worldPosition);
this.lastDirection = direction;
this.lastTargetPosition = this.targetNode.worldPosition.clone();
}
this.enemyToDirection.set(enemy, direction.normalize());
super.addEnemy(enemy);
}
public removeEnemy(enemy: Enemy): void {
this.enemyToDirection.delete(enemy);
super.removeEnemy(enemy);
}
public gameTick(deltaTime: number): void {
for (const enemyAndDirection of this.enemyToDirection) {
enemyAndDirection[0].moveBy(enemyAndDirection[1], deltaTime);
}
}
}

View File

@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "5215f6f3-855e-4d8e-afad-80915ca7b2f2",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@ -0,0 +1,27 @@
import { GameTimer } from "../../../Services/GameTimer";
import { randomPositiveOrNegative } from "../../../Services/Utils/MathUtils";
import { EnemyMovementType } from "./EnemyMovementType";
import { EnemySpawner } from "./EnemySpawner";
import { EnemyType } from "./EnemyType";
export class WaveEnemySpawner {
private spawnTimer: GameTimer = new GameTimer(5);
public constructor(private enemySpawner: EnemySpawner, private enemiesToSpawn: number, private enemyType: EnemyType) {}
public gameTick(deltaTime: number): void {
this.spawnTimer.gameTick(deltaTime);
if (this.spawnTimer.tryFinishPeriod()) {
const angle: number = (2 * Math.PI) / this.enemiesToSpawn;
const defaultPosX: number = 200 * randomPositiveOrNegative();
const defaultPosY: number = 200 * randomPositiveOrNegative();
for (let i = 0; i < this.enemiesToSpawn; i++) {
const posX: number = defaultPosX + 10 * i;
const posY: number = defaultPosY + 10 * (i % 2);
this.enemySpawner.spawnNewEnemy(posX, posY, EnemyMovementType.Launch);
}
}
}
}

View File

@ -0,0 +1,9 @@
{
"ver": "4.0.23",
"importer": "typescript",
"imported": true,
"uuid": "d4d27dc6-0934-46d2-b657-6c1eb87f6824",
"files": [],
"subMetas": {},
"userData": {}
}