converted all code to work with AABBs
This commit is contained in:
parent
a32066468f
commit
254462993a
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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
8
src/DataTypes/Shape.ts
Normal 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;
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Vec2 from "../DataTypes/Vec2";
|
||||||
/**
|
/**
|
||||||
* The representation of a UIElement - the parent class of things like buttons
|
* The representation of a UIElement - the parent class of things like buttons
|
||||||
*/
|
*/
|
||||||
export default class UIElement extends CanvasNode{
|
export default class UIElement extends CanvasNode {
|
||||||
// Style attributes
|
// Style attributes
|
||||||
protected textColor: Color;
|
protected textColor: Color;
|
||||||
protected backgroundColor: Color;
|
protected backgroundColor: Color;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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 {}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
97
src/Physics/Colliders/Collisions.ts
Normal file
97
src/Physics/Colliders/Collisions.ts
Normal 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];
|
||||||
|
}
|
|
@ -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", "");
|
||||||
|
@ -88,9 +93,12 @@ export default class PhysicsManager {
|
||||||
for(let row = minIndex.y; row <= maxIndex.y; row++){
|
for(let row = minIndex.y; row <= maxIndex.y; row++){
|
||||||
if(tilemap.isTileCollidable(col, row)){
|
if(tilemap.isTileCollidable(col, row)){
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user