UNPKG

@hiddentao/clockwork-engine

Version:

A TypeScript/PIXI.js game engine for deterministic, replayable games with built-in rendering

212 lines (211 loc) 6.63 kB
import { EventEmitter } from "./EventEmitter"; import { Vector2D } from "./geometry/Vector2D"; export var GameObjectEventType; (function (GameObjectEventType) { GameObjectEventType["POSITION_CHANGED"] = "positionChanged"; GameObjectEventType["HEALTH_CHANGED"] = "healthChanged"; GameObjectEventType["MAX_HEALTH_CHANGED"] = "maxHealthChanged"; GameObjectEventType["DESTROYED"] = "destroyed"; GameObjectEventType["SIZE_CHANGED"] = "sizeChanged"; GameObjectEventType["VELOCITY_CHANGED"] = "velocityChanged"; GameObjectEventType["ROTATION_CHANGED"] = "rotationChanged"; })(GameObjectEventType || (GameObjectEventType = {})); export class GameObject extends EventEmitter { constructor(id, position, size, health = 0, engine) { super(); this.isRepaintNeeded = true; this.id = id; this.position = position; this.size = size; this.velocity = new Vector2D(0, 0); this.rotation = 0; this.health = health; this.maxHealth = health; this.destroyed = false; this.engine = engine; // Auto-register with engine if provided if (engine) { engine.registerGameObject(this); } if (GameObject.debug) { // debug: creation log } } update(deltaTicks, _totalTicks) { if (this.destroyed) { return; } // Move object based on velocity const movement = this.velocity.scale(deltaTicks); const oldPosition = this.position; this.position = this.position.add(movement); if (oldPosition.x !== this.position.x || oldPosition.y !== this.position.y) { this.isRepaintNeeded = true; } } getPosition() { return this.position; } setPosition(position) { const oldPosition = this.position; this.position = position; if (oldPosition.x !== position.x || oldPosition.y !== position.y) { this.isRepaintNeeded = true; } ; this.emit(GameObjectEventType.POSITION_CHANGED, this, oldPosition, position); } getSize() { return this.size; } setSize(size) { if (this.size.x !== size.x || this.size.y !== size.y) { this.size = size; this.isRepaintNeeded = true; } } getVelocity() { return this.velocity; } setVelocity(velocity) { if (this.velocity.x !== velocity.x || this.velocity.y !== velocity.y) { this.velocity = velocity; this.isRepaintNeeded = true; } } getRotation() { return this.rotation; } setRotation(rotation) { if (this.rotation !== rotation) { this.rotation = rotation; this.isRepaintNeeded = true; } } getHealth() { return this.health; } getMaxHealth() { return this.maxHealth; } setMaxHealth(maxHealth) { const oldMaxHealth = this.maxHealth; this.maxHealth = maxHealth; if (this.maxHealth !== oldMaxHealth) { this.isRepaintNeeded = true; this.emit(GameObjectEventType.MAX_HEALTH_CHANGED, this, oldMaxHealth, maxHealth); } } setHealth(health) { const oldHealth = this.health; this.health = Math.max(0, Math.min(this.maxHealth, health)); if (this.health !== oldHealth) { this.isRepaintNeeded = true; this.emit(GameObjectEventType.HEALTH_CHANGED, this, this.health, this.maxHealth); } if (this.health === 0) { this.destroyed = true; this.emit(GameObjectEventType.DESTROYED, this); } } takeDamage(amount) { const oldHealth = this.health; this.health = Math.max(0, this.health - amount); if (this.health !== oldHealth) { this.isRepaintNeeded = true; this.emit(GameObjectEventType.HEALTH_CHANGED, this, this.health, this.maxHealth); } if (this.health === 0) { this.destroyed = true; this.emit(GameObjectEventType.DESTROYED, this); } } heal(amount) { const oldHealth = this.health; this.health = Math.min(this.maxHealth, this.health + amount); if (this.health !== oldHealth) { this.isRepaintNeeded = true; this.emit(GameObjectEventType.HEALTH_CHANGED, this, this.health, this.maxHealth); } } isDestroyed() { return this.destroyed; } destroy() { this.destroyed = true; this.isRepaintNeeded = true; if (GameObject.debug) { // debug: destroy log } ; this.emit(GameObjectEventType.DESTROYED, this); } /** * Get the repaint flag for this game object * @returns Whether the object needs to be repainted */ get needsRepaint() { return this.isRepaintNeeded; } /** * Set the repaint flag for this game object * @param value Whether the object needs to be repainted */ set needsRepaint(value) { this.isRepaintNeeded = value; } /** * Get the unique identifier for this game object * @returns The unique ID of this game object */ getId() { return this.id; } /** * Get the engine this GameObject is registered with * @returns The game engine instance, or undefined if not registered */ getEngine() { return this.engine; } /** * Register this GameObject with an engine * @param engine The engine to register with */ registerWithEngine(engine) { this.engine = engine; engine.registerGameObject(this); } /** * Get the collision source identifier for this game object * @returns The collision source ID of this game object */ getCollisionSourceId() { return this.getId(); } serialize() { return { position: { x: this.position.x, y: this.position.y, }, size: { x: this.size.x, y: this.size.y, }, velocity: { x: this.velocity.x, y: this.velocity.y, }, rotation: this.rotation, health: this.health, maxHealth: this.maxHealth, isDestroyed: this.destroyed, }; } static deserialize(_data) { throw new Error("GameObject.deserialize must be implemented by subclasses"); } } GameObject.debug = false;