import { _decorator, CCFloat, CCInteger, Component, geometry, math, Node, Vec3, tween, Collider2D, Sprite, Contact2DType, AudioClip, Tween, } from 'cc'; import { EventManger } from '../Manager/EventManger'; import GameEvent from '../Events/GameEvent'; import ScoreType from '../Enum/ScoreType'; import GameState from '../Enum/GameState'; import { SoundManager } from '../Manager/SoundManager'; const { ccclass, property } = _decorator; @ccclass('Enemy') export class Enemy extends Component { @property({ type: Collider2D, visible: true }) private _collider: Collider2D; @property({ type: Sprite, visible: true }) private _sprite: Sprite; @property({ type: Node, visible: true }) private _patrolPoint1: Node; @property({ type: Node, visible: true }) private _patrolPoint2: Node; @property({ type: CCFloat, visible: true }) private _speed = 100; @property({ type: CCInteger, visible: true }) private _requireGoal = 1; @property({ visible: true }) private _curveY = false; @property({ type: geometry.AnimationCurve, visible: true }) private _patrolCurve: geometry.AnimationCurve = new geometry.AnimationCurve(); @property({ type: CCFloat, visible: true }) private _multiplierCurve = 100; @property({ type: AudioClip, visible: true }) private _hitSound: AudioClip; private x: number; private y: number; private _distance: number; private _processDistance = 0; private _direction = 1; private _isActive = false; private _currentGoal = 0; protected onLoad(): void { EventManger.instance.on(GameEvent.Score, this.onScore, this); EventManger.instance.on(GameEvent.GameStateChange, this.onGameStateChange, this); this._collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this); } protected start(): void { this.init(); } private async init() { this._collider.node.setWorldPosition(this._patrolPoint1.worldPosition); const pos = this._patrolPoint1.getWorldPosition(); this.x = pos.x; this.y = pos.y; this._distance = Math.abs(this._patrolPoint2.worldPosition.x - this.x); this._direction = this._patrolPoint1.worldPosition.x < this._patrolPoint2.worldPosition.x ? 1 : -1; this._processDistance = 0; this._currentGoal = 0; this._isActive = false; this._collider.setNodeActive(false); } protected update(dt: number): void { if (!this._isActive) return; if (this._curveY) { this._processDistance += this._speed * dt; this._processDistance = math.clamp(this._processDistance, 0, this._distance); const process = math.clamp01(this._processDistance / this._distance); this.x += this._speed * dt * this._direction; this.y = this._patrolPoint1.worldPosition.y + this._patrolCurve.evaluate(process) * this._multiplierCurve; } else { const dir = new Vec3(); Vec3.subtract(dir, this._patrolPoint2.worldPosition, this._patrolPoint1.worldPosition); dir.multiplyScalar(this._direction).normalize(); this._processDistance += dir.x * this._speed * dt * this._direction; this._processDistance = math.clamp(this._processDistance, 0, this._distance); this.x += dir.x * this._speed * dt; this.y += dir.y * this._speed * dt; this.y = math.clamp(this.y, this._patrolPoint1.worldPosition.y, this._patrolPoint2.worldPosition.y); } this.x = math.clamp(this.x, this._patrolPoint1.worldPosition.x, this._patrolPoint2.worldPosition.x); this._collider.node.setWorldPosition(new Vec3(this.x, this.y)); if (this._processDistance == this._distance) { this._direction *= -1; this._processDistance = 0; } } private onBeginContact(self: Collider2D, other: Collider2D) { SoundManager.instance.playSfx(this._hitSound); Tween.stopAllByTarget(this._sprite.node.scale); tween(this._sprite.node.scale) .set(Vec3.ONE) .to(0.1, new Vec3(1.2, 1.2), { easing: 'backInOut' }) .to(0.5, Vec3.ONE, { easing: 'backOut' }) .start(); } private onScore(score: number, points: number, type: ScoreType) { if (type == ScoreType.Goal) { this._currentGoal++; if (this._currentGoal == this._requireGoal) { this._sprite.node.setScale(Vec3.ZERO); this._collider.setNodeActive(true); tween(this._sprite.node.scale) .delay(1) .to(0.5, Vec3.ONE, { easing: 'backOut', onComplete: () => { this._isActive = true; }, }) .start(); } } } private onGameStateChange(state: GameState) { switch (state) { case GameState.Init: break; case GameState.Ready: this.init(); break; case GameState.Playing: break; case GameState.GameOver: break; case GameState.Relive: break; case GameState.End: break; } } }