restructured project, added default scene, fixed viewport bug
This commit is contained in:
parent
1512fa5c8f
commit
681d63f202
package.json
src
BoidDemo.ts
DataTypes
Input
MainScene.tsPhysics
QuadTreeScene.tsSecondScene.tsWolfie2D
AI
DataTypes
Collection.ts
Functions
Graphs
Interfaces
Map.tsPhysics
QuadTree.tsQueue.tsRegionQuadTree.tsShapes
Spritesheet.tsStack.tsState
Tilesets
Vec2.tsDebug
Events
Input
Loop
Nodes
Pathfinding
Physics
Playback
Rendering
ResourceManager
Scene
SceneGraph
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "gameengine",
|
||||
"name": "wolfie2d",
|
||||
"version": "1.0.0",
|
||||
"description": "A game engine written in TypeScript",
|
||||
"main": "./dist/main.js",
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
import Vec2 from "./DataTypes/Vec2";
|
||||
import Scene from "./Scene/Scene";
|
||||
import SceneGraphQuadTree from "./SceneGraph/SceneGraphQuadTree";
|
||||
import Color from "./Utils/Color";
|
||||
import Boid from "./_DemoClasses/Boids/Boid";
|
||||
import FlockBehavior from "./_DemoClasses/Boids/FlockBehavior";
|
||||
import Player from "./_DemoClasses/Player/Player";
|
||||
import PlayerController from "./_DemoClasses/Player/PlayerController";
|
||||
|
||||
/**
|
||||
* This demo emphasizes an ai system for the game engine with component architecture
|
||||
* Boids move around with components
|
||||
* Boids have randomized affects (maybe?)
|
||||
* Boids respond to player movement
|
||||
*/
|
||||
export default class BoidDemo extends Scene {
|
||||
boids: Array<Boid>;
|
||||
|
||||
startScene(){
|
||||
// Set the world size
|
||||
// this.worldSize = new Vec2(800, 600);
|
||||
// this.sceneGraph = new SceneGraphQuadTree(this.viewport, this);
|
||||
// this.viewport.setBounds(0, 0, 800, 600)
|
||||
// this.viewport.setCenter(400, 300);
|
||||
|
||||
// let layer = this.addLayer();
|
||||
// this.boids = new Array();
|
||||
|
||||
// // Add the player
|
||||
// let player = this.add.graphic(Player, layer, new Vec2(0, 0));
|
||||
// player.addPhysics();
|
||||
// let ai = new PlayerController(player, "topdown");
|
||||
// player.update = (deltaT: number) => {ai.update(deltaT)}
|
||||
// this.viewport.follow(player);
|
||||
// this.viewport.enableZoom();
|
||||
|
||||
// // Create a bunch of boids
|
||||
// for(let i = 0; i < 150; i++){
|
||||
// let boid = this.add.graphic(Boid, layer, new Vec2(this.worldSize.x*Math.random(), this.worldSize.y*Math.random()));
|
||||
// boid.fb = new FlockBehavior(this, boid, this.boids, 75, 50);
|
||||
// boid.size.set(5, 5);
|
||||
// this.boids.push(boid);
|
||||
// }
|
||||
}
|
||||
|
||||
updateScene(deltaT: number): void {
|
||||
for(let boid of this.boids){
|
||||
boid.setColor(Color.RED);
|
||||
}
|
||||
|
||||
this.updateFlock();
|
||||
}
|
||||
|
||||
updateFlock(): void {
|
||||
for(let boid of this.boids){
|
||||
boid.fb.update();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
import Vec2 from "./Vec2";
|
||||
|
||||
// @ignorePage
|
||||
export default class Vec4 {
|
||||
|
||||
public vec: Float32Array;
|
||||
|
||||
constructor(x : number = 0, y : number = 0, z : number = 0, w : number = 0) {
|
||||
this.vec = new Float32Array(4);
|
||||
this.vec[0] = x;
|
||||
this.vec[1] = y;
|
||||
this.vec[2] = z;
|
||||
this.vec[3] = w;
|
||||
}
|
||||
|
||||
// Expose x and y with getters and setters
|
||||
get x() {
|
||||
return this.vec[0];
|
||||
}
|
||||
|
||||
set x(x: number) {
|
||||
this.vec[0] = x;
|
||||
}
|
||||
|
||||
get y() {
|
||||
return this.vec[1];
|
||||
}
|
||||
|
||||
set y(y: number) {
|
||||
this.vec[1] = y;
|
||||
}
|
||||
|
||||
get z() {
|
||||
return this.vec[2];
|
||||
}
|
||||
|
||||
set z(x: number) {
|
||||
this.vec[2] = x;
|
||||
}
|
||||
|
||||
get w() {
|
||||
return this.vec[3];
|
||||
}
|
||||
|
||||
set w(y: number) {
|
||||
this.vec[3] = y;
|
||||
}
|
||||
|
||||
split() : [Vec2, Vec2] {
|
||||
return [new Vec2(this.x, this.y), new Vec2(this.z, this.w)];
|
||||
}
|
||||
}
|
|
@ -1,239 +0,0 @@
|
|||
import Receiver from "../Events/Receiver";
|
||||
import Map from "../DataTypes/Map";
|
||||
import Vec2 from "../DataTypes/Vec2";
|
||||
import EventQueue from "../Events/EventQueue";
|
||||
import Viewport from "../SceneGraph/Viewport";
|
||||
import GameEvent from "../Events/GameEvent";
|
||||
import { GameEventType } from "../Events/GameEventType";
|
||||
|
||||
/**
|
||||
* Receives input events from the @reference[EventQueue] and allows for easy access of information about input by other systems
|
||||
*/
|
||||
export default class InputReceiver{
|
||||
private static instance: InputReceiver = null;
|
||||
|
||||
private mousePressed: boolean;
|
||||
private mouseJustPressed: boolean;
|
||||
|
||||
private keyJustPressed: Map<boolean>;
|
||||
private keyPressed: Map<boolean>;
|
||||
|
||||
private mousePosition: Vec2;
|
||||
private mousePressPosition: Vec2;
|
||||
|
||||
private scrollDirection: number;
|
||||
private justScrolled: boolean;
|
||||
|
||||
private eventQueue: EventQueue;
|
||||
private receiver: Receiver;
|
||||
private viewport: Viewport;
|
||||
|
||||
private constructor(){
|
||||
this.mousePressed = false;
|
||||
this.mouseJustPressed = false;
|
||||
this.receiver = new Receiver();
|
||||
this.keyJustPressed = new Map<boolean>();
|
||||
this.keyPressed = new Map<boolean>();
|
||||
this.mousePosition = new Vec2(0, 0);
|
||||
this.mousePressPosition = new Vec2(0, 0);
|
||||
this.scrollDirection = 0;
|
||||
this.justScrolled = false;
|
||||
|
||||
this.eventQueue = EventQueue.getInstance();
|
||||
// Subscribe to all input events
|
||||
this.eventQueue.subscribe(this.receiver, [GameEventType.MOUSE_DOWN, GameEventType.MOUSE_UP, GameEventType.MOUSE_MOVE,
|
||||
GameEventType.KEY_DOWN, GameEventType.KEY_UP, GameEventType.CANVAS_BLUR, GameEventType.WHEEL_UP, GameEventType.WHEEL_DOWN]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the statc instance of the Singleton InputReceiver
|
||||
* @returns The InputReceiver instance
|
||||
*/
|
||||
static getInstance(): InputReceiver{
|
||||
if(this.instance === null){
|
||||
this.instance = new InputReceiver();
|
||||
}
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
update(deltaT: number): void {
|
||||
// Reset the justPressed values to false
|
||||
this.mouseJustPressed = false;
|
||||
this.keyJustPressed.forEach((key: string) => this.keyJustPressed.set(key, false));
|
||||
this.justScrolled = false;
|
||||
this.scrollDirection = 0;
|
||||
|
||||
while(this.receiver.hasNextEvent()){
|
||||
let event = this.receiver.getNextEvent();
|
||||
|
||||
// Handle each event type
|
||||
if(event.type === GameEventType.MOUSE_DOWN){
|
||||
this.mouseJustPressed = true;
|
||||
this.mousePressed = true;
|
||||
this.mousePressPosition = event.data.get("position");
|
||||
}
|
||||
|
||||
if(event.type === GameEventType.MOUSE_UP){
|
||||
this.mousePressed = false;
|
||||
}
|
||||
|
||||
if(event.type === GameEventType.MOUSE_MOVE){
|
||||
this.mousePosition = event.data.get("position");
|
||||
}
|
||||
|
||||
if(event.type === GameEventType.KEY_DOWN){
|
||||
let key = event.data.get("key");
|
||||
// Handle space bar
|
||||
if(key === " "){
|
||||
key = "space";
|
||||
}
|
||||
if(!this.keyPressed.get(key)){
|
||||
this.keyJustPressed.set(key, true);
|
||||
this.keyPressed.set(key, true);
|
||||
}
|
||||
}
|
||||
|
||||
if(event.type === GameEventType.KEY_UP){
|
||||
let key = event.data.get("key");
|
||||
// Handle space bar
|
||||
if(key === " "){
|
||||
key = "space";
|
||||
}
|
||||
this.keyPressed.set(key, false);
|
||||
}
|
||||
|
||||
if(event.type === GameEventType.CANVAS_BLUR){
|
||||
this.clearKeyPresses()
|
||||
}
|
||||
|
||||
if(event.type === GameEventType.WHEEL_UP){
|
||||
this.scrollDirection = -1;
|
||||
this.justScrolled = true;
|
||||
} else if(event.type === GameEventType.WHEEL_DOWN){
|
||||
this.scrollDirection = 1;
|
||||
this.justScrolled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private clearKeyPresses(): void {
|
||||
this.keyJustPressed.forEach((key: string) => this.keyJustPressed.set(key, false));
|
||||
this.keyPressed.forEach((key: string) => this.keyPressed.set(key, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not a key was newly pressed this frame.
|
||||
* If the key is still pressed from last frame and wasn't re-pressed, this will return false.
|
||||
* @param key The key
|
||||
* @returns True if the key was just pressed, false otherwise
|
||||
*/
|
||||
isJustPressed(key: string): boolean {
|
||||
if(this.keyJustPressed.has(key)){
|
||||
return this.keyJustPressed.get(key)
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all of the keys that are newly pressed this frame.
|
||||
* If a key is still pressed from last frame and wasn't re-pressed, it will not be in this list.
|
||||
* @returns An array of all of the newly pressed keys.
|
||||
*/
|
||||
getKeysJustPressed(): Array<string> {
|
||||
let keys = Array<string>();
|
||||
this.keyJustPressed.forEach(key => {
|
||||
if(this.keyJustPressed.get(key)){
|
||||
keys.push(key);
|
||||
}
|
||||
});
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not a key is being pressed.
|
||||
* @param key The key
|
||||
* @returns True if the key is currently pressed, false otherwise
|
||||
*/
|
||||
isPressed(key: string): boolean {
|
||||
if(this.keyPressed.has(key)){
|
||||
return this.keyPressed.get(key)
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the mouse was newly pressed this frame
|
||||
* @returns True if the mouse was just pressed, false otherwise
|
||||
*/
|
||||
isMouseJustPressed(): boolean {
|
||||
return this.mouseJustPressed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the mouse is currently pressed
|
||||
* @returns True if the mouse is currently pressed, false otherwise
|
||||
*/
|
||||
isMousePressed(): boolean {
|
||||
return this.mousePressed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the user scrolled or not
|
||||
* @returns True if the user just scrolled this frame, false otherwise
|
||||
*/
|
||||
didJustScroll(): boolean {
|
||||
return this.justScrolled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the direction of the scroll
|
||||
* @returns -1 if the user scrolled up, 1 if they scrolled down
|
||||
*/
|
||||
getScrollDirection(): number {
|
||||
return this.scrollDirection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of the player's mouse
|
||||
* @returns The mouse position stored as a Vec2
|
||||
*/
|
||||
getMousePosition(): Vec2 {
|
||||
return this.mousePosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of the player's mouse in the game world,
|
||||
* taking into consideration the scrolling of the viewport
|
||||
* @returns The mouse position stored as a Vec2
|
||||
*/
|
||||
getGlobalMousePosition(): Vec2 {
|
||||
return this.mousePosition.clone().add(this.viewport.getOrigin());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of the last mouse press
|
||||
* @returns The mouse position stored as a Vec2
|
||||
*/
|
||||
getMousePressPosition(): Vec2 {
|
||||
return this.mousePressPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of the last mouse press in the game world,
|
||||
* taking into consideration the scrolling of the viewport
|
||||
* @returns The mouse position stored as a Vec2
|
||||
*/
|
||||
getGlobalMousePressPosition(): Vec2 {
|
||||
return this.mousePressPosition.clone().add(this.viewport.getOrigin());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives the input receiver a reference to the viewport
|
||||
* @param viewport The viewport
|
||||
*/
|
||||
setViewport(viewport: Viewport): void {
|
||||
this.viewport = viewport;
|
||||
}
|
||||
}
|
136
src/MainScene.ts
136
src/MainScene.ts
|
@ -1,136 +0,0 @@
|
|||
import Scene from "./Scene/Scene";
|
||||
import Rect from "./Nodes/Graphics/Rect";
|
||||
import Color from "./Utils/Color";
|
||||
import Vec2 from "./DataTypes/Vec2";
|
||||
import UIElement from "./Nodes/UIElement";
|
||||
import Button from "./Nodes/UIElements/Button";
|
||||
import Layer from "./Scene/Layer";
|
||||
import SecondScene from "./SecondScene";
|
||||
import { GameEventType } from "./Events/GameEventType";
|
||||
import SceneGraphQuadTree from "./SceneGraph/SceneGraphQuadTree";
|
||||
import PlayerController from "./_DemoClasses/Player/PlayerStates/Platformer/PlayerController";
|
||||
|
||||
export default class MainScene extends Scene {
|
||||
|
||||
loadScene(){
|
||||
this.load.tilemap("platformer", "assets/tilemaps/Platformer.json");
|
||||
this.load.tilemap("background", "assets/tilemaps/Background.json");
|
||||
this.load.image("player", "assets/sprites/player.png");
|
||||
this.load.audio("player_jump", "assets/sounds/jump-3.wav");
|
||||
//this.load.audio("level_music", "assets/sounds/level.wav");
|
||||
|
||||
let loadingScreen = this.addLayer();
|
||||
let box = this.add.graphic(Rect, loadingScreen, new Vec2(200, 300), new Vec2(400, 60));
|
||||
box.setColor(new Color(0, 0, 0));
|
||||
let bar = this.add.graphic(Rect, loadingScreen, new Vec2(205, 305), new Vec2(0, 50));
|
||||
bar.setColor(new Color(0, 200, 200));
|
||||
|
||||
this.load.onLoadProgress = (percentProgress: number) => {
|
||||
bar.size.x = 295 * percentProgress;
|
||||
}
|
||||
|
||||
this.load.onLoadComplete = () => {
|
||||
loadingScreen.disable();
|
||||
}
|
||||
}
|
||||
|
||||
startScene(){
|
||||
// Set world size
|
||||
this.worldSize = new Vec2(2560, 1280)
|
||||
|
||||
// Use a quadtree
|
||||
this.sceneGraph = new SceneGraphQuadTree(this.viewport, this);
|
||||
|
||||
// Add the background tilemap
|
||||
let backgroundTilemapLayer = this.add.tilemap("background", new Vec2(4, 4))[0];
|
||||
// ...and make it have parallax
|
||||
backgroundTilemapLayer.setParallax(0.5, 0.8);
|
||||
backgroundTilemapLayer.setAlpha(0.5);
|
||||
|
||||
// Add the music and start playing it on a loop
|
||||
//this.emitter.fireEvent(GameEventType.PLAY_SOUND, {key: "level_music", loop: true, holdReference: true});
|
||||
|
||||
// Add the tilemap
|
||||
this.add.tilemap("platformer", new Vec2(4, 4));
|
||||
|
||||
// Create the main game layer
|
||||
let mainLayer = this.addLayer();
|
||||
|
||||
// Add a player
|
||||
let playerSprite = this.add.sprite("player", mainLayer)
|
||||
playerSprite.position.set(0, 0);
|
||||
playerSprite.size.set(64, 64);
|
||||
|
||||
this.viewport.follow(playerSprite);
|
||||
|
||||
// Initialize UI
|
||||
let uiLayer = this.addLayer();
|
||||
uiLayer.setParallax(0, 0);
|
||||
|
||||
let recordButton = this.add.uiElement(Button, uiLayer);
|
||||
recordButton.size.set(100, 50);
|
||||
recordButton.setText("Record");
|
||||
recordButton.position.set(400, 30);
|
||||
recordButton.onClickEventId = GameEventType.START_RECORDING;
|
||||
|
||||
let stopButton = this.add.uiElement(Button, uiLayer);
|
||||
stopButton.size.set(100, 50);
|
||||
stopButton.setText("Stop");
|
||||
stopButton.position.set(550, 30);
|
||||
stopButton.onClickEventId = GameEventType.STOP_RECORDING;
|
||||
|
||||
let playButton = this.add.uiElement(Button, uiLayer);
|
||||
playButton.size.set(100, 50);
|
||||
playButton.setText("Play");
|
||||
playButton.position.set(700, 30);
|
||||
playButton.onClickEventId = GameEventType.PLAY_RECORDING;
|
||||
|
||||
let cycleFramerateButton = this.add.uiElement(Button, uiLayer);
|
||||
cycleFramerateButton.size.set(150, 50);
|
||||
cycleFramerateButton.setText("Cycle FPS");
|
||||
cycleFramerateButton.position.set(5, 400);
|
||||
let i = 0;
|
||||
let fps = [15, 30, 60];
|
||||
cycleFramerateButton.onClick = () => {
|
||||
this.game.setMaxUpdateFPS(fps[i]);
|
||||
i = (i + 1) % 3;
|
||||
}
|
||||
|
||||
// Pause Menu
|
||||
let pauseLayer = this.addLayer();
|
||||
pauseLayer.setParallax(0, 0);
|
||||
pauseLayer.disable();
|
||||
|
||||
let pauseButton = this.add.uiElement(Button, uiLayer);
|
||||
pauseButton.size.set(100, 50);
|
||||
pauseButton.setText("Pause");
|
||||
pauseButton.position.set(700, 400);
|
||||
pauseButton.onClick = () => {
|
||||
this.sceneGraph.getLayers().forEach((layer: Layer) => layer.setPaused(true));
|
||||
pauseLayer.enable();
|
||||
}
|
||||
|
||||
let modalBackground = this.add.uiElement(UIElement, pauseLayer);
|
||||
modalBackground.size.set(400, 200);
|
||||
modalBackground.setBackgroundColor(new Color(0, 0, 0, 0.4));
|
||||
modalBackground.position.set(200, 100);
|
||||
|
||||
let resumeButton = this.add.uiElement(Button, pauseLayer);
|
||||
resumeButton.size.set(100, 50);
|
||||
resumeButton.setText("Resume");
|
||||
resumeButton.position.set(360, 150);
|
||||
resumeButton.onClick = () => {
|
||||
this.sceneGraph.getLayers().forEach((layer: Layer) => layer.setPaused(false));
|
||||
pauseLayer.disable();
|
||||
}
|
||||
|
||||
let switchButton = this.add.uiElement(Button, pauseLayer);
|
||||
switchButton.size.set(140, 50);
|
||||
switchButton.setText("Change Scene");
|
||||
switchButton.position.set(340, 190);
|
||||
switchButton.onClick = () => {
|
||||
this.emitter.fireEvent(GameEventType.STOP_SOUND, {key: "level_music"});
|
||||
this.sceneManager.changeScene(SecondScene);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,353 +0,0 @@
|
|||
import Physical from "../DataTypes/Interfaces/Physical";
|
||||
import Vec2 from "../DataTypes/Vec2";
|
||||
import GameNode from "../Nodes/GameNode";
|
||||
import Tilemap from "../Nodes/Tilemap";
|
||||
import PhysicsManager from "./PhysicsManager";
|
||||
import BroadPhase from "./BroadPhaseAlgorithms/BroadPhase";
|
||||
import SweepAndPrune from "./BroadPhaseAlgorithms/SweepAndPrune";
|
||||
import Shape from "../DataTypes/Shapes/Shape";
|
||||
import MathUtils from "../Utils/MathUtils";
|
||||
import OrthogonalTilemap from "../Nodes/Tilemaps/OrthogonalTilemap";
|
||||
import AABB from "../DataTypes/Shapes/AABB";
|
||||
import Debug from "../Debug/Debug";
|
||||
|
||||
// @ignorePage
|
||||
|
||||
export default class BasicPhysicsManager extends PhysicsManager {
|
||||
|
||||
/** The array of static nodes */
|
||||
protected staticNodes: Array<Physical>;
|
||||
|
||||
/** The array of dynamic nodes */
|
||||
protected dynamicNodes: Array<Physical>;
|
||||
|
||||
/** The array of tilemaps */
|
||||
protected tilemaps: Array<Tilemap>;
|
||||
|
||||
/** The broad phase collision detection algorithm used by this physics system */
|
||||
protected broadPhase: BroadPhase;
|
||||
|
||||
/** A 2D array that contains information about which layers interact with each other */
|
||||
protected layerMask: number[][];
|
||||
|
||||
constructor(physicsOptions: Record<string, any>){
|
||||
super();
|
||||
this.staticNodes = new Array();
|
||||
this.dynamicNodes = new Array();
|
||||
this.tilemaps = new Array();
|
||||
this.broadPhase = new SweepAndPrune();
|
||||
|
||||
let i = 0;
|
||||
if(physicsOptions.physicsLayerNames !== null){
|
||||
for(let layer of physicsOptions.physicsLayerNames){
|
||||
if(i >= physicsOptions.numPhysicsLayers){
|
||||
// If we have too many string layers, don't add extras
|
||||
}
|
||||
|
||||
this.layerNames[i] = layer;
|
||||
this.layerMap.add(layer, i);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
for(i; i < physicsOptions.numPhysicsLayers; i++){
|
||||
this.layerNames[i] = "" + i;
|
||||
this.layerMap.add("" + i, i);
|
||||
}
|
||||
|
||||
this.layerMask = physicsOptions.physicsLayerCollisions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new physics object to be updated with the physics system
|
||||
* @param node The node to be added to the physics system
|
||||
*/
|
||||
registerObject(node: GameNode): void {
|
||||
if(node.isStatic){
|
||||
// Static and not collidable
|
||||
this.staticNodes.push(node);
|
||||
} else {
|
||||
// Dynamic and not collidable
|
||||
this.dynamicNodes.push(node);
|
||||
}
|
||||
this.broadPhase.addNode(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new tilemap to be updated with the physics system
|
||||
* @param tilemap The tilemap to be added to the physics system
|
||||
*/
|
||||
registerTilemap(tilemap: Tilemap): void {
|
||||
this.tilemaps.push(tilemap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a collision between two nodes, adjusting their velocities accordingly.
|
||||
* @param node1
|
||||
* @param node2
|
||||
* @param firstContact
|
||||
* @param lastContact
|
||||
* @param collidingX
|
||||
* @param collidingY
|
||||
*/
|
||||
resolveCollision(node1: Physical, node2: Physical, firstContact: Vec2, lastContact: Vec2, collidingX: boolean, collidingY: boolean): void {
|
||||
// Handle collision
|
||||
if( (firstContact.x < 1 || collidingX) && (firstContact.y < 1 || collidingY)){
|
||||
if(node1.isPlayer){
|
||||
node1.isColliding = true;
|
||||
} else if(node2.isPlayer){
|
||||
node2.isColliding = true;
|
||||
}
|
||||
|
||||
// We are colliding. Check for any triggers
|
||||
let group1 = node1.group;
|
||||
let group2 = node2.group;
|
||||
|
||||
// TODO - This is problematic if a collision happens, but it is later learned that another collision happens before it
|
||||
if(node1.triggers.has(group2)){
|
||||
// Node1 should send an event
|
||||
let eventType = node1.triggers.get(group2);
|
||||
this.emitter.fireEvent(eventType, {node: node1, other: node2, collision: {firstContact: firstContact}});
|
||||
}
|
||||
|
||||
if(node2.triggers.has(group1)){
|
||||
// Node2 should send an event
|
||||
let eventType = node2.triggers.get(group1);
|
||||
this.emitter.fireEvent(eventType, {node: node2, other: node1, collision: {firstContact: firstContact}});
|
||||
}
|
||||
|
||||
if(collidingX && collidingY){
|
||||
// If we're already intersecting, resolve the current collision
|
||||
} else if(node1.isCollidable && node2.isCollidable) {
|
||||
// We aren't already colliding, and both nodes can collide, so this is a new collision.
|
||||
|
||||
// Get the amount to scale x and y based on their initial collision times
|
||||
let xScale = MathUtils.clamp(firstContact.x, 0, 1);
|
||||
let yScale = MathUtils.clamp(firstContact.y, 0, 1);
|
||||
|
||||
MathUtils.floorToPlace(xScale, 4);
|
||||
MathUtils.floorToPlace(yScale, 4);
|
||||
|
||||
// Handle special case of stickiness on perfect corner to corner collisions
|
||||
if(xScale === yScale){
|
||||
xScale = 1;
|
||||
}
|
||||
|
||||
// Handle being stopped moving in the y-direction
|
||||
if(yScale !== 1){
|
||||
// Figure out which node is on top
|
||||
let node1onTop = node1.collisionShape.center.y < node2.collisionShape.center.y;
|
||||
|
||||
// If either is moving, set their onFloor and onCeiling appropriately
|
||||
if(!node1.isStatic && node1.moving){
|
||||
node1.onGround = node1onTop;
|
||||
node1.onCeiling = !node1onTop;
|
||||
}
|
||||
|
||||
if(!node2.isStatic && node2.moving){
|
||||
node2.onGround = !node1onTop;
|
||||
node2.onCeiling = node1onTop;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle being stopped moving in the x-direction
|
||||
if(xScale !== 1){
|
||||
// If either node is non-static and moving, set its onWall to true
|
||||
if(!node1.isStatic && node1.moving){
|
||||
node1.onWall = true;
|
||||
}
|
||||
|
||||
if(!node2.isStatic && node2.moving){
|
||||
node2.onWall = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Scale velocity for either node if it is moving
|
||||
node1._velocity.scale(xScale, yScale);
|
||||
|
||||
node2._velocity.scale(xScale, yScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
collideWithTilemap(node: Physical, tilemap: Tilemap, velocity: Vec2): void {
|
||||
if(tilemap instanceof OrthogonalTilemap){
|
||||
this.collideWithOrthogonalTilemap(node, tilemap, velocity);
|
||||
}
|
||||
}
|
||||
|
||||
collideWithOrthogonalTilemap(node: Physical, tilemap: OrthogonalTilemap, velocity: Vec2): void {
|
||||
// Get the starting position, ending position, and size of the node
|
||||
let startPos = node.collisionShape.center;
|
||||
let endPos = startPos.clone().add(velocity);
|
||||
let size = node.collisionShape.halfSize;
|
||||
|
||||
// Get the min and max x and y coordinates of the moving node
|
||||
let min = new Vec2(Math.min(startPos.x - size.x, endPos.x - size.x), Math.min(startPos.y - size.y, endPos.y - size.y));
|
||||
let max = new Vec2(Math.max(startPos.x + size.x, endPos.x + size.x), Math.max(startPos.y + size.y, endPos.y + size.y));
|
||||
|
||||
// Convert the min/max x/y to the min and max row/col in the tilemap array
|
||||
let minIndex = tilemap.getColRowAt(min);
|
||||
let maxIndex = tilemap.getColRowAt(max);
|
||||
|
||||
// Create an empty set of tilemap collisions (We'll handle all of them at the end)
|
||||
let tilemapCollisions = new Array<TileCollisionData>();
|
||||
let tileSize = tilemap.getTileSize();
|
||||
|
||||
// Loop over all possible tiles (which isn't many in the scope of the velocity per frame)
|
||||
for(let col = minIndex.x; col <= maxIndex.x; col++){
|
||||
for(let row = minIndex.y; row <= maxIndex.y; row++){
|
||||
if(tilemap.isTileCollidable(col, row)){
|
||||
|
||||
// Get the position of this tile
|
||||
let tilePos = new Vec2(col * tileSize.x + tileSize.x/2, row * tileSize.y + tileSize.y/2);
|
||||
|
||||
// Create a new collider for this tile
|
||||
let collider = new AABB(tilePos, tileSize.scaled(1/2));
|
||||
|
||||
// Calculate collision area between the node and the tile
|
||||
let dx = Math.min(startPos.x, tilePos.x) - Math.max(startPos.x + size.x, tilePos.x + size.x);
|
||||
let dy = Math.min(startPos.y, tilePos.y) - Math.max(startPos.y + size.y, tilePos.y + size.y);
|
||||
|
||||
// If we overlap, how much do we overlap by?
|
||||
let overlap = 0;
|
||||
if(dx * dy > 0){
|
||||
overlap = dx * dy;
|
||||
}
|
||||
|
||||
tilemapCollisions.push(new TileCollisionData(collider, overlap));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we have all collisions, sort by collision area highest to lowest
|
||||
tilemapCollisions = tilemapCollisions.sort((a, b) => a.overlapArea - b.overlapArea);
|
||||
|
||||
// Resolve the collisions in order of collision area (i.e. "closest" tiles are collided with first, so we can slide along a surface of tiles)
|
||||
tilemapCollisions.forEach(collision => {
|
||||
let [firstContact, _, collidingX, collidingY] = Shape.getTimeOfCollision(node.collisionShape, velocity, collision.collider, Vec2.ZERO);
|
||||
|
||||
// Handle collision
|
||||
if( (firstContact.x < 1 || collidingX) && (firstContact.y < 1 || collidingY)){
|
||||
// We are definitely colliding, so add to this node's tilemap collision list
|
||||
node.collidedWithTilemap = true;
|
||||
|
||||
if(collidingX && collidingY){
|
||||
// If we're already intersecting, freak out I guess? Probably should handle this in some way for if nodes get spawned inside of tiles
|
||||
} else {
|
||||
// Get the amount to scale x and y based on their initial collision times
|
||||
let xScale = MathUtils.clamp(firstContact.x, 0, 1);
|
||||
let yScale = MathUtils.clamp(firstContact.y, 0, 1);
|
||||
|
||||
// Handle special case of stickiness on perfect corner to corner collisions
|
||||
if(xScale === yScale){
|
||||
xScale = 1;
|
||||
}
|
||||
|
||||
if(yScale !== 1){
|
||||
// If the tile is below us
|
||||
if(collision.collider.y > node.collisionShape.center.y){
|
||||
node.onGround = true;
|
||||
} else {
|
||||
node.onCeiling = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(xScale !== 1){
|
||||
node.onWall = true;
|
||||
}
|
||||
|
||||
// Scale the velocity of the node
|
||||
velocity.scale(xScale, yScale);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
update(deltaT: number): void {
|
||||
/*---------- INITIALIZATION PHASE ----------*/
|
||||
for(let node of this.dynamicNodes){
|
||||
// Clear frame dependent boolean values for each node
|
||||
node.onGround = false;
|
||||
node.onCeiling = false;
|
||||
node.onWall = false;
|
||||
node.collidedWithTilemap = false;
|
||||
node.isColliding = false;
|
||||
|
||||
if(node.isPlayer){
|
||||
Debug.log("pvel", "Player Velocity:", node._velocity.toString());
|
||||
}
|
||||
|
||||
// Update the swept shapes of each node
|
||||
if(node.moving){
|
||||
// Round Velocity
|
||||
node._velocity.x = Math.round(node._velocity.x*1000)/1000;
|
||||
node._velocity.y = Math.round(node._velocity.y*1000)/1000;
|
||||
|
||||
// If moving, reflect that in the swept shape
|
||||
node.sweptRect.sweep(node._velocity, node.collisionShape.center, node.collisionShape.halfSize);
|
||||
} else {
|
||||
node.sweptRect.sweep(Vec2.ZERO_STATIC, node.collisionShape.center, node.collisionShape.halfSize);
|
||||
}
|
||||
}
|
||||
|
||||
/*---------- BROAD PHASE ----------*/
|
||||
// Get a potentially colliding set
|
||||
let potentialCollidingPairs = this.broadPhase.runAlgorithm();
|
||||
|
||||
|
||||
// TODO - Should I be getting all collisions first, sorting by the time they happen, the resolving them?
|
||||
/*---------- NARROW PHASE ----------*/
|
||||
for(let pair of potentialCollidingPairs){
|
||||
let node1 = pair[0];
|
||||
let node2 = pair[1];
|
||||
|
||||
// Make sure both nodes are active
|
||||
if(!node1.active || !node2.active){
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure both nodes can collide with each other based on their physics layer
|
||||
if(!(node1.physicsLayer === -1 || node2.physicsLayer === -1 || this.layerMask[node1.physicsLayer][node2.physicsLayer] === 1)){
|
||||
// Nodes do not collide. Continue onto the next pair
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get Collision (which may or may not happen)
|
||||
let [firstContact, lastContact, collidingX, collidingY] = Shape.getTimeOfCollision(node1.collisionShape, node1._velocity, node2.collisionShape, node2._velocity);
|
||||
|
||||
this.resolveCollision(node1, node2, firstContact, lastContact, collidingX, collidingY);
|
||||
}
|
||||
|
||||
/*---------- TILEMAP PHASE ----------*/
|
||||
for(let node of this.dynamicNodes){
|
||||
if(node.moving && node.isCollidable){
|
||||
// If a node is moving and can collide, check it against every tilemap
|
||||
for(let tilemap of this.tilemaps){
|
||||
// Check if there could even be a collision
|
||||
if(node.sweptRect.overlaps(tilemap.boundary)){
|
||||
this.collideWithTilemap(node, tilemap, node._velocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*---------- ENDING PHASE ----------*/
|
||||
for(let node of this.dynamicNodes){
|
||||
if(node.moving){
|
||||
node.finishMove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collision data objects for tilemaps
|
||||
class TileCollisionData {
|
||||
collider: AABB;
|
||||
overlapArea: number;
|
||||
|
||||
constructor(collider: AABB, overlapArea: number){
|
||||
this.collider = collider;
|
||||
this.overlapArea = overlapArea;
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
import Physical from "../../DataTypes/Interfaces/Physical";
|
||||
import GameNode from "../../Nodes/GameNode";
|
||||
|
||||
// @ignorePage
|
||||
|
||||
export default abstract class BroadPhase {
|
||||
/**
|
||||
* Runs the algorithm and returns an array of possible collision pairs.
|
||||
*/
|
||||
abstract runAlgorithm(): Array<Physical[]>;
|
||||
|
||||
abstract addNode(node: GameNode): void;
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
import Physical from "../../DataTypes/Interfaces/Physical";
|
||||
import SortingUtils from "../../Utils/SortingUtils";
|
||||
import BroadPhase from "./BroadPhase";
|
||||
|
||||
// @ignorePage
|
||||
|
||||
export default class SweepAndPrune extends BroadPhase {
|
||||
protected xList: Array<Physical>;
|
||||
protected yList: Array<Physical>;
|
||||
|
||||
constructor(){
|
||||
super();
|
||||
this.xList = new Array();
|
||||
this.yList = new Array();
|
||||
}
|
||||
|
||||
addNode(node: Physical): void {
|
||||
this.xList.push(node);
|
||||
this.yList.push(node);
|
||||
}
|
||||
|
||||
// TODO - Can optimize further by doing a callback whenever a swap occurs
|
||||
// TODO - And by using better pair management
|
||||
runAlgorithm(): Array<Physical[]> {
|
||||
// Sort the xList
|
||||
SortingUtils.insertionSort(this.xList, (a, b) => (a.sweptRect.left - b.sweptRect.left) );
|
||||
|
||||
let xCollisions = [];
|
||||
for(let i = 0; i < this.xList.length; i++){
|
||||
let node = this.xList[i];
|
||||
|
||||
let index = 1;
|
||||
while(i + index < this.xList.length && node.sweptRect.right >= this.xList[i + index].sweptRect.left){
|
||||
// Colliding pair in x-axis
|
||||
xCollisions.push([node, this.xList[i + index]]);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the y-list
|
||||
SortingUtils.insertionSort(this.yList, (a, b) => (a.sweptRect.top - b.sweptRect.top) );
|
||||
|
||||
let yCollisions = [];
|
||||
for(let i = 0; i < this.yList.length; i++){
|
||||
let node = this.yList[i];
|
||||
|
||||
let index = 1;
|
||||
while(i + index < this.yList.length && node.sweptRect.bottom >= this.yList[i + index].sweptRect.top){
|
||||
// Colliding pair in y-axis
|
||||
yCollisions.push([node, this.yList[i + index]]);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the pairs
|
||||
let collisions = []
|
||||
for(let xPair of xCollisions){
|
||||
for(let yPair of yCollisions){
|
||||
if((xPair[0] === yPair[0] && xPair[1] === yPair[1])
|
||||
||(xPair[0] === yPair[1] && xPair[1] === yPair[0])){
|
||||
// Colliding in both axes, add to set
|
||||
collisions.push(xPair);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return collisions;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
import Physical from "../DataTypes/Interfaces/Physical";
|
||||
import Vec2 from "../DataTypes/Vec2";
|
||||
|
||||
// @ignorePage
|
||||
|
||||
export class Collision {
|
||||
firstContact: Vec2;
|
||||
lastContact: Vec2;
|
||||
collidingX: boolean;
|
||||
collidingY: boolean;
|
||||
node1: Physical;
|
||||
node2: Physical;
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
import Scene from "./Scene/Scene";
|
||||
import Point from "./Nodes/Graphics/Point";
|
||||
import Rect from "./Nodes/Graphics/Rect";
|
||||
import Layer from "./Scene/Layer";
|
||||
import SceneGraphQuadTree from "./SceneGraph/SceneGraphQuadTree"
|
||||
import Vec2 from "./DataTypes/Vec2";
|
||||
import InputReceiver from "./Input/InputReceiver";
|
||||
import Color from "./Utils/Color";
|
||||
|
||||
export default class QuadTreeScene extends Scene {
|
||||
|
||||
mainLayer: Layer;
|
||||
view: Rect;
|
||||
points: Array<Point>;
|
||||
|
||||
loadScene(){}
|
||||
|
||||
startScene(){
|
||||
// Make the scene graph a quadtree scenegraph
|
||||
this.sceneGraph = new SceneGraphQuadTree(this.viewport, this);
|
||||
|
||||
// Make a main layer
|
||||
this.mainLayer = this.sceneGraph.addLayer();
|
||||
|
||||
// Generate a bunch of random points
|
||||
this.points = [];
|
||||
for(let i = 0; i < 1000; i++){
|
||||
let pos = new Vec2(500/3*(Math.random() + Math.random() + Math.random()), 500/3*(Math.random() + Math.random() + Math.random()));
|
||||
let point = this.add.graphic(Point, this.mainLayer, pos);
|
||||
point.setColor(Color.RED);
|
||||
this.points.push(point);
|
||||
}
|
||||
|
||||
this.view = this.add.graphic(Rect, this.mainLayer, Vec2.ZERO, new Vec2(150, 100));
|
||||
this.view.setColor(Color.TRANSPARENT);
|
||||
this.view.setBorderColor(Color.ORANGE);
|
||||
}
|
||||
|
||||
updateScene(deltaT: number): void {
|
||||
this.view.position.copy(InputReceiver.getInstance().getGlobalMousePosition());
|
||||
for(let point of this.points){
|
||||
point.setColor(Color.RED);
|
||||
|
||||
point.position.add(Vec2.UP.rotateCCW(Math.random()*2*Math.PI).add(point.position.vecTo(this.view.position).normalize().scale(0.1)));
|
||||
}
|
||||
|
||||
let results = this.sceneGraph.getNodesInRegion(this.view.boundary);
|
||||
|
||||
for(let result of results){
|
||||
if(result instanceof Point){
|
||||
result.setColor(Color.GREEN);
|
||||
}
|
||||
}
|
||||
|
||||
results = this.sceneGraph.getNodesAt(this.view.position);
|
||||
|
||||
for(let result of results){
|
||||
if(result instanceof Point){
|
||||
result.setColor(Color.YELLOW);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
import Scene from "./Scene/Scene";
|
||||
import Rect from "./Nodes/Graphics/Rect";
|
||||
import Color from "./Utils/Color";
|
||||
import Vec2 from "./DataTypes/Vec2";
|
||||
import UIElement from "./Nodes/UIElement";
|
||||
import Button from "./Nodes/UIElements/Button";
|
||||
import Layer from "./Scene/Layer";
|
||||
import { GameEventType } from "./Events/GameEventType";
|
||||
|
||||
export default class SecondScene extends Scene {
|
||||
|
||||
loadScene(){
|
||||
this.load.tilemap("level2", "assets/tilemaps/TopDown2.json");
|
||||
this.load.image("player", "assets/sprites/player.png");
|
||||
this.load.audio("music", "assets/sounds/level.wav")
|
||||
|
||||
let loadingScreen = this.addLayer();
|
||||
let box = this.add.graphic(Rect, loadingScreen, new Vec2(200, 300), new Vec2(400, 60));
|
||||
box.setColor(new Color(0, 0, 0));
|
||||
let bar = this.add.graphic(Rect, loadingScreen, new Vec2(205, 305), new Vec2(0, 50));
|
||||
bar.setColor(new Color(255, 100, 0));
|
||||
|
||||
this.load.onLoadProgress = (percentProgress: number) => {
|
||||
//bar.setSize(295 * percentProgress, bar.getSize().y);
|
||||
}
|
||||
|
||||
this.load.onLoadComplete = () => {
|
||||
loadingScreen.disable();
|
||||
}
|
||||
}
|
||||
|
||||
startScene(){
|
||||
// // Add the tilemap
|
||||
// let mainLayer = this.add.tilemap("level2")[1];
|
||||
// mainLayer.setYSort(true);
|
||||
|
||||
// // Add a player
|
||||
// let player = this.add.physics(Player, mainLayer, "topdown");
|
||||
// let playerSprite = this.add.sprite("player", mainLayer);
|
||||
// player.setSprite(playerSprite);
|
||||
|
||||
// this.viewport.follow(player);
|
||||
|
||||
// // Initialize UI
|
||||
// let uiLayer = this.addLayer();
|
||||
// uiLayer.setParallax(0, 0);
|
||||
|
||||
// let recordButton = this.add.uiElement(Button, uiLayer);
|
||||
// recordButton.setSize(100, 50);
|
||||
// recordButton.setText("Record");
|
||||
// recordButton.setPosition(400, 30);
|
||||
// recordButton.onClickEventId = GameEventType.START_RECORDING;
|
||||
|
||||
// let stopButton = this.add.uiElement(Button, uiLayer);
|
||||
// stopButton.setSize(100, 50);
|
||||
// stopButton.setText("Stop");
|
||||
// stopButton.setPosition(550, 30);
|
||||
// stopButton.onClickEventId = GameEventType.STOP_RECORDING;
|
||||
|
||||
// let playButton = this.add.uiElement(Button, uiLayer);
|
||||
// playButton.setSize(100, 50);
|
||||
// playButton.setText("Play");
|
||||
// playButton.setPosition(700, 30);
|
||||
// playButton.onClickEventId = GameEventType.PLAY_RECORDING;
|
||||
|
||||
// let cycleFramerateButton = this.add.uiElement(Button, uiLayer);
|
||||
// cycleFramerateButton.setSize(150, 50);
|
||||
// cycleFramerateButton.setText("Cycle FPS");
|
||||
// cycleFramerateButton.setPosition(5, 400);
|
||||
// let i = 0;
|
||||
// let fps = [15, 30, 60];
|
||||
// cycleFramerateButton.onClick = () => {
|
||||
// this.game.setMaxUpdateFPS(fps[i]);
|
||||
// i = (i + 1) % 3;
|
||||
// }
|
||||
|
||||
// // Pause Menu
|
||||
// let pauseLayer = this.addLayer();
|
||||
// pauseLayer.setParallax(0, 0);
|
||||
// pauseLayer.disable();
|
||||
|
||||
// let pauseButton = this.add.uiElement(Button, uiLayer);
|
||||
// pauseButton.setSize(100, 50);
|
||||
// pauseButton.setText("Pause");
|
||||
// pauseButton.setPosition(700, 400);
|
||||
// pauseButton.onClick = () => {
|
||||
// this.sceneGraph.getLayers().forEach((layer: Layer) => layer.setPaused(true));
|
||||
// pauseLayer.enable();
|
||||
// }
|
||||
|
||||
// let modalBackground = this.add.uiElement(UIElement, pauseLayer);
|
||||
// modalBackground.setSize(400, 200);
|
||||
// modalBackground.setBackgroundColor(new Color(0, 0, 0, 0.4));
|
||||
// modalBackground.setPosition(200, 100);
|
||||
|
||||
// let resumeButton = this.add.uiElement(Button, pauseLayer);
|
||||
// resumeButton.setSize(100, 50);
|
||||
// resumeButton.setText("Resume");
|
||||
// resumeButton.setPosition(400, 200);
|
||||
// resumeButton.onClick = () => {
|
||||
// this.sceneGraph.getLayers().forEach((layer: Layer) => layer.setPaused(false));
|
||||
// pauseLayer.disable();
|
||||
// }
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
// @ignorePage
|
||||
|
||||
/**
|
||||
* A placeholder function for No Operation. Does nothing
|
||||
*/
|
291
src/Wolfie2D/Input/Input.ts
Normal file
291
src/Wolfie2D/Input/Input.ts
Normal file
|
@ -0,0 +1,291 @@
|
|||
import Receiver from "../Events/Receiver";
|
||||
import Map from "../DataTypes/Map";
|
||||
import Vec2 from "../DataTypes/Vec2";
|
||||
import EventQueue from "../Events/EventQueue";
|
||||
import Viewport from "../SceneGraph/Viewport";
|
||||
import GameEvent from "../Events/GameEvent";
|
||||
import { GameEventType } from "../Events/GameEventType";
|
||||
|
||||
/**
|
||||
* Receives input events from the @reference[EventQueue] and allows for easy access of information about input by other systems
|
||||
*/
|
||||
export default class Input {
|
||||
private static mousePressed: boolean;
|
||||
private static mouseJustPressed: boolean;
|
||||
|
||||
private static keyJustPressed: Map<boolean>;
|
||||
private static keyPressed: Map<boolean>;
|
||||
|
||||
private static mousePosition: Vec2;
|
||||
private static mousePressPosition: Vec2;
|
||||
|
||||
private static scrollDirection: number;
|
||||
private static justScrolled: boolean;
|
||||
|
||||
private static eventQueue: EventQueue;
|
||||
private static receiver: Receiver;
|
||||
private static viewport: Viewport;
|
||||
|
||||
private static keyMap: Map<Array<string>>;
|
||||
|
||||
/**
|
||||
* Initializes the Input object
|
||||
* @param viewport A reference to the viewport of the game
|
||||
*/
|
||||
static initialize(viewport: Viewport, keyMap: Array<Record<string, any>>){
|
||||
Input.viewport = viewport;
|
||||
Input.mousePressed = false;
|
||||
Input.mouseJustPressed = false;
|
||||
Input.receiver = new Receiver();
|
||||
Input.keyJustPressed = new Map<boolean>();
|
||||
Input.keyPressed = new Map<boolean>();
|
||||
Input.mousePosition = new Vec2(0, 0);
|
||||
Input.mousePressPosition = new Vec2(0, 0);
|
||||
Input.scrollDirection = 0;
|
||||
Input.justScrolled = false;
|
||||
|
||||
// Initialize the keymap
|
||||
Input.keyMap = new Map();
|
||||
|
||||
// Add all keys to the keymap
|
||||
for(let entry in keyMap){
|
||||
let name = keyMap[entry].name;
|
||||
let keys = keyMap[entry].keys;
|
||||
Input.keyMap.add(name, keys);
|
||||
}
|
||||
|
||||
Input.eventQueue = EventQueue.getInstance();
|
||||
// Subscribe to all input events
|
||||
Input.eventQueue.subscribe(Input.receiver, [GameEventType.MOUSE_DOWN, GameEventType.MOUSE_UP, GameEventType.MOUSE_MOVE,
|
||||
GameEventType.KEY_DOWN, GameEventType.KEY_UP, GameEventType.CANVAS_BLUR, GameEventType.WHEEL_UP, GameEventType.WHEEL_DOWN]);
|
||||
}
|
||||
|
||||
static update(deltaT: number): void {
|
||||
// Reset the justPressed values to false
|
||||
Input.mouseJustPressed = false;
|
||||
Input.keyJustPressed.forEach((key: string) => Input.keyJustPressed.set(key, false));
|
||||
Input.justScrolled = false;
|
||||
Input.scrollDirection = 0;
|
||||
|
||||
while(Input.receiver.hasNextEvent()){
|
||||
let event = Input.receiver.getNextEvent();
|
||||
|
||||
// Handle each event type
|
||||
if(event.type === GameEventType.MOUSE_DOWN){
|
||||
Input.mouseJustPressed = true;
|
||||
Input.mousePressed = true;
|
||||
Input.mousePressPosition = event.data.get("position");
|
||||
}
|
||||
|
||||
if(event.type === GameEventType.MOUSE_UP){
|
||||
Input.mousePressed = false;
|
||||
}
|
||||
|
||||
if(event.type === GameEventType.MOUSE_MOVE){
|
||||
Input.mousePosition = event.data.get("position");
|
||||
}
|
||||
|
||||
if(event.type === GameEventType.KEY_DOWN){
|
||||
let key = event.data.get("key");
|
||||
// Handle space bar
|
||||
if(key === " "){
|
||||
key = "space";
|
||||
}
|
||||
if(!Input.keyPressed.get(key)){
|
||||
Input.keyJustPressed.set(key, true);
|
||||
Input.keyPressed.set(key, true);
|
||||
}
|
||||
}
|
||||
|
||||
if(event.type === GameEventType.KEY_UP){
|
||||
let key = event.data.get("key");
|
||||
// Handle space bar
|
||||
if(key === " "){
|
||||
key = "space";
|
||||
}
|
||||
Input.keyPressed.set(key, false);
|
||||
}
|
||||
|
||||
if(event.type === GameEventType.CANVAS_BLUR){
|
||||
Input.clearKeyPresses()
|
||||
}
|
||||
|
||||
if(event.type === GameEventType.WHEEL_UP){
|
||||
Input.scrollDirection = -1;
|
||||
Input.justScrolled = true;
|
||||
} else if(event.type === GameEventType.WHEEL_DOWN){
|
||||
Input.scrollDirection = 1;
|
||||
Input.justScrolled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static clearKeyPresses(): void {
|
||||
Input.keyJustPressed.forEach((key: string) => Input.keyJustPressed.set(key, false));
|
||||
Input.keyPressed.forEach((key: string) => Input.keyPressed.set(key, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not a key was newly pressed Input frame.
|
||||
* If the key is still pressed from last frame and wasn't re-pressed, Input will return false.
|
||||
* @param key The key
|
||||
* @returns True if the key was just pressed, false otherwise
|
||||
*/
|
||||
static isKeyJustPressed(key: string): boolean {
|
||||
if(Input.keyJustPressed.has(key)){
|
||||
return Input.keyJustPressed.get(key)
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all of the keys that are newly pressed Input frame.
|
||||
* If a key is still pressed from last frame and wasn't re-pressed, it will not be in Input list.
|
||||
* @returns An array of all of the newly pressed keys.
|
||||
*/
|
||||
static getKeysJustPressed(): Array<string> {
|
||||
let keys = Array<string>();
|
||||
Input.keyJustPressed.forEach(key => {
|
||||
if(Input.keyJustPressed.get(key)){
|
||||
keys.push(key);
|
||||
}
|
||||
});
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not a key is being pressed.
|
||||
* @param key The key
|
||||
* @returns True if the key is currently pressed, false otherwise
|
||||
*/
|
||||
static isKeyPressed(key: string): boolean {
|
||||
if(Input.keyPressed.has(key)){
|
||||
return Input.keyPressed.get(key)
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the binding of an input name to keys
|
||||
* @param inputName The name of the input
|
||||
* @param keys The corresponding keys
|
||||
*/
|
||||
static changeKeyBinding(inputName: string, keys: Array<string>): void {
|
||||
Input.keyMap.set(inputName, keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all key bindings
|
||||
*/
|
||||
static clearAllKeyBindings(): void {
|
||||
Input.keyMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not an input was just pressed this frame
|
||||
* @param inputName The name of the input
|
||||
* @returns True if the input was just pressed, false otherwise
|
||||
*/
|
||||
static isJustPressed(inputName: string): boolean {
|
||||
if(Input.keyMap.has(inputName)){
|
||||
const keys = Input.keyMap.get(inputName);
|
||||
let justPressed = false;
|
||||
|
||||
for(let key of keys){
|
||||
justPressed = justPressed || Input.isKeyJustPressed(key);
|
||||
}
|
||||
|
||||
return justPressed;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not an input is currently pressed
|
||||
* @param inputName The name of the input
|
||||
* @returns True if the input is pressed, false otherwise
|
||||
*/
|
||||
static isPressed(inputName: string): boolean {
|
||||
if(Input.keyMap.has(inputName)){
|
||||
const keys = Input.keyMap.get(inputName);
|
||||
let pressed = false;
|
||||
|
||||
for(let key of keys){
|
||||
pressed = pressed || Input.isKeyPressed(key);
|
||||
}
|
||||
|
||||
return pressed;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the mouse was newly pressed Input frame
|
||||
* @returns True if the mouse was just pressed, false otherwise
|
||||
*/
|
||||
static isMouseJustPressed(): boolean {
|
||||
return Input.mouseJustPressed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the mouse is currently pressed
|
||||
* @returns True if the mouse is currently pressed, false otherwise
|
||||
*/
|
||||
static isMousePressed(): boolean {
|
||||
return Input.mousePressed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the user scrolled or not
|
||||
* @returns True if the user just scrolled Input frame, false otherwise
|
||||
*/
|
||||
static didJustScroll(): boolean {
|
||||
return Input.justScrolled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the direction of the scroll
|
||||
* @returns -1 if the user scrolled up, 1 if they scrolled down
|
||||
*/
|
||||
static getScrollDirection(): number {
|
||||
return Input.scrollDirection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of the player's mouse
|
||||
* @returns The mouse position stored as a Vec2
|
||||
*/
|
||||
static getMousePosition(): Vec2 {
|
||||
return Input.mousePosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of the player's mouse in the game world,
|
||||
* taking into consideration the scrolling of the viewport
|
||||
* @returns The mouse position stored as a Vec2
|
||||
*/
|
||||
static getGlobalMousePosition(): Vec2 {
|
||||
return Input.mousePosition.clone().add(Input.viewport.getOrigin());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of the last mouse press
|
||||
* @returns The mouse position stored as a Vec2
|
||||
*/
|
||||
static getMousePressPosition(): Vec2 {
|
||||
return Input.mousePressPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position of the last mouse press in the game world,
|
||||
* taking into consideration the scrolling of the viewport
|
||||
* @returns The mouse position stored as a Vec2
|
||||
*/
|
||||
static getGlobalMousePressPosition(): Vec2 {
|
||||
return Input.mousePressPosition.clone().add(Input.viewport.getOrigin());
|
||||
}
|
||||
}
|
47
src/Wolfie2D/Loop/EnvironmentInitializer.ts
Normal file
47
src/Wolfie2D/Loop/EnvironmentInitializer.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import {} from "../../index"; // This import allows us to modify the CanvasRenderingContext2D to add extra functionality
|
||||
// @ignorePage
|
||||
|
||||
/**
|
||||
* Sets up the environment of the game engine
|
||||
*/
|
||||
export default class EnvironmentInitializer {
|
||||
static setup(){
|
||||
CanvasRenderingContext2D.prototype.roundedRect = function(x: number, y: number, w: number, h: number, r: number): void {
|
||||
// Clamp the radius between 0 and the min of the width or height
|
||||
if(r < 0) r = 0;
|
||||
if(r > Math.min(w, h)) r = Math.min(w, h);
|
||||
|
||||
// Draw the rounded rect
|
||||
this.beginPath();
|
||||
|
||||
// Top
|
||||
this.moveTo(x + r, y);
|
||||
this.lineTo(x + w - r, y);
|
||||
this.arcTo(x + w, y, x + w, y + r, r);
|
||||
|
||||
// Right
|
||||
this.lineTo(x + w, y + h - r);
|
||||
this.arcTo(x + w, y + h, x + w - r, y + h, r);
|
||||
|
||||
// Bottom
|
||||
this.lineTo(x + r, y + h);
|
||||
this.arcTo(x, y + h, x, y + h - r, r);
|
||||
|
||||
// Left
|
||||
this.lineTo(x, y + r);
|
||||
this.arcTo(x, y, x + r, y, r)
|
||||
|
||||
this.closePath();
|
||||
}
|
||||
|
||||
CanvasRenderingContext2D.prototype.strokeRoundedRect = function(x, y, w, h, r){
|
||||
this.roundedRect(x, y, w, h, r);
|
||||
this.stroke();
|
||||
}
|
||||
|
||||
CanvasRenderingContext2D.prototype.fillRoundedRect = function(x, y, w, h, r){
|
||||
this.roundedRect(x, y, w, h, r);
|
||||
this.fill();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import EventQueue from "../Events/EventQueue";
|
||||
import InputReceiver from "../Input/InputReceiver";
|
||||
import Input from "../Input/Input";
|
||||
import InputHandler from "../Input/InputHandler";
|
||||
import Recorder from "../Playback/Recorder";
|
||||
import Debug from "../Debug/Debug";
|
||||
|
@ -14,6 +14,8 @@ import Color from "../Utils/Color";
|
|||
import GameOptions from "./GameOptions";
|
||||
import GameLoop from "./GameLoop";
|
||||
import FixedUpdateGameLoop from "./FixedUpdateGameLoop";
|
||||
import EnvironmentInitializer from "./EnvironmentInitializer";
|
||||
import Vec2 from "../DataTypes/Vec2";
|
||||
|
||||
/**
|
||||
* The main loop of the game engine.
|
||||
|
@ -22,6 +24,8 @@ import FixedUpdateGameLoop from "./FixedUpdateGameLoop";
|
|||
*/
|
||||
export default class Game {
|
||||
gameOptions: GameOptions;
|
||||
private showDebug: boolean;
|
||||
private showStats: boolean;
|
||||
|
||||
// The game loop
|
||||
private loop: GameLoop;
|
||||
|
@ -38,7 +42,6 @@ export default class Game {
|
|||
// All of the necessary subsystems that need to run here
|
||||
private eventQueue: EventQueue;
|
||||
private inputHandler: InputHandler;
|
||||
private inputReceiver: InputReceiver;
|
||||
private recorder: Recorder;
|
||||
private resourceManager: ResourceManager;
|
||||
private sceneManager: SceneManager;
|
||||
|
@ -50,9 +53,15 @@ export default class Game {
|
|||
* @param options The options for Game initialization
|
||||
*/
|
||||
constructor(options?: Record<string, any>){
|
||||
// Before anything else, build the environment
|
||||
EnvironmentInitializer.setup();
|
||||
|
||||
// Typecast the config object to a GameConfig object
|
||||
this.gameOptions = GameOptions.parse(options);
|
||||
|
||||
this.showDebug = this.gameOptions.showDebug;
|
||||
this.showStats = this.gameOptions.showStats;
|
||||
|
||||
// Create an instance of a game loop
|
||||
this.loop = new FixedUpdateGameLoop();
|
||||
|
||||
|
@ -74,22 +83,23 @@ export default class Game {
|
|||
Debug.initializeDebugCanvas(this.DEBUG_CANVAS, this.WIDTH, this.HEIGHT);
|
||||
Stats.initStats();
|
||||
|
||||
if(this.gameOptions.showStats) {
|
||||
// Find the stats output and make it no longer hidden
|
||||
document.getElementById("stats").hidden = false;
|
||||
}
|
||||
|
||||
// Size the viewport to the game canvas
|
||||
this.viewport = new Viewport();
|
||||
this.viewport.setCanvasSize(this.WIDTH, this.HEIGHT);
|
||||
this.viewport.setSize(this.WIDTH, this.HEIGHT);
|
||||
const viewportSize = new Vec2(this.WIDTH, this.HEIGHT);
|
||||
this.viewport = new Viewport(viewportSize.scaled(0.5), viewportSize);
|
||||
|
||||
// Initialize all necessary game subsystems
|
||||
this.eventQueue = EventQueue.getInstance();
|
||||
this.inputHandler = new InputHandler(this.GAME_CANVAS);
|
||||
this.inputReceiver = InputReceiver.getInstance();
|
||||
this.inputReceiver.setViewport(this.viewport);
|
||||
Input.initialize(this.viewport, this.gameOptions.inputs);
|
||||
this.recorder = new Recorder();
|
||||
this.resourceManager = ResourceManager.getInstance();
|
||||
this.sceneManager = new SceneManager(this.viewport, this.renderingManager);
|
||||
this.audioManager = AudioManager.getInstance();
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -134,7 +144,7 @@ export default class Game {
|
|||
this.eventQueue.update(deltaT);
|
||||
|
||||
// Update the input data structures so game objects can see the input
|
||||
this.inputReceiver.update(deltaT);
|
||||
Input.update(deltaT);
|
||||
|
||||
// Update the recording of the game
|
||||
this.recorder.update(deltaT);
|
||||
|
@ -163,7 +173,12 @@ export default class Game {
|
|||
this.sceneManager.render();
|
||||
|
||||
// Debug render
|
||||
Debug.render();
|
||||
Stats.render();
|
||||
if(this.showDebug){
|
||||
Debug.render();
|
||||
}
|
||||
|
||||
if(this.showStats){
|
||||
Stats.render();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,15 @@ export default class GameOptions {
|
|||
/** The color to clear the canvas to each frame */
|
||||
clearColor: {r: number, g: number, b: number}
|
||||
|
||||
/* A list of input bindings */
|
||||
inputs: Array<{name: string, keys: Array<string>}>;
|
||||
|
||||
/* Whether or not the debug rendering should occur */
|
||||
showDebug: boolean;
|
||||
|
||||
/* Whether or not the stats rendering should occur */
|
||||
showStats: boolean;
|
||||
|
||||
/**
|
||||
* Parses the data in the raw options object
|
||||
* @param options The game options as a Record
|
||||
|
@ -17,7 +26,10 @@ export default class GameOptions {
|
|||
let gOpt = new GameOptions();
|
||||
|
||||
gOpt.viewportSize = options.viewportSize ? options.viewportSize : {x: 800, y: 600};
|
||||
gOpt.clearColor = options.clearColor ? options.clearColor : {r: 255, g: 255, b: 255};
|
||||
gOpt.clearColor = options.clearColor ? options.clearColor : {r: 255, g: 255, b: 255};
|
||||
gOpt.inputs = options.inputs ? options.inputs : [];
|
||||
gOpt.showDebug = !!options.showDebug;
|
||||
gOpt.showStats = !!options.showStats;
|
||||
|
||||
return gOpt;
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
import InputReceiver from "../Input/InputReceiver";
|
||||
import Vec2 from "../DataTypes/Vec2";
|
||||
import Receiver from "../Events/Receiver";
|
||||
import Emitter from "../Events/Emitter";
|
||||
|
@ -60,8 +59,6 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
|
|||
pathfinding: boolean = false;
|
||||
|
||||
/*---------- GENERAL ----------*/
|
||||
/** An reference to the user input handler. This allows subclasses to easily access information about user input. */
|
||||
protected input: InputReceiver;
|
||||
/** An event receiver. */
|
||||
protected receiver: Receiver;
|
||||
/** An event emitter. */
|
||||
|
@ -79,7 +76,6 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
|
|||
|
||||
// Constructor docs are ignored, as the user should NOT create new GameNodes with a raw constructor
|
||||
constructor(){
|
||||
this.input = InputReceiver.getInstance();
|
||||
this._position = new Vec2(0, 0);
|
||||
this._position.setOnChange(() => this.positionChanged());
|
||||
this.receiver = new Receiver();
|
||||
|
@ -136,6 +132,7 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
|
|||
* @param velocity The velocity with which the object will move.
|
||||
*/
|
||||
finishMove(): void {
|
||||
console.log("finish");
|
||||
this.moving = false;
|
||||
this.position.add(this._velocity);
|
||||
if(this.pathfinding){
|
|
@ -1,6 +1,7 @@
|
|||
import CanvasNode from "./CanvasNode";
|
||||
import Color from "../Utils/Color";
|
||||
import Vec2 from "../DataTypes/Vec2";
|
||||
import Input from "../Input/Input";
|
||||
|
||||
/**
|
||||
* The representation of a UIElement - the parent class of things like buttons
|
||||
|
@ -79,8 +80,8 @@ export default abstract class UIElement extends CanvasNode {
|
|||
super.update(deltaT);
|
||||
|
||||
// See of this object was just clicked
|
||||
if(this.input.isMouseJustPressed()){
|
||||
let clickPos = this.input.getMousePressPosition();
|
||||
if(Input.isMouseJustPressed()){
|
||||
let clickPos = Input.getMousePressPosition();
|
||||
if(this.contains(clickPos.x, clickPos.y)){
|
||||
this.isClicked = true;
|
||||
|
||||
|
@ -96,14 +97,14 @@ export default abstract class UIElement extends CanvasNode {
|
|||
}
|
||||
|
||||
// If the mouse wasn't just pressed, then we definitely weren't clicked
|
||||
if(!this.input.isMousePressed()){
|
||||
if(!Input.isMousePressed()){
|
||||
if(this.isClicked){
|
||||
this.isClicked = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the mouse is hovering over this element
|
||||
let mousePos = this.input.getMousePosition();
|
||||
let mousePos = Input.getMousePosition();
|
||||
if(mousePos && this.contains(mousePos.x, mousePos.y)){
|
||||
this.isEntered = true;
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import Vec2 from "../../DataTypes/Vec2";
|
||||
import Input from "../../Input/Input";
|
||||
import Color from "../../Utils/Color";
|
||||
import MathUtils from "../../Utils/MathUtils";
|
||||
import UIElement from "../UIElement";
|
||||
|
@ -52,7 +53,7 @@ export default class Slider extends UIElement {
|
|||
super.update(deltaT);
|
||||
|
||||
if(this.isClicked){
|
||||
let val = MathUtils.invLerp(this.position.x - this.size.x/2, this.position.x + this.size.x/2, this.input.getMousePosition().x);
|
||||
let val = MathUtils.invLerp(this.position.x - this.size.x/2, this.position.x + this.size.x/2, Input.getMousePosition().x);
|
||||
this.value = MathUtils.clamp01(val);
|
||||
this.valueChanged();
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import Vec2 from "../../DataTypes/Vec2";
|
||||
import Color from "../../Utils/Color";
|
||||
import Label from "./Label";
|
||||
import Input from "../../Input/Input";
|
||||
|
||||
/** A text input UIElement */
|
||||
export default class TextInput extends Label {
|
||||
|
@ -26,8 +27,8 @@ export default class TextInput extends Label {
|
|||
update(deltaT: number): void {
|
||||
super.update(deltaT);
|
||||
|
||||
if(this.input.isMouseJustPressed()){
|
||||
let clickPos = this.input.getMousePressPosition();
|
||||
if(Input.isMouseJustPressed()){
|
||||
let clickPos = Input.getMousePressPosition();
|
||||
if(this.contains(clickPos.x, clickPos.y)){
|
||||
this.focused = true;
|
||||
this.cursorCounter = 30;
|
||||
|
@ -37,15 +38,15 @@ export default class TextInput extends Label {
|
|||
}
|
||||
|
||||
if(this.focused){
|
||||
let keys = this.input.getKeysJustPressed();
|
||||
let keys = Input.getKeysJustPressed();
|
||||
let nums = "1234567890";
|
||||
let specialChars = "`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?";
|
||||
let letters = "qwertyuiopasdfghjklzxcvbnm";
|
||||
let mask = nums + specialChars + letters;
|
||||
keys = keys.filter(key => mask.includes(key));
|
||||
let shiftPressed = this.input.isPressed("shift");
|
||||
let backspacePressed = this.input.isJustPressed("backspace");
|
||||
let spacePressed = this.input.isJustPressed("space");
|
||||
let shiftPressed = Input.isKeyPressed("shift");
|
||||
let backspacePressed = Input.isKeyJustPressed("backspace");
|
||||
let spacePressed = Input.isKeyJustPressed("space");
|
||||
|
||||
if(backspacePressed){
|
||||
this.text = this.text.substring(0, this.text.length - 1);
|
|
@ -41,7 +41,7 @@ import AreaCollision from "../DataTypes/Physics/AreaCollision";
|
|||
* Cons:
|
||||
* - Nodes that are processed early have movement priority over other nodes. This can lead to some undesirable interactions.
|
||||
*/
|
||||
export default class TestPhysicsManager extends PhysicsManager {
|
||||
export default class BasicPhysicsManager extends PhysicsManager {
|
||||
|
||||
/** The array of static nodes */
|
||||
protected staticNodes: Array<Physical>;
|
||||
|
@ -52,7 +52,7 @@ export default class TestPhysicsManager extends PhysicsManager {
|
|||
/** The array of tilemaps */
|
||||
protected tilemaps: Array<Tilemap>;
|
||||
|
||||
constructor(){
|
||||
constructor(options: Record<string, any>){
|
||||
super();
|
||||
this.staticNodes = new Array();
|
||||
this.dynamicNodes = new Array();
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user