restructured project, added default scene, fixed viewport bug

This commit is contained in:
Joe Weaver 2021-02-02 18:24:57 -05:00
parent 1512fa5c8f
commit 681d63f202
148 changed files with 573 additions and 2928 deletions

View File

@ -1,5 +1,5 @@
{
"name": "gameengine",
"name": "wolfie2d",
"version": "1.0.0",
"description": "A game engine written in TypeScript",
"main": "./dist/main.js",

View File

@ -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();
}
}
}

View File

@ -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)];
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);
}
}
}
}

View File

@ -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();
// }
}
}

View File

@ -1,3 +1,5 @@
// @ignorePage
/**
* A placeholder function for No Operation. Does nothing
*/

291
src/Wolfie2D/Input/Input.ts Normal file
View 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());
}
}

View 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();
}
}
}

View File

@ -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
if(this.showDebug){
Debug.render();
}
if(this.showStats){
Stats.render();
}
}
}

View File

@ -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
@ -18,6 +27,9 @@ export default class GameOptions {
gOpt.viewportSize = options.viewportSize ? options.viewportSize : {x: 800, y: 600};
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;
}

View File

@ -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){

View File

@ -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;

View File

@ -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();
}

View File

@ -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);

View File

@ -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