diff --git a/assets/Scripts/Game/Unit/Enemy/CircularEnemySpawner.ts b/assets/Scripts/Game/Unit/Enemy/CircularEnemySpawner.ts index d031a79..8308786 100644 --- a/assets/Scripts/Game/Unit/Enemy/CircularEnemySpawner.ts +++ b/assets/Scripts/Game/Unit/Enemy/CircularEnemySpawner.ts @@ -1,5 +1,6 @@ import { GameTimer } from "../../../Services/GameTimer"; import { roundToOneDecimal } from "../../../Services/Utils/MathUtils"; +import { EnemyMovementType } from "./EnemyMovementType"; import { EnemySpawner } from "./EnemySpawner"; import { EnemyType } from "./EnemyType"; @@ -15,7 +16,7 @@ export class CircularEnemySpawner { for (let i = 0; i < this.enemiesToSpawn; i++) { const posX: number = roundToOneDecimal(Math.sin(angle * i)) * 500; const posY: number = roundToOneDecimal(Math.cos(angle * i)) * 500; - this.enemySpawner.spawnNewEnemy(posX, posY); + this.enemySpawner.spawnNewEnemy(posX, posY, EnemyMovementType.Follow); } } } diff --git a/assets/Scripts/Game/Unit/Enemy/Enemy.ts b/assets/Scripts/Game/Unit/Enemy/Enemy.ts index c8c26fe..c4816d0 100644 --- a/assets/Scripts/Game/Unit/Enemy/Enemy.ts +++ b/assets/Scripts/Game/Unit/Enemy/Enemy.ts @@ -2,6 +2,7 @@ import { BoxCollider2D, Component, randomRange, Vec3, _decorator } from "cc"; import { ISignal } from "../../../Services/EventSystem/ISignal"; import { Signal } from "../../../Services/EventSystem/Signal"; import { UnitHealth } from "../UnitHealth"; +import { EnemyMovementType } from "./EnemyMovementType"; const { ccclass, property } = _decorator; @@ -9,17 +10,23 @@ const { ccclass, property } = _decorator; export class Enemy extends Component { @property(BoxCollider2D) public collider: BoxCollider2D; + private movementType: EnemyMovementType; private health: UnitHealth = new UnitHealth(1); private deathEvent: Signal = new Signal(); private speed: number; - public setup(position: Vec3): void { + public setup(position: Vec3, movementType: EnemyMovementType): void { + this.movementType = movementType; this.health = new UnitHealth(1); this.speed = randomRange(40, 90); this.node.setWorldPosition(position); this.node.active = true; } + public get MovementType(): EnemyMovementType { + return this.movementType; + } + public get Collider(): BoxCollider2D { 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; - newPosition.x += move.x * this.speed; - newPosition.y += move.y * this.speed; + newPosition.x += move.x * this.speed * deltaTime; + newPosition.y += move.y * this.speed * deltaTime; this.node.setWorldPosition(newPosition); } diff --git a/assets/Scripts/Game/Unit/Enemy/EnemyManager.ts b/assets/Scripts/Game/Unit/Enemy/EnemyManager.ts index 7ce8713..b16f31d 100644 --- a/assets/Scripts/Game/Unit/Enemy/EnemyManager.ts +++ b/assets/Scripts/Game/Unit/Enemy/EnemyManager.ts @@ -2,10 +2,14 @@ import { Component, Node, _decorator } from "cc"; import { XPSpawner } from "../../XP/XPSpawner"; import { CircularEnemySpawner } from "./CircularEnemySpawner"; import { Enemy } from "./Enemy"; +import { EnemyMovementType } from "./EnemyMovementType"; import { EnemyMover } from "./EnemyMover"; import { EnemySpawner } from "./EnemySpawner"; import { EnemyType } from "./EnemyType"; +import { FollowTargetEnemyMover } from "./FollowTargetEnemyMover"; import { InvididualEnemySpawner as IndividualEnemySpawner } from "./InvididualEnemySpawner"; +import { LaunchToTargetEnemyMover } from "./LaunchToTargetEnemyMover"; +import { WaveEnemySpawner } from "./WaveEnemySpawner"; const { ccclass, property } = _decorator; @ccclass("EnemyManager") @@ -13,33 +17,40 @@ export class EnemyManager extends Component { @property(EnemySpawner) private enemySpawner: EnemySpawner; @property(XPSpawner) private xpSpawner: XPSpawner; - private enemyMover: EnemyMover; + private movementTypeToMover: Map = new Map(); private individualEnemySpawner: IndividualEnemySpawner; private circularEnemySpawner: CircularEnemySpawner; + private waveEnemySpawner: WaveEnemySpawner; public init(targetNode: Node): void { - this.enemyMover = new EnemyMover(targetNode); - this.enemySpawner.init(targetNode); this.enemySpawner.EnemyAddedEvent.on(this.onEnemyAdded, this); this.enemySpawner.enemyRemovedEvent.on(this.onRemoveEnemy, this); 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(); } public gameTick(deltaTime: number): void { - this.individualEnemySpawner.gameTick(deltaTime); - this.circularEnemySpawner.gameTick(deltaTime); - this.enemyMover.gameTick(deltaTime); + //this.individualEnemySpawner.gameTick(deltaTime); + //this.circularEnemySpawner.gameTick(deltaTime); + this.waveEnemySpawner.gameTick(deltaTime); + + for (const kvp of this.movementTypeToMover) { + kvp[1].gameTick(deltaTime); + } } private onEnemyAdded(enemy: Enemy): void { enemy.DeathEvent.on(this.onEnemyDied, this); - this.enemyMover.addEnemy(enemy); + this.getEnemyMover(enemy).addEnemy(enemy); } private onEnemyDied(enemy: Enemy): void { @@ -48,6 +59,14 @@ export class EnemyManager extends Component { } 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); } } diff --git a/assets/Scripts/Game/Unit/Enemy/EnemyMovementType.ts b/assets/Scripts/Game/Unit/Enemy/EnemyMovementType.ts new file mode 100644 index 0000000..e1e244b --- /dev/null +++ b/assets/Scripts/Game/Unit/Enemy/EnemyMovementType.ts @@ -0,0 +1,4 @@ +export enum EnemyMovementType { + Follow, + Launch +} diff --git a/assets/Scripts/Game/Unit/Enemy/EnemyMovementType.ts.meta b/assets/Scripts/Game/Unit/Enemy/EnemyMovementType.ts.meta new file mode 100644 index 0000000..d10453e --- /dev/null +++ b/assets/Scripts/Game/Unit/Enemy/EnemyMovementType.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "a3031fa5-181d-4873-b4e7-86f5a3b5c433", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/Scripts/Game/Unit/Enemy/EnemyMover.ts b/assets/Scripts/Game/Unit/Enemy/EnemyMover.ts index c24f6ff..6cb5eb0 100644 --- a/assets/Scripts/Game/Unit/Enemy/EnemyMover.ts +++ b/assets/Scripts/Game/Unit/Enemy/EnemyMover.ts @@ -1,9 +1,10 @@ -import { Node, Vec3 } from "cc"; +import { Node } from "cc"; import { Enemy } from "./Enemy"; -export class EnemyMover { - private targetNode: Node; - private enemies: Enemy[] = []; +export abstract class EnemyMover { + protected targetNode: Node; + protected enemies: Enemy[] = []; + public constructor(targetNode: Node) { this.targetNode = targetNode; } @@ -18,11 +19,5 @@ export class 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().multiplyScalar(deltaTime)); - }); - } + public abstract gameTick(deltaTime: number): void; } diff --git a/assets/Scripts/Game/Unit/Enemy/EnemySpawner.ts b/assets/Scripts/Game/Unit/Enemy/EnemySpawner.ts index 7c51db7..7445087 100644 --- a/assets/Scripts/Game/Unit/Enemy/EnemySpawner.ts +++ b/assets/Scripts/Game/Unit/Enemy/EnemySpawner.ts @@ -4,6 +4,7 @@ import { Signal } from "../../../Services/EventSystem/Signal"; import { ObjectPool } from "../../../Services/ObjectPool"; import { Enemy } from "./Enemy"; +import { EnemyMovementType } from "./EnemyMovementType"; const { ccclass, property } = _decorator; @ccclass("EnemySpawner") @@ -30,20 +31,22 @@ export class EnemySpawner extends Component { 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 spawnPosition = new Vec3(); spawnPosition.x = this.targetNode.worldPosition.x + positionX; 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); + + return enemy; } - private returnEnemyToPool(enemy: Enemy): void { - enemy.DeathEvent.off(this.returnEnemyToPool); + public returnEnemy(enemy: Enemy): void { + enemy.DeathEvent.off(this.returnEnemy); this.enemyPool.return(enemy); this.enemyRemovedEvent.trigger(enemy); diff --git a/assets/Scripts/Game/Unit/Enemy/FollowTargetEnemyMover.ts b/assets/Scripts/Game/Unit/Enemy/FollowTargetEnemyMover.ts new file mode 100644 index 0000000..11e6038 --- /dev/null +++ b/assets/Scripts/Game/Unit/Enemy/FollowTargetEnemyMover.ts @@ -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); + }); + } +} diff --git a/assets/Scripts/Game/Unit/Enemy/FollowTargetEnemyMover.ts.meta b/assets/Scripts/Game/Unit/Enemy/FollowTargetEnemyMover.ts.meta new file mode 100644 index 0000000..c142c5c --- /dev/null +++ b/assets/Scripts/Game/Unit/Enemy/FollowTargetEnemyMover.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "20a3f56b-402d-4639-9629-c90f70f55206", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/Scripts/Game/Unit/Enemy/InvididualEnemySpawner.ts b/assets/Scripts/Game/Unit/Enemy/InvididualEnemySpawner.ts index a53dea1..c696626 100644 --- a/assets/Scripts/Game/Unit/Enemy/InvididualEnemySpawner.ts +++ b/assets/Scripts/Game/Unit/Enemy/InvididualEnemySpawner.ts @@ -1,6 +1,7 @@ import { randomRange } from "cc"; import { GameTimer } from "../../../Services/GameTimer"; import { randomPositiveOrNegative } from "../../../Services/Utils/MathUtils"; +import { EnemyMovementType } from "./EnemyMovementType"; import { EnemySpawner } from "./EnemySpawner"; import { EnemyType } from "./EnemyType"; @@ -12,7 +13,7 @@ export class InvididualEnemySpawner { if (this.spawnTimer.tryFinishPeriod()) { const posX: number = randomRange(300, 600) * randomPositiveOrNegative(); const posY: number = randomRange(300, 600) * randomPositiveOrNegative(); - this.enemySpawner.spawnNewEnemy(posX, posY); + this.enemySpawner.spawnNewEnemy(posX, posY, EnemyMovementType.Launch); } } } diff --git a/assets/Scripts/Game/Unit/Enemy/LaunchToTargetEnemyMover.ts b/assets/Scripts/Game/Unit/Enemy/LaunchToTargetEnemyMover.ts new file mode 100644 index 0000000..58d7b84 --- /dev/null +++ b/assets/Scripts/Game/Unit/Enemy/LaunchToTargetEnemyMover.ts @@ -0,0 +1,36 @@ +import { Vec3 } from "cc"; +import { Enemy } from "./Enemy"; +import { EnemyMover } from "./EnemyMover"; + +export class LaunchToTargetEnemyMover extends EnemyMover { + private enemyToDirection: Map = new Map(); + 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); + } + } +} diff --git a/assets/Scripts/Game/Unit/Enemy/LaunchToTargetEnemyMover.ts.meta b/assets/Scripts/Game/Unit/Enemy/LaunchToTargetEnemyMover.ts.meta new file mode 100644 index 0000000..61b6953 --- /dev/null +++ b/assets/Scripts/Game/Unit/Enemy/LaunchToTargetEnemyMover.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "5215f6f3-855e-4d8e-afad-80915ca7b2f2", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/Scripts/Game/Unit/Enemy/WaveEnemySpawner.ts b/assets/Scripts/Game/Unit/Enemy/WaveEnemySpawner.ts new file mode 100644 index 0000000..62bc012 --- /dev/null +++ b/assets/Scripts/Game/Unit/Enemy/WaveEnemySpawner.ts @@ -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); + } + } + } +} diff --git a/assets/Scripts/Game/Unit/Enemy/WaveEnemySpawner.ts.meta b/assets/Scripts/Game/Unit/Enemy/WaveEnemySpawner.ts.meta new file mode 100644 index 0000000..aef5dfe --- /dev/null +++ b/assets/Scripts/Game/Unit/Enemy/WaveEnemySpawner.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "d4d27dc6-0934-46d2-b657-6c1eb87f6824", + "files": [], + "subMetas": {}, + "userData": {} +}