ShatteredSword/src/Nodes/GameNode.ts

297 lines
7.4 KiB
TypeScript
Raw Normal View History

import InputReceiver from "../Input/InputReceiver";
import Vec2 from "../DataTypes/Vec2";
import Receiver from "../Events/Receiver";
import Emitter from "../Events/Emitter";
2020-09-06 18:07:09 -04:00
import Scene from "../Scene/Scene";
2020-09-05 00:04:14 -04:00
import Layer from "../Scene/Layer";
2020-12-21 12:32:32 -05:00
import { Physical, Positioned, isRegion, Unique, Updateable, Actor, AI, DebugRenderable } from "../DataTypes/Interfaces/Descriptors"
2020-10-25 17:46:43 -04:00
import Shape from "../DataTypes/Shapes/Shape";
import Map from "../DataTypes/Map";
import AABB from "../DataTypes/Shapes/AABB";
import NavigationPath from "../Pathfinding/NavigationPath";
2020-11-29 19:49:04 -05:00
import TweenManager from "../Rendering/Animations/TweenManager";
2020-12-21 12:32:32 -05:00
import Debug from "../Debug/Debug";
import Color from "../Utils/Color";
2020-09-13 20:57:28 -04:00
/**
* The representation of an object in the game world
*/
2020-12-21 12:32:32 -05:00
export default abstract class GameNode implements Positioned, Unique, Updateable, Physical, Actor, DebugRenderable {
2020-10-25 17:46:43 -04:00
/*---------- POSITIONED ----------*/
private _position: Vec2;
2020-10-25 17:46:43 -04:00
/*---------- UNIQUE ----------*/
private _id: number;
/*---------- PHYSICAL ----------*/
hasPhysics: boolean;
moving: boolean;
onGround: boolean;
onWall: boolean;
onCeiling: boolean;
active: boolean;
collisionShape: Shape;
2020-12-22 13:18:10 -05:00
colliderOffset: Vec2;
2020-10-25 17:46:43 -04:00
isStatic: boolean;
isCollidable: boolean;
isTrigger: boolean;
group: string;
triggers: Map<string>;
_velocity: Vec2;
sweptRect: AABB;
collidedWithTilemap: boolean;
physicsLayer: number;
isPlayer: boolean;
2020-12-22 13:18:10 -05:00
isColliding: boolean = false;
2020-10-25 17:46:43 -04:00
/*---------- ACTOR ----------*/
_ai: AI;
aiActive: boolean;
actorId: number;
path: NavigationPath;
pathfinding: boolean = false;
2020-11-29 19:49:04 -05:00
/*---------- GENERAL ----------*/
2020-10-25 17:46:43 -04:00
protected input: InputReceiver;
protected receiver: Receiver;
protected emitter: Emitter;
2020-09-06 18:07:09 -04:00
protected scene: Scene;
protected layer: Layer;
2020-11-29 19:49:04 -05:00
tweens: TweenManager;
rotation: number;
2020-12-01 14:04:24 -05:00
alpha: number;
constructor(){
this.input = InputReceiver.getInstance();
this._position = new Vec2(0, 0);
2020-11-18 10:57:55 -05:00
this._position.setOnChange(() => this.positionChanged());
this.receiver = new Receiver();
this.emitter = new Emitter();
2020-11-29 19:49:04 -05:00
this.tweens = new TweenManager(this);
this.rotation = 0;
2020-12-01 14:04:24 -05:00
this.alpha = 1;
}
2020-08-17 20:09:41 -04:00
2020-10-25 17:46:43 -04:00
/*---------- POSITIONED ----------*/
get position(): Vec2 {
return this._position;
}
set position(pos: Vec2) {
this._position = pos;
2020-11-18 10:57:55 -05:00
this._position.setOnChange(() => this.positionChanged());
this.positionChanged();
}
2020-12-21 12:32:32 -05:00
get relativePosition(): Vec2 {
let origin = this.scene.getViewTranslation(this);
let zoom = this.scene.getViewScale();
return this.position.clone().sub(origin).scale(zoom);
}
2020-10-25 17:46:43 -04:00
/*---------- UNIQUE ----------*/
get id(): number {
return this._id;
}
set id(id: number) {
// id can only be set once
if(this._id === undefined){
this._id = id;
} else {
throw "Attempted to assign id to object that already has id."
}
}
/*---------- PHYSICAL ----------*/
/**
* @param velocity The velocity with which to move the object.
*/
move = (velocity: Vec2): void => {
this.moving = true;
this._velocity = velocity;
};
/**
* @param velocity The velocity with which the object will move.
*/
finishMove = (): void => {
this.moving = false;
this.position.add(this._velocity);
if(this.pathfinding){
this.path.handlePathProgress(this);
}
}
2020-10-25 17:46:43 -04:00
/**
* @param collisionShape The collider for this object. If this has a region (implements Region),
* it will be used when no collision shape is specified (or if collision shape is null).
* @param isCollidable Whether this is collidable or not. True by default.
* @param isStatic Whether this is static or not. False by default
*/
2020-12-22 13:18:10 -05:00
addPhysics = (collisionShape?: Shape, colliderOffset?: Vec2, isCollidable: boolean = true, isStatic: boolean = false): void => {
2020-10-25 17:46:43 -04:00
this.hasPhysics = true;
this.moving = false;
this.onGround = false;
this.onWall = false;
this.onCeiling = false;
2020-10-25 17:46:43 -04:00
this.active = true;
this.isCollidable = isCollidable;
this.isStatic = isStatic;
this.isTrigger = false;
this.group = "";
this.triggers = new Map();
this._velocity = Vec2.ZERO;
this.sweptRect = new AABB();
this.collidedWithTilemap = false;
this.physicsLayer = -1;
2020-10-25 17:46:43 -04:00
if(collisionShape){
this.collisionShape = collisionShape;
} else if (isRegion(this)) {
// If the gamenode has a region and no other is specified, use that
this.collisionShape = (<any>this).boundary.clone();
} else {
2020-10-25 17:46:43 -04:00
throw "No collision shape specified for physics object."
}
2020-10-25 17:46:43 -04:00
2020-12-22 13:18:10 -05:00
if(colliderOffset){
this.colliderOffset = colliderOffset;
} else {
this.colliderOffset = Vec2.ZERO;
}
2020-10-25 17:46:43 -04:00
this.sweptRect = this.collisionShape.getBoundingRect();
this.scene.getPhysicsManager().registerObject(this);
}
/**
* @param group The name of the group that will activate the trigger
* @param eventType The type of this event to send when this trigger is activated
*/
addTrigger = (group: string, eventType: string): void => {
this.isTrigger = true;
this.triggers.add(group, eventType);
};
setPhysicsLayer = (layer: string): void => {
this.scene.getPhysicsManager().setLayer(this, layer);
}
2020-12-22 13:18:10 -05:00
getLastVelocity(): Vec2 {
return this._velocity;
}
/*---------- ACTOR ----------*/
get ai(): AI {
return this._ai;
}
set ai(ai: AI) {
if(!this._ai){
// If we haven't been previously had an ai, register us with the ai manager
this.scene.getAIManager().registerActor(this);
}
this._ai = ai;
this.aiActive = true;
}
addAI<T extends AI>(ai: string | (new () => T), options?: Record<string, any>): void {
if(!this._ai){
this.scene.getAIManager().registerActor(this);
}
if(typeof ai === "string"){
this._ai = this.scene.getAIManager().generateAI(ai);
} else {
this._ai = new ai();
}
this._ai.initializeAI(this, options);
this.aiActive = true;
}
setAIActive(active: boolean): void {
this.aiActive = active;
}
2020-11-29 19:49:04 -05:00
/*---------- TWEENABLE PROPERTIES ----------*/
set positionX(value: number) {
this.position.x = value;
}
set positionY(value: number) {
this.position.y = value;
}
abstract set scaleX(value: number);
abstract set scaleY(value: number);
2020-10-25 17:46:43 -04:00
/*---------- GAME NODE ----------*/
/**
* Sets the scene for this object.
* @param scene The scene this object belongs to.
*/
setScene(scene: Scene): void {
this.scene = scene;
}
2020-10-25 17:46:43 -04:00
/** Gets the scene this object is in. */
getScene(): Scene {
return this.scene;
}
2020-10-25 17:46:43 -04:00
/**
* Sets the layer of this object.
* @param layer The layer this object will be on.
*/
setLayer(layer: Layer): void {
this.layer = layer;
}
/** Returns the layer this object is on. */
getLayer(): Layer {
return this.layer;
}
/**
* Called if the position vector is modified or replaced
*/
2020-11-18 10:57:55 -05:00
protected positionChanged(): void {
2020-10-25 17:46:43 -04:00
if(this.hasPhysics){
2020-12-22 13:18:10 -05:00
this.collisionShape.center = this.position.clone().add(this.colliderOffset);
2020-10-25 17:46:43 -04:00
}
};
2020-11-29 19:49:04 -05:00
update(deltaT: number): void {
this.tweens.update(deltaT);
}
2020-12-21 12:32:32 -05:00
debugRender(): void {
2020-12-22 13:18:10 -05:00
let color = this.isColliding ? Color.RED : Color.GREEN;
Debug.drawPoint(this.relativePosition, color);
2020-12-21 12:32:32 -05:00
// If velocity is not zero, draw a vector for it
if(this._velocity && !this._velocity.isZero()){
2020-12-22 13:18:10 -05:00
Debug.drawRay(this.relativePosition, this._velocity.clone().scaleTo(20).add(this.relativePosition), color);
}
// If this has a collider, draw it
if(this.isCollidable && this.collisionShape){
Debug.drawBox(this.collisionShape.center, this.collisionShape.halfSize, false, Color.RED);
2020-12-21 12:32:32 -05:00
}
}
2020-11-29 19:49:04 -05:00
}
export enum TweenableProperties{
posX = "positionX",
posY = "positionY",
scaleX = "scaleX",
scaleY = "scaleY",
2020-12-01 14:04:24 -05:00
rotation = "rotation",
alpha = "alpha"
}