restructured project, added default scene, fixed viewport bug
This commit is contained in:
parent
1512fa5c8f
commit
681d63f202
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "gameengine",
|
"name": "wolfie2d",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "A game engine written in TypeScript",
|
"description": "A game engine written in TypeScript",
|
||||||
"main": "./dist/main.js",
|
"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
|
* 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 EventQueue from "../Events/EventQueue";
|
||||||
import InputReceiver from "../Input/InputReceiver";
|
import Input from "../Input/Input";
|
||||||
import InputHandler from "../Input/InputHandler";
|
import InputHandler from "../Input/InputHandler";
|
||||||
import Recorder from "../Playback/Recorder";
|
import Recorder from "../Playback/Recorder";
|
||||||
import Debug from "../Debug/Debug";
|
import Debug from "../Debug/Debug";
|
||||||
|
@ -14,6 +14,8 @@ import Color from "../Utils/Color";
|
||||||
import GameOptions from "./GameOptions";
|
import GameOptions from "./GameOptions";
|
||||||
import GameLoop from "./GameLoop";
|
import GameLoop from "./GameLoop";
|
||||||
import FixedUpdateGameLoop from "./FixedUpdateGameLoop";
|
import FixedUpdateGameLoop from "./FixedUpdateGameLoop";
|
||||||
|
import EnvironmentInitializer from "./EnvironmentInitializer";
|
||||||
|
import Vec2 from "../DataTypes/Vec2";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main loop of the game engine.
|
* The main loop of the game engine.
|
||||||
|
@ -22,6 +24,8 @@ import FixedUpdateGameLoop from "./FixedUpdateGameLoop";
|
||||||
*/
|
*/
|
||||||
export default class Game {
|
export default class Game {
|
||||||
gameOptions: GameOptions;
|
gameOptions: GameOptions;
|
||||||
|
private showDebug: boolean;
|
||||||
|
private showStats: boolean;
|
||||||
|
|
||||||
// The game loop
|
// The game loop
|
||||||
private loop: GameLoop;
|
private loop: GameLoop;
|
||||||
|
@ -38,7 +42,6 @@ export default class Game {
|
||||||
// All of the necessary subsystems that need to run here
|
// All of the necessary subsystems that need to run here
|
||||||
private eventQueue: EventQueue;
|
private eventQueue: EventQueue;
|
||||||
private inputHandler: InputHandler;
|
private inputHandler: InputHandler;
|
||||||
private inputReceiver: InputReceiver;
|
|
||||||
private recorder: Recorder;
|
private recorder: Recorder;
|
||||||
private resourceManager: ResourceManager;
|
private resourceManager: ResourceManager;
|
||||||
private sceneManager: SceneManager;
|
private sceneManager: SceneManager;
|
||||||
|
@ -50,9 +53,15 @@ export default class Game {
|
||||||
* @param options The options for Game initialization
|
* @param options The options for Game initialization
|
||||||
*/
|
*/
|
||||||
constructor(options?: Record<string, any>){
|
constructor(options?: Record<string, any>){
|
||||||
|
// Before anything else, build the environment
|
||||||
|
EnvironmentInitializer.setup();
|
||||||
|
|
||||||
// Typecast the config object to a GameConfig object
|
// Typecast the config object to a GameConfig object
|
||||||
this.gameOptions = GameOptions.parse(options);
|
this.gameOptions = GameOptions.parse(options);
|
||||||
|
|
||||||
|
this.showDebug = this.gameOptions.showDebug;
|
||||||
|
this.showStats = this.gameOptions.showStats;
|
||||||
|
|
||||||
// Create an instance of a game loop
|
// Create an instance of a game loop
|
||||||
this.loop = new FixedUpdateGameLoop();
|
this.loop = new FixedUpdateGameLoop();
|
||||||
|
|
||||||
|
@ -74,22 +83,23 @@ export default class Game {
|
||||||
Debug.initializeDebugCanvas(this.DEBUG_CANVAS, this.WIDTH, this.HEIGHT);
|
Debug.initializeDebugCanvas(this.DEBUG_CANVAS, this.WIDTH, this.HEIGHT);
|
||||||
Stats.initStats();
|
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
|
// Size the viewport to the game canvas
|
||||||
this.viewport = new Viewport();
|
const viewportSize = new Vec2(this.WIDTH, this.HEIGHT);
|
||||||
this.viewport.setCanvasSize(this.WIDTH, this.HEIGHT);
|
this.viewport = new Viewport(viewportSize.scaled(0.5), viewportSize);
|
||||||
this.viewport.setSize(this.WIDTH, this.HEIGHT);
|
|
||||||
|
|
||||||
// Initialize all necessary game subsystems
|
// Initialize all necessary game subsystems
|
||||||
this.eventQueue = EventQueue.getInstance();
|
this.eventQueue = EventQueue.getInstance();
|
||||||
this.inputHandler = new InputHandler(this.GAME_CANVAS);
|
this.inputHandler = new InputHandler(this.GAME_CANVAS);
|
||||||
this.inputReceiver = InputReceiver.getInstance();
|
Input.initialize(this.viewport, this.gameOptions.inputs);
|
||||||
this.inputReceiver.setViewport(this.viewport);
|
|
||||||
this.recorder = new Recorder();
|
this.recorder = new Recorder();
|
||||||
this.resourceManager = ResourceManager.getInstance();
|
this.resourceManager = ResourceManager.getInstance();
|
||||||
this.sceneManager = new SceneManager(this.viewport, this.renderingManager);
|
this.sceneManager = new SceneManager(this.viewport, this.renderingManager);
|
||||||
this.audioManager = AudioManager.getInstance();
|
this.audioManager = AudioManager.getInstance();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -134,7 +144,7 @@ export default class Game {
|
||||||
this.eventQueue.update(deltaT);
|
this.eventQueue.update(deltaT);
|
||||||
|
|
||||||
// Update the input data structures so game objects can see the input
|
// 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
|
// Update the recording of the game
|
||||||
this.recorder.update(deltaT);
|
this.recorder.update(deltaT);
|
||||||
|
@ -163,7 +173,12 @@ export default class Game {
|
||||||
this.sceneManager.render();
|
this.sceneManager.render();
|
||||||
|
|
||||||
// Debug render
|
// Debug render
|
||||||
Debug.render();
|
if(this.showDebug){
|
||||||
Stats.render();
|
Debug.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.showStats){
|
||||||
|
Stats.render();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,6 +8,15 @@ export default class GameOptions {
|
||||||
/** The color to clear the canvas to each frame */
|
/** The color to clear the canvas to each frame */
|
||||||
clearColor: {r: number, g: number, b: number}
|
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
|
* Parses the data in the raw options object
|
||||||
* @param options The game options as a Record
|
* @param options The game options as a Record
|
||||||
|
@ -17,7 +26,10 @@ export default class GameOptions {
|
||||||
let gOpt = new GameOptions();
|
let gOpt = new GameOptions();
|
||||||
|
|
||||||
gOpt.viewportSize = options.viewportSize ? options.viewportSize : {x: 800, y: 600};
|
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;
|
return gOpt;
|
||||||
}
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
import InputReceiver from "../Input/InputReceiver";
|
|
||||||
import Vec2 from "../DataTypes/Vec2";
|
import Vec2 from "../DataTypes/Vec2";
|
||||||
import Receiver from "../Events/Receiver";
|
import Receiver from "../Events/Receiver";
|
||||||
import Emitter from "../Events/Emitter";
|
import Emitter from "../Events/Emitter";
|
||||||
|
@ -60,8 +59,6 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
|
||||||
pathfinding: boolean = false;
|
pathfinding: boolean = false;
|
||||||
|
|
||||||
/*---------- GENERAL ----------*/
|
/*---------- GENERAL ----------*/
|
||||||
/** An reference to the user input handler. This allows subclasses to easily access information about user input. */
|
|
||||||
protected input: InputReceiver;
|
|
||||||
/** An event receiver. */
|
/** An event receiver. */
|
||||||
protected receiver: Receiver;
|
protected receiver: Receiver;
|
||||||
/** An event emitter. */
|
/** 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 docs are ignored, as the user should NOT create new GameNodes with a raw constructor
|
||||||
constructor(){
|
constructor(){
|
||||||
this.input = InputReceiver.getInstance();
|
|
||||||
this._position = new Vec2(0, 0);
|
this._position = new Vec2(0, 0);
|
||||||
this._position.setOnChange(() => this.positionChanged());
|
this._position.setOnChange(() => this.positionChanged());
|
||||||
this.receiver = new Receiver();
|
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.
|
* @param velocity The velocity with which the object will move.
|
||||||
*/
|
*/
|
||||||
finishMove(): void {
|
finishMove(): void {
|
||||||
|
console.log("finish");
|
||||||
this.moving = false;
|
this.moving = false;
|
||||||
this.position.add(this._velocity);
|
this.position.add(this._velocity);
|
||||||
if(this.pathfinding){
|
if(this.pathfinding){
|
|
@ -1,6 +1,7 @@
|
||||||
import CanvasNode from "./CanvasNode";
|
import CanvasNode from "./CanvasNode";
|
||||||
import Color from "../Utils/Color";
|
import Color from "../Utils/Color";
|
||||||
import Vec2 from "../DataTypes/Vec2";
|
import Vec2 from "../DataTypes/Vec2";
|
||||||
|
import Input from "../Input/Input";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The representation of a UIElement - the parent class of things like buttons
|
* 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);
|
super.update(deltaT);
|
||||||
|
|
||||||
// See of this object was just clicked
|
// See of this object was just clicked
|
||||||
if(this.input.isMouseJustPressed()){
|
if(Input.isMouseJustPressed()){
|
||||||
let clickPos = this.input.getMousePressPosition();
|
let clickPos = Input.getMousePressPosition();
|
||||||
if(this.contains(clickPos.x, clickPos.y)){
|
if(this.contains(clickPos.x, clickPos.y)){
|
||||||
this.isClicked = true;
|
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 the mouse wasn't just pressed, then we definitely weren't clicked
|
||||||
if(!this.input.isMousePressed()){
|
if(!Input.isMousePressed()){
|
||||||
if(this.isClicked){
|
if(this.isClicked){
|
||||||
this.isClicked = false;
|
this.isClicked = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the mouse is hovering over this element
|
// 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)){
|
if(mousePos && this.contains(mousePos.x, mousePos.y)){
|
||||||
this.isEntered = true;
|
this.isEntered = true;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import Vec2 from "../../DataTypes/Vec2";
|
import Vec2 from "../../DataTypes/Vec2";
|
||||||
|
import Input from "../../Input/Input";
|
||||||
import Color from "../../Utils/Color";
|
import Color from "../../Utils/Color";
|
||||||
import MathUtils from "../../Utils/MathUtils";
|
import MathUtils from "../../Utils/MathUtils";
|
||||||
import UIElement from "../UIElement";
|
import UIElement from "../UIElement";
|
||||||
|
@ -52,7 +53,7 @@ export default class Slider extends UIElement {
|
||||||
super.update(deltaT);
|
super.update(deltaT);
|
||||||
|
|
||||||
if(this.isClicked){
|
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.value = MathUtils.clamp01(val);
|
||||||
this.valueChanged();
|
this.valueChanged();
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import Vec2 from "../../DataTypes/Vec2";
|
import Vec2 from "../../DataTypes/Vec2";
|
||||||
import Color from "../../Utils/Color";
|
import Color from "../../Utils/Color";
|
||||||
import Label from "./Label";
|
import Label from "./Label";
|
||||||
|
import Input from "../../Input/Input";
|
||||||
|
|
||||||
/** A text input UIElement */
|
/** A text input UIElement */
|
||||||
export default class TextInput extends Label {
|
export default class TextInput extends Label {
|
||||||
|
@ -26,8 +27,8 @@ export default class TextInput extends Label {
|
||||||
update(deltaT: number): void {
|
update(deltaT: number): void {
|
||||||
super.update(deltaT);
|
super.update(deltaT);
|
||||||
|
|
||||||
if(this.input.isMouseJustPressed()){
|
if(Input.isMouseJustPressed()){
|
||||||
let clickPos = this.input.getMousePressPosition();
|
let clickPos = Input.getMousePressPosition();
|
||||||
if(this.contains(clickPos.x, clickPos.y)){
|
if(this.contains(clickPos.x, clickPos.y)){
|
||||||
this.focused = true;
|
this.focused = true;
|
||||||
this.cursorCounter = 30;
|
this.cursorCounter = 30;
|
||||||
|
@ -37,15 +38,15 @@ export default class TextInput extends Label {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.focused){
|
if(this.focused){
|
||||||
let keys = this.input.getKeysJustPressed();
|
let keys = Input.getKeysJustPressed();
|
||||||
let nums = "1234567890";
|
let nums = "1234567890";
|
||||||
let specialChars = "`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?";
|
let specialChars = "`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?";
|
||||||
let letters = "qwertyuiopasdfghjklzxcvbnm";
|
let letters = "qwertyuiopasdfghjklzxcvbnm";
|
||||||
let mask = nums + specialChars + letters;
|
let mask = nums + specialChars + letters;
|
||||||
keys = keys.filter(key => mask.includes(key));
|
keys = keys.filter(key => mask.includes(key));
|
||||||
let shiftPressed = this.input.isPressed("shift");
|
let shiftPressed = Input.isKeyPressed("shift");
|
||||||
let backspacePressed = this.input.isJustPressed("backspace");
|
let backspacePressed = Input.isKeyJustPressed("backspace");
|
||||||
let spacePressed = this.input.isJustPressed("space");
|
let spacePressed = Input.isKeyJustPressed("space");
|
||||||
|
|
||||||
if(backspacePressed){
|
if(backspacePressed){
|
||||||
this.text = this.text.substring(0, this.text.length - 1);
|
this.text = this.text.substring(0, this.text.length - 1);
|
|
@ -41,7 +41,7 @@ import AreaCollision from "../DataTypes/Physics/AreaCollision";
|
||||||
* Cons:
|
* Cons:
|
||||||
* - Nodes that are processed early have movement priority over other nodes. This can lead to some undesirable interactions.
|
* - 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 */
|
/** The array of static nodes */
|
||||||
protected staticNodes: Array<Physical>;
|
protected staticNodes: Array<Physical>;
|
||||||
|
@ -52,7 +52,7 @@ export default class TestPhysicsManager extends PhysicsManager {
|
||||||
/** The array of tilemaps */
|
/** The array of tilemaps */
|
||||||
protected tilemaps: Array<Tilemap>;
|
protected tilemaps: Array<Tilemap>;
|
||||||
|
|
||||||
constructor(){
|
constructor(options: Record<string, any>){
|
||||||
super();
|
super();
|
||||||
this.staticNodes = new Array();
|
this.staticNodes = new Array();
|
||||||
this.dynamicNodes = 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