converted all code to work with AABBs

This commit is contained in:
Joe Weaver 2020-10-05 15:01:26 -04:00
parent a32066468f
commit 254462993a
26 changed files with 383 additions and 240 deletions

View File

@ -1,11 +1,13 @@
import Shape from "./Shape";
import Vec2 from "./Vec2"; import Vec2 from "./Vec2";
export default class AABB { export default class AABB extends Shape {
protected center: Vec2; protected center: Vec2;
protected halfSize: Vec2; protected halfSize: Vec2;
constructor(center?: Vec2, halfSize?: Vec2){ constructor(center?: Vec2, halfSize?: Vec2){
super();
this.center = center ? center : new Vec2(0, 0); this.center = center ? center : new Vec2(0, 0);
this.halfSize = halfSize ? halfSize : new Vec2(0, 0); this.halfSize = halfSize ? halfSize : new Vec2(0, 0);
} }
@ -26,6 +28,22 @@ export default class AABB {
return this.halfSize.y; return this.halfSize.y;
} }
get top(): number {
return this.y - this.hh;
}
get bottom(): number {
return this.y + this.hh;
}
get left(): number {
return this.x - this.hw;
}
get right(): number {
return this.x + this.hw;
}
getCenter(): Vec2 { getCenter(): Vec2 {
return this.center; return this.center;
} }
@ -34,6 +52,10 @@ export default class AABB {
this.center = center; this.center = center;
} }
getBoundingRect(): AABB {
return this;
}
getHalfSize(): Vec2 { getHalfSize(): Vec2 {
return this.halfSize; return this.halfSize;
} }
@ -100,4 +122,19 @@ export default class AABB {
return true; return true;
} }
// TODO - Implement this generally and use it in the tilemap
overlapArea(other: AABB): number {
let leftx = Math.max(this.x - this.hw, other.x - other.hw);
let rightx = Math.min(this.x + this.hw, other.x + other.hw);
let dx = rightx - leftx;
let lefty = Math.max(this.y - this.hh, other.y - other.hh);
let righty = Math.min(this.y + this.hh, other.y + other.hh);
let dy = righty - lefty;
if(dx < 0 || dy < 0) return 0;
return dx*dy;
}
} }

View File

@ -123,8 +123,9 @@ export default class QuadTree<T extends Region & Unique> implements Collection {
} }
/** /**
* Returns all items at this point. * Returns all items in this region
* @param point The point to query at * @param boundary The region to check
* @param inclusionCheck Allows for additional inclusion checks to further refine searches
*/ */
queryRegion(boundary: AABB): Array<T> { queryRegion(boundary: AABB): Array<T> {
// A matrix to keep track of our results // A matrix to keep track of our results
@ -212,6 +213,7 @@ export default class QuadTree<T extends Region & Unique> implements Collection {
* @param ctx * @param ctx
*/ */
public render_demo(ctx: CanvasRenderingContext2D): void { public render_demo(ctx: CanvasRenderingContext2D): void {
return;
ctx.strokeStyle = "#0000FF"; ctx.strokeStyle = "#0000FF";
ctx.strokeRect(this.boundary.x - this.boundary.hw, this.boundary.y - this.boundary.hh, 2*this.boundary.hw, 2*this.boundary.hh); ctx.strokeRect(this.boundary.x - this.boundary.hw, this.boundary.y - this.boundary.hh, 2*this.boundary.hw, 2*this.boundary.hh);

8
src/DataTypes/Shape.ts Normal file
View File

@ -0,0 +1,8 @@
import AABB from "./AABB";
import Vec2 from "./Vec2";
export default abstract class Shape {
abstract setCenter(center: Vec2): void;
abstract getCenter(): Vec2;
abstract getBoundingRect(): AABB;
}

View File

@ -4,26 +4,31 @@
export default class Vec2 { export default class Vec2 {
// Store x and y in an array // Store x and y in an array
private vec: Float32Array; //private vec: Float32Array;
protected _x: number;
protected _y: number;
/** /**
* When this vector changes its value, do something * When this vector changes its value, do something
*/ */
private onChange: Function; private onChange: Function = () => {};
constructor(x: number = 0, y: number = 0) { constructor(x: number = 0, y: number = 0) {
this.vec = new Float32Array(2); // this.vec = new Float32Array(2);
this.vec[0] = x; // this.vec[0] = x;
this.vec[1] = y; // this.vec[1] = y;
this._x = x;
this._y = y;
} }
// Expose x and y with getters and setters // Expose x and y with getters and setters
get x() { get x() {
return this.vec[0]; return this._x; //this.vec[0];
} }
set x(x: number) { set x(x: number) {
this.vec[0] = x; this._x = x;//this.vec[0] = x;
if(this.onChange){ if(this.onChange){
this.onChange(); this.onChange();
@ -31,11 +36,11 @@ export default class Vec2 {
} }
get y() { get y() {
return this.vec[1]; return this._y;//this.vec[1];
} }
set y(y: number) { set y(y: number) {
this.vec[1] = y; this._y = y;//this.vec[1] = y;
if(this.onChange){ if(this.onChange){
this.onChange(); this.onChange();
@ -181,6 +186,17 @@ export default class Vec2 {
return this; return this;
} }
/**
* Divides this vector with another vector element-wise
* @param other
*/
div(other: Vec2): Vec2 {
if(other.x === 0 || other.y === 0) throw "Divide by zero error";
this.x /= other.x;
this.y /= other.y;
return this;
}
/** /**
* Returns the squared distance between this vector and another vector * Returns the squared distance between this vector and another vector
* @param other * @param other
@ -218,4 +234,8 @@ export default class Vec2 {
setOnChange(f: Function): void { setOnChange(f: Function): void {
this.onChange = f; this.onChange = f;
} }
getOnChange(): string {
return this.onChange.toString();
}
} }

View File

@ -120,7 +120,7 @@ export default class InputReceiver{
} }
getGlobalMousePosition(): Vec2 { getGlobalMousePosition(): Vec2 {
return this.mousePosition.clone().add(this.viewport.getPosition()); return this.mousePosition.clone().add(this.viewport.getOrigin());
} }
getMousePressPosition(): Vec2 { getMousePressPosition(): Vec2 {
@ -128,7 +128,7 @@ export default class InputReceiver{
} }
getGlobalMousePressPosition(): Vec2 { getGlobalMousePressPosition(): Vec2 {
return this.mousePressPosition.clone().add(this.viewport.getPosition()); return this.mousePressPosition.clone().add(this.viewport.getOrigin());
} }
setViewport(viewport: Viewport): void { setViewport(viewport: Viewport): void {

View File

@ -48,7 +48,6 @@ export default class GameLoop {
constructor(config?: object){ constructor(config?: object){
// Typecast the config object to a GameConfig object // Typecast the config object to a GameConfig object
let gameConfig = config ? <GameConfig>config : new GameConfig(); let gameConfig = config ? <GameConfig>config : new GameConfig();
console.log(gameConfig)
this.maxFPS = 60; this.maxFPS = 60;
this.simulationTimestep = Math.floor(1000/this.maxFPS); this.simulationTimestep = Math.floor(1000/this.maxFPS);

View File

@ -9,6 +9,7 @@ import Button from "./Nodes/UIElements/Button";
import Layer from "./Scene/Layer"; import Layer from "./Scene/Layer";
import SecondScene from "./SecondScene"; import SecondScene from "./SecondScene";
import { GameEventType } from "./Events/GameEventType"; import { GameEventType } from "./Events/GameEventType";
import SceneGraphQuadTree from "./SceneGraph/SceneGraphQuadTree";
export default class MainScene extends Scene { export default class MainScene extends Scene {
@ -17,7 +18,7 @@ export default class MainScene extends Scene {
this.load.tilemap("background", "assets/tilemaps/Background.json"); this.load.tilemap("background", "assets/tilemaps/Background.json");
this.load.image("player", "assets/sprites/player.png"); this.load.image("player", "assets/sprites/player.png");
this.load.audio("player_jump", "assets/sounds/jump-3.wav"); this.load.audio("player_jump", "assets/sounds/jump-3.wav");
this.load.audio("level_music", "assets/sounds/level.wav"); //this.load.audio("level_music", "assets/sounds/level.wav");
let loadingScreen = this.addLayer(); let loadingScreen = this.addLayer();
let box = this.add.graphic(Rect, loadingScreen, new Vec2(200, 300), new Vec2(400, 60)); let box = this.add.graphic(Rect, loadingScreen, new Vec2(200, 300), new Vec2(400, 60));
@ -35,17 +36,23 @@ export default class MainScene extends Scene {
} }
startScene(){ startScene(){
// Set world size
this.worldSize = new Vec2(2560, 1280)
// Use a quadtree
this.sceneGraph = new SceneGraphQuadTree(this.viewport, this);
// Add the background tilemap // Add the background tilemap
let backgroundTilemapLayer = this.add.tilemap("background")[0]; let backgroundTilemapLayer = this.add.tilemap("background", new Vec2(4, 4))[0];
// ...and make it have parallax // ...and make it have parallax
backgroundTilemapLayer.setParallax(0.5, 0.8); backgroundTilemapLayer.setParallax(0.5, 0.8);
backgroundTilemapLayer.setAlpha(0.5); backgroundTilemapLayer.setAlpha(0.5);
// Add the music and start playing it on a loop // Add the music and start playing it on a loop
this.emitter.fireEvent(GameEventType.PLAY_SOUND, {key: "level_music", loop: true, holdReference: true}); //this.emitter.fireEvent(GameEventType.PLAY_SOUND, {key: "level_music", loop: true, holdReference: true});
// Add the tilemap // Add the tilemap
this.add.tilemap("platformer"); this.add.tilemap("platformer", new Vec2(4, 4));
// Create the main game layer // Create the main game layer
let mainLayer = this.addLayer(); let mainLayer = this.addLayer();

View File

@ -13,6 +13,7 @@ export default abstract class CanvasNode extends GameNode implements Region {
constructor(){ constructor(){
super(); super();
this.position.setOnChange(this.positionChanged);
this._size = new Vec2(0, 0); this._size = new Vec2(0, 0);
this._size.setOnChange(this.sizeChanged); this._size.setOnChange(this.sizeChanged);
this._scale = new Vec2(1, 1); this._scale = new Vec2(1, 1);
@ -37,7 +38,7 @@ export default abstract class CanvasNode extends GameNode implements Region {
set scale(scale: Vec2){ set scale(scale: Vec2){
this._scale = scale; this._scale = scale;
this._scale.setOnChange(this.sizeChanged); this._scale.setOnChange(this.scaleChanged);
this.scaleChanged(); this.scaleChanged();
} }
@ -68,15 +69,15 @@ export default abstract class CanvasNode extends GameNode implements Region {
this.scale = scale; this.scale = scale;
} }
positionChanged = (): void => { protected positionChanged = (): void => {
this.updateBoundary(); this.updateBoundary();
} }
sizeChanged = (): void => { protected sizeChanged = (): void => {
this.updateBoundary(); this.updateBoundary();
} }
scaleChanged = (): void => { protected scaleChanged = (): void => {
this.updateBoundary(); this.updateBoundary();
} }

View File

@ -6,6 +6,7 @@ import Emitter from "../Events/Emitter";
import Scene from "../Scene/Scene"; import Scene from "../Scene/Scene";
import Layer from "../Scene/Layer"; import Layer from "../Scene/Layer";
import { Positioned, Unique } from "../DataTypes/Interfaces/Descriptors" import { Positioned, Unique } from "../DataTypes/Interfaces/Descriptors"
import UIElement from "./UIElement";
/** /**
* The representation of an object in the game world * The representation of an object in the game world
@ -76,11 +77,11 @@ export default abstract class GameNode implements Positioned, Unique {
/** /**
* Called if the position vector is modified or replaced * Called if the position vector is modified or replaced
*/ */
protected positionChanged(){} protected positionChanged = (): void => {};
// TODO - This doesn't seem ideal. Is there a better way to do this? // TODO - This doesn't seem ideal. Is there a better way to do this?
protected getViewportOriginWithParallax(): Vec2 { getViewportOriginWithParallax(): Vec2 {
return this.scene.getViewport().getPosition().clone().mult(this.layer.getParallax()); return this.scene.getViewport().getOrigin().mult(this.layer.getParallax());
} }
abstract update(deltaT: number): void; abstract update(deltaT: number): void;

View File

@ -30,8 +30,15 @@ export default class Sprite extends CanvasNode {
render(ctx: CanvasRenderingContext2D): void { render(ctx: CanvasRenderingContext2D): void {
let image = ResourceManager.getInstance().getImage(this.imageId); let image = ResourceManager.getInstance().getImage(this.imageId);
let origin = this.getViewportOriginWithParallax(); let origin = this.getViewportOriginWithParallax();
ctx.drawImage(image, ctx.drawImage(image,
this.imageOffset.x, this.imageOffset.y, this.size.x, this.size.y, this.imageOffset.x, this.imageOffset.y, this.size.x, this.size.y,
this.position.x - origin.x, this.position.y - origin.y, this.size.x * this.scale.x, this.size.y * this.scale.y); this.position.x - origin.x - this.size.x*this.scale.x/2, this.position.y - origin.y - this.size.y*this.scale.y/2,
this.size.x * this.scale.x, this.size.y * this.scale.y);
ctx.lineWidth = 4;
ctx.strokeStyle = "#00FF00"
let b = this.getBoundary();
ctx.strokeRect(b.x - b.hw - origin.x, b.y - b.hh - origin.y, b.hw*2, b.hh*2);
} }
} }

View File

@ -9,7 +9,7 @@ import { TiledTilemapData, TiledLayerData } from "../DataTypes/Tilesets/TiledDat
export default abstract class Tilemap extends GameNode { export default abstract class Tilemap extends GameNode {
// A tileset represents the tiles within one specific image loaded from a file // A tileset represents the tiles within one specific image loaded from a file
protected tilesets: Array<Tileset>; protected tilesets: Array<Tileset>;
protected worldSize: Vec2; protected size: Vec2;
protected tileSize: Vec2; protected tileSize: Vec2;
protected scale: Vec2; protected scale: Vec2;
public data: Array<number>; public data: Array<number>;
@ -17,23 +17,23 @@ export default abstract class Tilemap extends GameNode {
public visible: boolean; public visible: boolean;
// TODO: Make this no longer be specific to Tiled // TODO: Make this no longer be specific to Tiled
constructor(tilemapData: TiledTilemapData, layer: TiledLayerData, tilesets: Array<Tileset>) { constructor(tilemapData: TiledTilemapData, layer: TiledLayerData, tilesets: Array<Tileset>, scale: Vec2) {
super(); super();
this.tilesets = tilesets; this.tilesets = tilesets;
this.worldSize = new Vec2(0, 0); this.size = new Vec2(0, 0);
this.tileSize = new Vec2(0, 0); this.tileSize = new Vec2(0, 0);
// Defer parsing of the data to child classes - this allows for isometric vs. orthographic tilemaps and handling of Tiled data or other data // Defer parsing of the data to child classes - this allows for isometric vs. orthographic tilemaps and handling of Tiled data or other data
this.parseTilemapData(tilemapData, layer); this.parseTilemapData(tilemapData, layer);
this.scale = new Vec2(4, 4); this.scale = scale.clone();
} }
getTilesets(): Tileset[] { getTilesets(): Tileset[] {
return this.tilesets; return this.tilesets;
} }
getWorldSize(): Vec2 { getsize(): Vec2 {
return this.worldSize; return this.size;
} }
getTileSize(): Vec2 { getTileSize(): Vec2 {

View File

@ -14,7 +14,7 @@ export default class OrthogonalTilemap extends Tilemap {
* @param layer * @param layer
*/ */
protected parseTilemapData(tilemapData: TiledTilemapData, layer: TiledLayerData): void { protected parseTilemapData(tilemapData: TiledTilemapData, layer: TiledLayerData): void {
this.worldSize.set(tilemapData.width, tilemapData.height); this.size.set(tilemapData.width, tilemapData.height);
this.tileSize.set(tilemapData.tilewidth, tilemapData.tileheight); this.tileSize.set(tilemapData.tilewidth, tilemapData.tileheight);
this.data = layer.data; this.data = layer.data;
this.visible = layer.visible; this.visible = layer.visible;
@ -34,12 +34,12 @@ export default class OrthogonalTilemap extends Tilemap {
*/ */
getTileAt(worldCoords: Vec2): number { getTileAt(worldCoords: Vec2): number {
let localCoords = this.getColRowAt(worldCoords); let localCoords = this.getColRowAt(worldCoords);
if(localCoords.x < 0 || localCoords.x >= this.worldSize.x || localCoords.y < 0 || localCoords.y >= this.worldSize.y){ if(localCoords.x < 0 || localCoords.x >= this.size.x || localCoords.y < 0 || localCoords.y >= this.size.y){
// There are no tiles in negative positions or out of bounds positions // There are no tiles in negative positions or out of bounds positions
return 0; return 0;
} }
return this.data[localCoords.y * this.worldSize.x + localCoords.x] return this.data[localCoords.y * this.size.x + localCoords.x]
} }
/** /**
@ -50,11 +50,11 @@ export default class OrthogonalTilemap extends Tilemap {
isTileCollidable(indexOrCol: number, row?: number): boolean { isTileCollidable(indexOrCol: number, row?: number): boolean {
let index = 0; let index = 0;
if(row){ if(row){
if(indexOrCol < 0 || indexOrCol >= this.worldSize.x || row < 0 || row >= this.worldSize.y){ if(indexOrCol < 0 || indexOrCol >= this.size.x || row < 0 || row >= this.size.y){
// There are no tiles in negative positions or out of bounds positions // There are no tiles in negative positions or out of bounds positions
return false; return false;
} }
index = row * this.worldSize.x + indexOrCol; index = row * this.size.x + indexOrCol;
} else { } else {
if(indexOrCol < 0 || indexOrCol >= this.data.length){ if(indexOrCol < 0 || indexOrCol >= this.data.length){
// Tiles that don't exist aren't collidable // Tiles that don't exist aren't collidable
@ -93,7 +93,7 @@ export default class OrthogonalTilemap extends Tilemap {
for(let tileset of this.tilesets){ for(let tileset of this.tilesets){
if(tileset.hasTile(tileIndex)){ if(tileset.hasTile(tileIndex)){
tileset.renderTile(ctx, tileIndex, i, this.worldSize, origin, this.scale); tileset.renderTile(ctx, tileIndex, i, this.size, origin, this.scale);
} }
} }
} }

View File

@ -179,22 +179,29 @@ export default class UIElement extends CanvasNode{
let previousAlpha = ctx.globalAlpha; let previousAlpha = ctx.globalAlpha;
ctx.globalAlpha = this.getLayer().getAlpha(); ctx.globalAlpha = this.getLayer().getAlpha();
let origin = this.scene.getViewport().getPosition().clone().mult(this.layer.getParallax()); let origin = this.getViewportOriginWithParallax();
ctx.font = this.fontSize + "px " + this.font; ctx.font = this.fontSize + "px " + this.font;
let offset = this.calculateOffset(ctx); let offset = this.calculateOffset(ctx);
// Stroke and fill a rounded rect and give it text // Stroke and fill a rounded rect and give it text
ctx.fillStyle = this.calculateBackgroundColor(); ctx.fillStyle = this.calculateBackgroundColor();
ctx.fillRoundedRect(this.position.x - origin.x, this.position.y - origin.y, this.size.x, this.size.y, this.borderRadius); ctx.fillRoundedRect(this.position.x - origin.x - this.size.x/2, this.position.y - origin.y - this.size.y/2,
this.size.x, this.size.y, this.borderRadius);
ctx.strokeStyle = this.calculateBorderColor(); ctx.strokeStyle = this.calculateBorderColor();
ctx.lineWidth = this.borderWidth; ctx.lineWidth = this.borderWidth;
ctx.strokeRoundedRect(this.position.x - origin.x, this.position.y - origin.y, this.size.x, this.size.y, this.borderRadius); ctx.strokeRoundedRect(this.position.x - origin.x - this.size.x/2, this.position.y - origin.y - this.size.y/2,
this.size.x, this.size.y, this.borderRadius);
ctx.fillStyle = this.calculateTextColor(); ctx.fillStyle = this.calculateTextColor();
ctx.fillText(this.text, this.position.x + offset.x - origin.x, this.position.y + offset.y - origin.y); ctx.fillText(this.text, this.position.x + offset.x - origin.x - this.size.x/2, this.position.y + offset.y - origin.y - this.size.y/2);
ctx.globalAlpha = previousAlpha; ctx.globalAlpha = previousAlpha;
ctx.lineWidth = 4;
ctx.strokeStyle = "#00FF00"
let b = this.getBoundary();
ctx.strokeRect(b.x - b.hw - origin.x, b.y - b.hh - origin.y, b.hw*2, b.hh*2);
} }
} }

View File

@ -1,29 +0,0 @@
import Collider from "./Collider";
import Vec2 from "../../DataTypes/Vec2";
export default class AABBCollider extends Collider {
isCollidingWith(other: Collider): boolean {
if(other instanceof AABBCollider){
if(other.position.x > this.position.x && other.position.x < this.position.x + this.size.x){
return other.position.y > this.position.y && other.position.y < this.position.y + this.size.y;
}
}
return false;
}
willCollideWith(other: Collider, thisVel: Vec2, otherVel: Vec2): boolean {
if(other instanceof AABBCollider){
let thisPos = new Vec2(this.position.x + thisVel.x, this.position.y + thisVel.y);
let otherPos = new Vec2(other.position.x + otherVel.x, other.position.y + otherVel.y);
if(otherPos.x > thisPos.x && otherPos.x < thisPos.x + this.size.x){
return otherPos.y > thisPos.y && otherPos.y < thisPos.y + this.size.y;
}
}
return false;
}
update(deltaT: number): void {}
}

View File

@ -1,19 +1,39 @@
import GameNode from "../../Nodes/GameNode"; import AABB from "../../DataTypes/AABB";
import { Positioned } from "../../DataTypes/Interfaces/Descriptors";
import Shape from "../../DataTypes/Shape";
import Vec2 from "../../DataTypes/Vec2"; import Vec2 from "../../DataTypes/Vec2";
export default abstract class Collider extends GameNode { export default class Collider implements Positioned {
protected size: Vec2; protected shape: Shape;
getSize(): Vec2 { constructor(shape: Shape){
return this.size; this.shape = shape;
} }
// TODO: Make this accept vector arguments and number arguments setPosition(position: Vec2): void {
setSize(size: Vec2): void { this.shape.setCenter(position);
this.size = size;
} }
abstract isCollidingWith(other: Collider): boolean; getPosition(): Vec2 {
return this.shape.getCenter();
abstract willCollideWith(other: Collider, thisVel: Vec2, otherVel: Vec2): boolean; }
getBoundingRect(): AABB {
return this.shape.getBoundingRect();
}
/**
* Sets the collision shape for this collider.
* @param shape
*/
setCollisionShape(shape: Shape): void {
this.shape = shape;
}
/**
* Returns the collision shape this collider has
*/
getCollisionShape(): Shape {
return this.shape;
}
} }

View File

@ -0,0 +1,97 @@
import Shape from "../../DataTypes/Shape";
import AABB from "../../DataTypes/AABB";
import Vec2 from "../../DataTypes/Vec2";
import Collider from "./Collider";
import Debug from "../../Debug/Debug";
export function getTimeOfCollision(A: Collider, velA: Vec2, B: Collider, velB: Vec2): [Vec2, Vec2, boolean, boolean] {
let shapeA = A.getCollisionShape();
let shapeB = B.getCollisionShape();
if(shapeA instanceof AABB && shapeB instanceof AABB){
return getTimeOfCollision_AABB_AABB(shapeA, velA, shapeB, velB);
}
}
// TODO - Make this work with centered points to avoid this initial calculation
function getTimeOfCollision_AABB_AABB(A: AABB, velA: Vec2, B: AABB, velB: Vec2): [Vec2, Vec2, boolean, boolean] {
let posA = A.getCenter().clone();
let posB = B.getCenter().clone();
let sizeA = A.getHalfSize();
let sizeB = B.getHalfSize();
let firstContact = new Vec2(0, 0);
let lastContact = new Vec2(0, 0);
let collidingX = false;
let collidingY = false;
// Sort by position
if(posB.x < posA.x){
// Swap, because B is to the left of A
let temp: Vec2;
temp = sizeA;
sizeA = sizeB;
sizeB = temp;
temp = posA;
posA = posB;
posB = temp;
temp = velA;
velA = velB;
velB = temp;
}
// A is left, B is right
firstContact.x = Infinity;
lastContact.x = Infinity;
if (posB.x - sizeB.x >= posA.x + sizeA.x){
// If we aren't currently colliding
let relVel = velA.x - velB.x;
if(relVel > 0){
// If they are moving towards each other
firstContact.x = ((posB.x - sizeB.x) - (posA.x + sizeA.x))/(relVel);
lastContact.x = ((posB.x + sizeB.x) - (posA.x - sizeA.x))/(relVel);
}
} else {
collidingX = true;
}
if(posB.y < posA.y){
// Swap, because B is above A
let temp: Vec2;
temp = sizeA;
sizeA = sizeB;
sizeB = temp;
temp = posA;
posA = posB;
posB = temp;
temp = velA;
velA = velB;
velB = temp;
}
// A is top, B is bottom
firstContact.y = Infinity;
lastContact.y = Infinity;
if (posB.y - sizeB.y >= posA.y + sizeA.y){
// If we aren't currently colliding
let relVel = velA.y - velB.y;
if(relVel > 0){
// If they are moving towards each other
firstContact.y = ((posB.y - sizeB.y) - (posA.y + sizeA.y))/(relVel);
lastContact.y = ((posB.y + sizeB.y) - (posA.y - sizeA.y))/(relVel);
}
} else {
collidingY = true;
}
return [firstContact, lastContact, collidingX, collidingY];
}

View File

@ -5,12 +5,16 @@ import Debug from "../Debug/Debug";
import MathUtils from "../Utils/MathUtils"; import MathUtils from "../Utils/MathUtils";
import Tilemap from "../Nodes/Tilemap"; import Tilemap from "../Nodes/Tilemap";
import OrthogonalTilemap from "../Nodes/Tilemaps/OrthogonalTilemap"; import OrthogonalTilemap from "../Nodes/Tilemaps/OrthogonalTilemap";
import AABB from "../DataTypes/AABB";
import { getTimeOfCollision } from "./Colliders/Collisions";
import Collider from "./Colliders/Collider";
export default class PhysicsManager { export default class PhysicsManager {
private physicsNodes: Array<PhysicsNode>; private physicsNodes: Array<PhysicsNode>;
private tilemaps: Array<Tilemap>; private tilemaps: Array<Tilemap>;
private movements: Array<MovementData>; private movements: Array<MovementData>;
private tcols: Array<TileCollisionData> = [];
constructor(){ constructor(){
this.physicsNodes = new Array(); this.physicsNodes = new Array();
@ -63,14 +67,14 @@ export default class PhysicsManager {
*/ */
private collideWithOrthogonalTilemap(node: PhysicsNode, tilemap: OrthogonalTilemap, velocity: Vec2): void { private collideWithOrthogonalTilemap(node: PhysicsNode, tilemap: OrthogonalTilemap, velocity: Vec2): void {
// Get the starting position of the moving node // Get the starting position of the moving node
let startPos = node.getPosition(); let startPos = node.getCollider().getPosition();
// Get the end position of the moving node // Get the end position of the moving node
let endPos = startPos.clone().add(velocity); let endPos = startPos.clone().add(velocity);
let size = node.getCollider().getSize(); let size = node.getCollider().getBoundingRect().getHalfSize();
// Get the min and max x and y coordinates of the moving node // Get the min and max x and y coordinates of the moving node
let min = new Vec2(Math.min(startPos.x, endPos.x), Math.min(startPos.y, endPos.y)); 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)); 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 // Convert the min/max x/y to the min and max row/col in the tilemap array
@ -79,6 +83,7 @@ export default class PhysicsManager {
// Create an empty set of tilemap collisions (We'll handle all of them at the end) // Create an empty set of tilemap collisions (We'll handle all of them at the end)
let tilemapCollisions = new Array<TileCollisionData>(); let tilemapCollisions = new Array<TileCollisionData>();
this.tcols = [];
let tileSize = tilemap.getTileSize(); let tileSize = tilemap.getTileSize();
Debug.log("tilemapCollision", ""); Debug.log("tilemapCollision", "");
@ -90,7 +95,10 @@ export default class PhysicsManager {
Debug.log("tilemapCollision", "Colliding with Tile"); Debug.log("tilemapCollision", "Colliding with Tile");
// Get the position of this tile // Get the position of this tile
let tilePos = new Vec2(col * tileSize.x, row * tileSize.y); 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 Collider(new AABB(tilePos, tileSize.scaled(1/2)));
// Calculate collision area between the node and the tile // 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 dx = Math.min(startPos.x, tilePos.x) - Math.max(startPos.x + size.x, tilePos.x + size.x);
@ -102,17 +110,21 @@ export default class PhysicsManager {
overlap = dx * dy; overlap = dx * dy;
} }
tilemapCollisions.push(new TileCollisionData(tilePos, overlap)); this.tcols.push(new TileCollisionData(collider, overlap))
tilemapCollisions.push(new TileCollisionData(collider, overlap));
} }
} }
} }
// Now that we have all collisions, sort by collision area highest to lowest // Now that we have all collisions, sort by collision area highest to lowest
tilemapCollisions = tilemapCollisions.sort((a, b) => a.overlapArea - b.overlapArea); tilemapCollisions = tilemapCollisions.sort((a, b) => a.overlapArea - b.overlapArea);
let areas = "";
tilemapCollisions.forEach(col => areas += col.overlapArea + ", ")
Debug.log("cols", areas)
// 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) // 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 => { tilemapCollisions.forEach(collision => {
let [firstContact, _, collidingX, collidingY] = this.getTimeOfAABBCollision(startPos, size, velocity, collision.position, tileSize, new Vec2(0, 0)); let [firstContact, _, collidingX, collidingY] = getTimeOfCollision(node.getCollider(), velocity, collision.collider, Vec2.ZERO);
// Handle collision // Handle collision
if( (firstContact.x < 1 || collidingX) && (firstContact.y < 1 || collidingY)){ if( (firstContact.x < 1 || collidingX) && (firstContact.y < 1 || collidingY)){
@ -143,13 +155,7 @@ export default class PhysicsManager {
} }
private collideWithStaticNode(movingNode: PhysicsNode, staticNode: PhysicsNode, velocity: Vec2){ private collideWithStaticNode(movingNode: PhysicsNode, staticNode: PhysicsNode, velocity: Vec2){
let sizeA = movingNode.getCollider().getSize(); let [firstContact, _, collidingX, collidingY] = getTimeOfCollision(movingNode.getCollider(), velocity, staticNode.getCollider(), Vec2.ZERO);
let posA = movingNode.getPosition();
let velA = velocity;
let sizeB = staticNode.getCollider().getSize();
let posB = staticNode.getPosition();
let [firstContact, _, collidingX, collidingY] = this.getTimeOfAABBCollision(posA, sizeA, velA, posB, sizeB, new Vec2(0, 0));
if( (firstContact.x < 1 || collidingX) && (firstContact.y < 1 || collidingY)){ if( (firstContact.x < 1 || collidingX) && (firstContact.y < 1 || collidingY)){
if(collidingX && collidingY){ if(collidingX && collidingY){
@ -178,88 +184,6 @@ export default class PhysicsManager {
} }
} }
/**
* Gets the collision time of two AABBs using continuous collision checking. Returns vectors representing the time
* of the start and end of the collision and booleans for whether or not the objects are currently overlapping
* (before they move).
*/
private getTimeOfAABBCollision(posA: Vec2, sizeA: Vec2, velA: Vec2, posB: Vec2, sizeB: Vec2, velB: Vec2): [Vec2, Vec2, boolean, boolean] {
let firstContact = new Vec2(0, 0);
let lastContact = new Vec2(0, 0);
let collidingX = false;
let collidingY = false;
// Sort by position
if(posB.x < posA.x){
// Swap, because B is to the left of A
let temp: Vec2;
temp = sizeA;
sizeA = sizeB;
sizeB = temp;
temp = posA;
posA = posB;
posB = temp;
temp = velA;
velA = velB;
velB = temp;
}
// A is left, B is right
firstContact.x = Infinity;
lastContact.x = Infinity;
if (posB.x >= posA.x + sizeA.x){
// If we aren't currently colliding
let relVel = velA.x - velB.x;
if(relVel > 0){
// If they are moving towards each other
firstContact.x = (posB.x - (posA.x + (sizeA.x)))/(relVel);
lastContact.x = ((posB.x + sizeB.x) - posA.x)/(relVel);
}
} else {
collidingX = true;
}
if(posB.y < posA.y){
// Swap, because B is above A
let temp: Vec2;
temp = sizeA;
sizeA = sizeB;
sizeB = temp;
temp = posA;
posA = posB;
posB = temp;
temp = velA;
velA = velB;
velB = temp;
}
// A is top, B is bottom
firstContact.y = Infinity;
lastContact.y = Infinity;
if (posB.y >= posA.y + sizeA.y){
// If we aren't currently colliding
let relVel = velA.y - velB.y;
if(relVel > 0){
// If they are moving towards each other
firstContact.y = (posB.y - (posA.y + (sizeA.y)))/(relVel);
lastContact.y = ((posB.y + sizeB.y) - posA.y)/(relVel);
}
} else {
collidingY = true;
}
return [firstContact, lastContact, collidingX, collidingY];
}
update(deltaT: number): void { update(deltaT: number): void {
for(let node of this.physicsNodes){ for(let node of this.physicsNodes){
if(!node.getLayer().isPaused()){ if(!node.getLayer().isPaused()){
@ -309,6 +233,28 @@ export default class PhysicsManager {
// Reset movements // Reset movements
this.movements = new Array(); this.movements = new Array();
} }
render(ctx: CanvasRenderingContext2D): void {
let vpo;
for(let node of this.physicsNodes){
vpo = node.getViewportOriginWithParallax();
let pos = node.getPosition().sub(node.getViewportOriginWithParallax());
let size = (<AABB>node.getCollider().getCollisionShape()).getHalfSize();
ctx.lineWidth = 2;
ctx.strokeStyle = "#FF0000";
ctx.strokeRect(pos.x - size.x, pos.y-size.y, size.x*2, size.y*2);
}
for(let node of this.tcols){
let pos = node.collider.getPosition().sub(vpo);
let size = node.collider.getBoundingRect().getHalfSize();
ctx.lineWidth = 2;
ctx.strokeStyle = "#FF0000";
ctx.strokeRect(pos.x - size.x, pos.y-size.y, size.x*2, size.y*2);
}
}
} }
// Helper classes for internal data // Helper classes for internal data
@ -325,10 +271,11 @@ class MovementData {
// Collision data objects for tilemaps // Collision data objects for tilemaps
class TileCollisionData { class TileCollisionData {
position: Vec2; collider: Collider;
overlapArea: number; overlapArea: number;
constructor(position: Vec2, overlapArea: number){
this.position = position; constructor(collider: Collider, overlapArea: number){
this.collider = collider;
this.overlapArea = overlapArea; this.overlapArea = overlapArea;
} }
} }

View File

@ -67,7 +67,7 @@ export default abstract class PhysicsNode extends GameNode {
this.position.add(velocity); this.position.add(velocity);
this.collider.getPosition().add(velocity); this.collider.getPosition().add(velocity);
for(let child of this.children){ for(let child of this.children){
child.getPosition().add(velocity); child.position.add(velocity);
} }
} }

View File

@ -1,15 +1,15 @@
import PhysicsNode from "./PhysicsNode"; import PhysicsNode from "./PhysicsNode";
import Vec2 from "../DataTypes/Vec2"; import Vec2 from "../DataTypes/Vec2";
import AABBCollider from "./Colliders/AABBCollider"; import Collider from "./Colliders/Collider";
import AABB from "../DataTypes/AABB";
export default class StaticBody extends PhysicsNode { export default class StaticBody extends PhysicsNode {
constructor(position: Vec2, size: Vec2){ constructor(position: Vec2, size: Vec2){
super(); super();
this.setPosition(position.x, position.y); this.setPosition(position.x, position.y);
this.collider = new AABBCollider(); let aabb = new AABB(position.clone(), size.scaled(1/2));
this.collider.setPosition(position.x, position.y); this.collider = new Collider(aabb);
this.collider.setSize(new Vec2(size.x, size.y));
this.moving = false; this.moving = false;
} }

View File

@ -1,9 +1,10 @@
import PhysicsNode from "./Physics/PhysicsNode"; import PhysicsNode from "./Physics/PhysicsNode";
import Vec2 from "./DataTypes/Vec2"; import Vec2 from "./DataTypes/Vec2";
import Debug from "./Debug/Debug"; import Debug from "./Debug/Debug";
import AABBCollider from "./Physics/Colliders/AABBCollider";
import CanvasNode from "./Nodes/CanvasNode"; import CanvasNode from "./Nodes/CanvasNode";
import { GameEventType } from "./Events/GameEventType"; import { GameEventType } from "./Events/GameEventType";
import AABB from "./DataTypes/AABB";
import Collider from "./Physics/Colliders/Collider";
export default class Player extends PhysicsNode { export default class Player extends PhysicsNode {
velocity: Vec2; velocity: Vec2;
@ -19,18 +20,20 @@ export default class Player extends PhysicsNode {
this.velocity = new Vec2(0, 0); this.velocity = new Vec2(0, 0);
this.speed = 600; this.speed = 600;
this.size = new Vec2(50, 50); this.size = new Vec2(50, 50);
this.collider = new AABBCollider();
this.collider.setSize(this.size);
this.position = new Vec2(0, 0); this.position = new Vec2(0, 0);
if(this.type === "topdown"){ if(this.type === "topdown"){
this.position = new Vec2(100, 100); this.position = new Vec2(100, 100);
} }
this.collider = new Collider(new AABB(this.position.clone(), this.size.scaled(1/2)));
} }
create(): void {}; create(): void {};
sprite: CanvasNode;
setSprite(sprite: CanvasNode): void { setSprite(sprite: CanvasNode): void {
sprite.setPosition(this.position); this.sprite = sprite;
sprite.position = this.position.clone();
sprite.setSize(this.size); sprite.setSize(this.size);
this.children.push(sprite); this.children.push(sprite);
} }
@ -46,7 +49,8 @@ export default class Player extends PhysicsNode {
this.move(new Vec2(this.velocity.x * deltaT, this.velocity.y * deltaT)); this.move(new Vec2(this.velocity.x * deltaT, this.velocity.y * deltaT));
Debug.log("player", "Player Pos: " + this.position + ", Player Vel: " + this.velocity); Debug.log("player", "Pos: " + this.sprite.getPosition() + ", Size: " + this.sprite.getSize());
Debug.log("playerbound", "Pos: " + this.sprite.getBoundary().getCenter() + ", Size: " + this.sprite.getBoundary().getHalfSize());
} }
topdown_computeDirection(): Vec2 { topdown_computeDirection(): Vec2 {

View File

@ -29,7 +29,7 @@ export default class TilemapFactory {
* @param constr The constructor of the desired tilemap * @param constr The constructor of the desired tilemap
* @param args Additional arguments to send to the tilemap constructor * @param args Additional arguments to send to the tilemap constructor
*/ */
add = (key: string): Array<Layer> => { add = (key: string, scale: Vec2 = new Vec2(1, 1)): Array<Layer> => {
// Get Tilemap Data // Get Tilemap Data
let tilemapData = this.resourceManager.getTilemap(key); let tilemapData = this.resourceManager.getTilemap(key);
@ -70,7 +70,7 @@ export default class TilemapFactory {
if(layer.type === "tilelayer"){ if(layer.type === "tilelayer"){
// Create a new tilemap object for the layer // Create a new tilemap object for the layer
let tilemap = new constr(tilemapData, layer, tilesets); let tilemap = new constr(tilemapData, layer, tilesets, scale);
tilemap.setId(this.scene.generateId()); tilemap.setId(this.scene.generateId());
tilemap.setScene(this.scene); tilemap.setScene(this.scene);
@ -107,10 +107,10 @@ export default class TilemapFactory {
let offset = tileset.getImageOffsetForTile(obj.gid); let offset = tileset.getImageOffsetForTile(obj.gid);
sprite = this.scene.add.sprite(imageKey, sceneLayer); sprite = this.scene.add.sprite(imageKey, sceneLayer);
let size = tileset.getTileSize().clone(); let size = tileset.getTileSize().clone();
sprite.setPosition(obj.x*4, (obj.y - size.y)*4); sprite.setPosition((obj.x + size.x/2)*scale.x, (obj.y - size.y/2)*scale.y);
sprite.setImageOffset(offset); sprite.setImageOffset(offset);
sprite.setSize(size); sprite.setSize(size);
sprite.setScale(new Vec2(4, 4)); sprite.setScale(new Vec2(scale.x, scale.y));
} }
} }
@ -120,8 +120,8 @@ export default class TilemapFactory {
if(obj.gid === tile.id){ if(obj.gid === tile.id){
let imageKey = tile.image; let imageKey = tile.image;
sprite = this.scene.add.sprite(imageKey, sceneLayer); sprite = this.scene.add.sprite(imageKey, sceneLayer);
sprite.setPosition(obj.x*4, (obj.y - tile.imageheight)*4); sprite.setPosition((obj.x + tile.imagewidth/2)*scale.x, (obj.y - tile.imageheight/2)*scale.y);
sprite.setScale(new Vec2(4, 4)); sprite.setScale(new Vec2(scale.x, scale.y));
} }
} }
} }
@ -129,9 +129,9 @@ export default class TilemapFactory {
// Now we have sprite. Associate it with our physics object if there is one // Now we have sprite. Associate it with our physics object if there is one
if(collidable){ if(collidable){
let pos = sprite.getPosition().clone(); let pos = sprite.getPosition().clone();
let size = sprite.getSize().clone().mult(sprite.getScale());
pos.x = Math.floor(pos.x); pos.x = Math.floor(pos.x);
pos.y = Math.floor(pos.y); pos.y = Math.floor(pos.y);
let size = sprite.getSize().clone().mult(sprite.getScale());
let staticBody = this.scene.add.physics(StaticBody, sceneLayer, pos, size); let staticBody = this.scene.add.physics(StaticBody, sceneLayer, pos, size);
staticBody.addChild(sprite); staticBody.addChild(sprite);
} }

View File

@ -125,6 +125,9 @@ export default class Scene{
// Render visible set // Render visible set
visibleSet.forEach(node => node.render(ctx)); visibleSet.forEach(node => node.render(ctx));
// Debug render the physicsManager
this.physicsManager.render(ctx);
} }
setRunning(running: boolean): void { setRunning(running: boolean): void {

View File

@ -49,7 +49,7 @@ export default class SceneManager {
this.resourceManager.unloadAllResources(); this.resourceManager.unloadAllResources();
this.viewport.setPosition(0, 0); this.viewport.setCenter(0, 0);
this.addScene(constr); this.addScene(constr);
} }

View File

@ -56,14 +56,9 @@ export default class SceneGraphQuadTree extends SceneGraph {
} }
getVisibleSet(): Array<CanvasNode> { getVisibleSet(): Array<CanvasNode> {
let visibleSet = new Array<CanvasNode>(); let visibleSet = this.qt.queryRegion(this.viewport.getView());
// TODO - Currently just gets all of them visibleSet = visibleSet.filter(node => !node.getLayer().isHidden());
this.qt.forEach((node: CanvasNode) => {
if(!node.getLayer().isHidden() && this.viewport.includes(node)){
visibleSet.push(node);
}
});
// Sort by depth, then by visible set by y-value // Sort by depth, then by visible set by y-value
visibleSet.sort((a, b) => { visibleSet.sort((a, b) => {

View File

@ -4,11 +4,12 @@ import GameNode from "../Nodes/GameNode";
import CanvasNode from "../Nodes/CanvasNode"; import CanvasNode from "../Nodes/CanvasNode";
import MathUtils from "../Utils/MathUtils"; import MathUtils from "../Utils/MathUtils";
import Queue from "../DataTypes/Queue"; import Queue from "../DataTypes/Queue";
import AABB from "../DataTypes/AABB";
import Debug from "../Debug/Debug";
export default class Viewport { export default class Viewport {
private position: Vec2; private view: AABB;
private size: Vec2; private boundary: AABB;
private bounds: Vec4;
private following: GameNode; private following: GameNode;
/** /**
@ -22,9 +23,8 @@ export default class Viewport {
private smoothingFactor: number; private smoothingFactor: number;
constructor(){ constructor(){
this.position = new Vec2(0, 0); this.view = new AABB(Vec2.ZERO, Vec2.ZERO);
this.size = new Vec2(0, 0); this.boundary = new AABB(Vec2.ZERO, Vec2.ZERO);
this.bounds = new Vec4(0, 0, 0, 0);
this.lastPositions = new Queue(); this.lastPositions = new Queue();
this.smoothingFactor = 10; this.smoothingFactor = 10;
} }
@ -32,8 +32,19 @@ export default class Viewport {
/** /**
* Returns the position of the viewport as a Vec2 * Returns the position of the viewport as a Vec2
*/ */
getPosition(): Vec2 { getCenter(): Vec2 {
return this.position; return this.view.getCenter();
}
getOrigin(): Vec2 {
return this.view.getCenter().clone().sub(this.view.getHalfSize())
}
/**
* Returns the region visible to this viewport
*/
getView(): AABB {
return this.view;
} }
/** /**
@ -41,7 +52,7 @@ export default class Viewport {
* @param vecOrX * @param vecOrX
* @param y * @param y
*/ */
setPosition(vecOrX: Vec2 | number, y: number = null): void { setCenter(vecOrX: Vec2 | number, y: number = null): void {
let pos: Vec2; let pos: Vec2;
if(vecOrX instanceof Vec2){ if(vecOrX instanceof Vec2){
pos = vecOrX; pos = vecOrX;
@ -56,8 +67,8 @@ export default class Viewport {
/** /**
* Returns the size of the viewport as a Vec2 * Returns the size of the viewport as a Vec2
*/ */
getSize(): Vec2{ getHalfSize(): Vec2 {
return this.size; return this.view.getHalfSize();
} }
/** /**
@ -67,9 +78,17 @@ export default class Viewport {
*/ */
setSize(vecOrX: Vec2 | number, y: number = null): void { setSize(vecOrX: Vec2 | number, y: number = null): void {
if(vecOrX instanceof Vec2){ if(vecOrX instanceof Vec2){
this.size.set(vecOrX.x, vecOrX.y); this.view.setHalfSize(vecOrX.scaled(1/2));
} else { } else {
this.size.set(vecOrX, y); this.view.setHalfSize(new Vec2(vecOrX/2, y/2));
}
}
setHalfSize(vecOrX: Vec2 | number, y: number = null): void {
if(vecOrX instanceof Vec2){
this.view.setHalfSize(vecOrX.clone());
} else {
this.view.setHalfSize(new Vec2(vecOrX, y));
} }
} }
@ -87,19 +106,12 @@ export default class Viewport {
* @param node * @param node
*/ */
includes(node: CanvasNode): boolean { includes(node: CanvasNode): boolean {
let nodePos = node.getPosition();
let nodeSize = node.getSize();
let nodeScale = node.getScale();
let parallax = node.getLayer().getParallax(); let parallax = node.getLayer().getParallax();
let originX = this.position.x*parallax.x; let center = this.view.getCenter().clone();
let originY = this.position.y*parallax.y; this.view.getCenter().mult(parallax);
if(nodePos.x + nodeSize.x * nodeScale.x > originX && nodePos.x < originX + this.size.x){ let overlaps = this.view.overlaps(node.getBoundary());
if(nodePos.y + nodeSize.y * nodeScale.y > originY && nodePos.y < originY + this.size.y){ this.view.setCenter(center);
return true; return overlaps;
}
}
return false;
} }
// TODO: Put some error handling on this for trying to make the bounds too small for the viewport // TODO: Put some error handling on this for trying to make the bounds too small for the viewport
@ -112,7 +124,12 @@ export default class Viewport {
* @param upperY * @param upperY
*/ */
setBounds(lowerX: number, lowerY: number, upperX: number, upperY: number): void { setBounds(lowerX: number, lowerY: number, upperX: number, upperY: number): void {
this.bounds = new Vec4(lowerX, lowerY, upperX, upperY); let hwidth = (upperX - lowerX)/2;
let hheight = (upperY - lowerY)/2;
let x = lowerX + hwidth;
let y = lowerY + hheight;
this.boundary.setCenter(new Vec2(x, y));
this.boundary.setHalfSize(new Vec2(hwidth, hheight));
} }
/** /**
@ -138,11 +155,10 @@ export default class Viewport {
pos.scale(1/this.lastPositions.getSize()); pos.scale(1/this.lastPositions.getSize());
// Set this position either to the object or to its bounds // Set this position either to the object or to its bounds
this.position.x = pos.x - this.size.x/2; pos.x = MathUtils.clamp(pos.x, this.boundary.left + this.view.hw, this.boundary.right - this.view.hw);
this.position.y = pos.y - this.size.y/2; pos.y = MathUtils.clamp(pos.y, this.boundary.top + this.view.hh, this.boundary.bottom - this.view.hh);
let [min, max] = this.bounds.split();
this.position.x = MathUtils.clamp(this.position.x, min.x, max.x - this.size.x); this.view.setCenter(pos);
this.position.y = MathUtils.clamp(this.position.y, min.y, max.y - this.size.y);
} }
} }
} }

View File

@ -1,13 +1,14 @@
import GameLoop from "./Loop/GameLoop"; import GameLoop from "./Loop/GameLoop";
import {} from "./index"; import {} from "./index";
import MainScene from "./MainScene"
import QuadTreeScene from "./QuadTreeScene"; import QuadTreeScene from "./QuadTreeScene";
function main(){ function main(){
// Create the game object // Create the game object
let game = new GameLoop({viewportSize: {x: 500, y: 500}}); let game = new GameLoop({viewportSize: {x: 800, y: 600}});
game.start(); game.start();
let sm = game.getSceneManager(); let sm = game.getSceneManager();
sm.addScene(QuadTreeScene); sm.addScene(MainScene);
} }
CanvasRenderingContext2D.prototype.roundedRect = function(x: number, y: number, w: number, h: number, r: number): void { CanvasRenderingContext2D.prototype.roundedRect = function(x: number, y: number, w: number, h: number, r: number): void {