fixed tilemap physics
This commit is contained in:
parent
66ced08987
commit
2dd60b5197
|
@ -65,7 +65,7 @@ export default class Tileset {
|
||||||
return tileIndex >= this.startIndex && tileIndex <= this.endIndex;
|
return tileIndex >= this.startIndex && tileIndex <= this.endIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTile(ctx: CanvasRenderingContext2D, tileIndex: number, dataIndex: number, worldSize: Vec2, origin: Vec2): void {
|
renderTile(ctx: CanvasRenderingContext2D, tileIndex: number, dataIndex: number, worldSize: Vec2, origin: Vec2, scale: Vec2): void {
|
||||||
let index = tileIndex - this.startIndex;
|
let index = tileIndex - this.startIndex;
|
||||||
let row = Math.floor(index / this.numCols);
|
let row = Math.floor(index / this.numCols);
|
||||||
let col = index % this.numCols;
|
let col = index % this.numCols;
|
||||||
|
@ -73,8 +73,8 @@ export default class Tileset {
|
||||||
let height = this.tileSize.y;
|
let height = this.tileSize.y;
|
||||||
let left = col * width;
|
let left = col * width;
|
||||||
let top = row * height;
|
let top = row * height;
|
||||||
let x = (dataIndex % worldSize.x) * width * 4;
|
let x = (dataIndex % worldSize.x) * width * scale.x;
|
||||||
let y = Math.floor(dataIndex / worldSize.x) * height * 4;
|
let y = Math.floor(dataIndex / worldSize.x) * height * scale.y;
|
||||||
ctx.drawImage(this.image, left, top, width, height, x - origin.x, y - origin.y, width * 4, height * 4);
|
ctx.drawImage(this.image, left, top, width, height, x - origin.x, y - origin.y, width * scale.x, height * scale.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -80,4 +80,8 @@ export default class Vec2 {
|
||||||
toFixed(): string {
|
toFixed(): string {
|
||||||
return "(" + this.x.toFixed(1) + ", " + this.y.toFixed(1) + ")";
|
return "(" + this.x.toFixed(1) + ", " + this.y.toFixed(1) + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clone(): Vec2 {
|
||||||
|
return new Vec2(this.x, this.y);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -2,6 +2,7 @@ import Scene from "../Scene";
|
||||||
import Viewport from "../../SceneGraph/Viewport";
|
import Viewport from "../../SceneGraph/Viewport";
|
||||||
import PhysicsNode from "../../Physics/PhysicsNode";
|
import PhysicsNode from "../../Physics/PhysicsNode";
|
||||||
import PhysicsManager from "../../Physics/PhysicsManager";
|
import PhysicsManager from "../../Physics/PhysicsManager";
|
||||||
|
import Tilemap from "../../Nodes/Tilemap";
|
||||||
|
|
||||||
export default class PhysicsNodeFactory {
|
export default class PhysicsNodeFactory {
|
||||||
private scene: Scene;
|
private scene: Scene;
|
||||||
|
@ -20,4 +21,8 @@ export default class PhysicsNodeFactory {
|
||||||
this.physicsManager.add(instance);
|
this.physicsManager.add(instance);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addTilemap(tilemap: Tilemap): void {
|
||||||
|
this.physicsManager.addTilemap(tilemap);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -28,17 +28,20 @@ export default class TilemapFactory {
|
||||||
this.scene.addTilemap(tilemap);
|
this.scene.addTilemap(tilemap);
|
||||||
|
|
||||||
if(tilemap.isCollidable()){
|
if(tilemap.isCollidable()){
|
||||||
// Create colliders
|
// Register in physics as a tilemap
|
||||||
let worldSize = tilemap.getWorldSize();
|
this.scene.physics.addTilemap(tilemap);
|
||||||
let tileSize = tilemap.getTileSize();
|
|
||||||
|
|
||||||
tilemap.forEachTile((tileIndex: number, i: number) => {
|
// Create colliders
|
||||||
if(tileIndex !== 0){
|
// let worldSize = tilemap.getWorldSize();
|
||||||
let x = (i % worldSize.x) * tileSize.x * 4;
|
// let tileSize = tilemap.getTileSize();
|
||||||
let y = Math.floor(i / worldSize.x) * tileSize.y * 4;
|
|
||||||
this.scene.physics.add(StaticBody, new Vec2(x, y), new Vec2(tileSize.x * 4, tileSize.y * 4));
|
// tilemap.forEachTile((tileIndex: number, i: number) => {
|
||||||
}
|
// if(tileIndex !== 0){
|
||||||
});
|
// let x = (i % worldSize.x) * tileSize.x * 4;
|
||||||
|
// let y = Math.floor(i / worldSize.x) * tileSize.y * 4;
|
||||||
|
// this.scene.physics.add(StaticBody, new Vec2(x, y), new Vec2(tileSize.x * 4, tileSize.y * 4));
|
||||||
|
// }
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load images for the tilesets
|
// Load images for the tilesets
|
||||||
|
|
|
@ -8,11 +8,10 @@ export default class GameState{
|
||||||
private worldSize: Vec2;
|
private worldSize: Vec2;
|
||||||
private viewport: Viewport;
|
private viewport: Viewport;
|
||||||
|
|
||||||
constructor(){
|
constructor(viewport: Viewport){
|
||||||
this.sceneStack = new Stack(10);
|
this.sceneStack = new Stack(10);
|
||||||
this.worldSize = new Vec2(1600, 1000);
|
this.worldSize = new Vec2(1600, 1000);
|
||||||
this.viewport = new Viewport();
|
this.viewport = viewport;
|
||||||
this.viewport.setSize(800, 500);
|
|
||||||
this.viewport.setBounds(0, 0, 2560, 1280);
|
this.viewport.setBounds(0, 0, 2560, 1280);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,6 +98,7 @@ export default class Scene {
|
||||||
this.viewport.update(deltaT);
|
this.viewport.update(deltaT);
|
||||||
this.physicsManager.update(deltaT);
|
this.physicsManager.update(deltaT);
|
||||||
this.sceneGraph.update(deltaT);
|
this.sceneGraph.update(deltaT);
|
||||||
|
this.tilemaps.forEach((tilemap: Tilemap) => tilemap.update(deltaT));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import Receiver from "../Events/Receiver";
|
||||||
import Map from "../DataTypes/Map";
|
import Map from "../DataTypes/Map";
|
||||||
import Vec2 from "../DataTypes/Vec2";
|
import Vec2 from "../DataTypes/Vec2";
|
||||||
import EventQueue from "../Events/EventQueue";
|
import EventQueue from "../Events/EventQueue";
|
||||||
|
import Viewport from "../SceneGraph/Viewport";
|
||||||
|
|
||||||
export default class InputReceiver{
|
export default class InputReceiver{
|
||||||
private static instance: InputReceiver = null;
|
private static instance: InputReceiver = null;
|
||||||
|
@ -14,6 +15,7 @@ export default class InputReceiver{
|
||||||
private mousePressPosition: Vec2;
|
private mousePressPosition: Vec2;
|
||||||
private eventQueue: EventQueue;
|
private eventQueue: EventQueue;
|
||||||
private receiver: Receiver;
|
private receiver: Receiver;
|
||||||
|
private viewport: Viewport;
|
||||||
|
|
||||||
private constructor(){
|
private constructor(){
|
||||||
this.mousePressed = false;
|
this.mousePressed = false;
|
||||||
|
@ -108,7 +110,19 @@ export default class InputReceiver{
|
||||||
return this.mousePosition;
|
return this.mousePosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getGlobalMousePosition(): Vec2 {
|
||||||
|
return this.mousePosition.clone().add(this.viewport.getPosition());
|
||||||
|
}
|
||||||
|
|
||||||
getMousePressPosition(): Vec2 {
|
getMousePressPosition(): Vec2 {
|
||||||
return this.mousePressPosition;
|
return this.mousePressPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getGlobalMousePressPosition(): Vec2 {
|
||||||
|
return this.mousePressPosition.clone().add(this.viewport.getPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
setViewport(viewport: Viewport): void {
|
||||||
|
this.viewport = viewport;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -5,6 +5,7 @@ import Recorder from "../Playback/Recorder";
|
||||||
import GameState from "../GameState/GameState";
|
import GameState from "../GameState/GameState";
|
||||||
import Debug from "../Debug/Debug";
|
import Debug from "../Debug/Debug";
|
||||||
import ResourceManager from "../ResourceManager/ResourceManager";
|
import ResourceManager from "../ResourceManager/ResourceManager";
|
||||||
|
import Viewport from "../SceneGraph/Viewport";
|
||||||
|
|
||||||
export default class GameLoop{
|
export default class GameLoop{
|
||||||
// The amount of time to spend on a physics step
|
// The amount of time to spend on a physics step
|
||||||
|
@ -29,7 +30,8 @@ export default class GameLoop{
|
||||||
|
|
||||||
readonly GAME_CANVAS: HTMLCanvasElement;
|
readonly GAME_CANVAS: HTMLCanvasElement;
|
||||||
readonly WIDTH: number;
|
readonly WIDTH: number;
|
||||||
readonly HEIGHT: number;
|
readonly HEIGHT: number;
|
||||||
|
private viewport: Viewport;
|
||||||
private ctx: CanvasRenderingContext2D;
|
private ctx: CanvasRenderingContext2D;
|
||||||
private eventQueue: EventQueue;
|
private eventQueue: EventQueue;
|
||||||
private inputHandler: InputHandler;
|
private inputHandler: InputHandler;
|
||||||
|
@ -57,12 +59,15 @@ export default class GameLoop{
|
||||||
this.WIDTH = 800;
|
this.WIDTH = 800;
|
||||||
this.HEIGHT = 500;
|
this.HEIGHT = 500;
|
||||||
this.ctx = this.initializeCanvas(this.GAME_CANVAS, this.WIDTH, this.HEIGHT);
|
this.ctx = this.initializeCanvas(this.GAME_CANVAS, this.WIDTH, this.HEIGHT);
|
||||||
|
this.viewport = new Viewport();
|
||||||
|
this.viewport.setSize(this.WIDTH, this.HEIGHT);
|
||||||
|
|
||||||
this.eventQueue = EventQueue.getInstance();
|
this.eventQueue = EventQueue.getInstance();
|
||||||
this.inputHandler = new InputHandler(this.GAME_CANVAS);
|
this.inputHandler = new InputHandler(this.GAME_CANVAS);
|
||||||
this.inputReceiver = InputReceiver.getInstance();
|
this.inputReceiver = InputReceiver.getInstance();
|
||||||
|
this.inputReceiver.setViewport(this.viewport);
|
||||||
this.recorder = new Recorder();
|
this.recorder = new Recorder();
|
||||||
this.gameState = new GameState();
|
this.gameState = new GameState(this.viewport);
|
||||||
this.debug = Debug.getInstance();
|
this.debug = Debug.getInstance();
|
||||||
this.resourceManager = ResourceManager.getInstance();
|
this.resourceManager = ResourceManager.getInstance();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,13 @@ import { TiledTilemapData, TiledLayerData } from "../DataTypes/Tilesets/TiledDat
|
||||||
*/
|
*/
|
||||||
export default abstract class Tilemap extends GameNode {
|
export default abstract class Tilemap extends GameNode {
|
||||||
protected data: number[];
|
protected data: number[];
|
||||||
|
protected collisionData: number [];
|
||||||
protected tilesets: Tileset[];
|
protected tilesets: Tileset[];
|
||||||
protected worldSize: Vec2;
|
protected worldSize: Vec2;
|
||||||
protected tileSize: Vec2;
|
protected tileSize: Vec2;
|
||||||
protected visible: boolean;
|
protected visible: boolean;
|
||||||
protected collidable: boolean;
|
protected collidable: boolean;
|
||||||
|
protected scale: Vec2;
|
||||||
|
|
||||||
constructor(tilemapData: TiledTilemapData, layerData: TiledLayerData){
|
constructor(tilemapData: TiledTilemapData, layerData: TiledLayerData){
|
||||||
super();
|
super();
|
||||||
|
@ -20,6 +22,7 @@ export default abstract class Tilemap extends GameNode {
|
||||||
this.worldSize = new Vec2(0, 0);
|
this.worldSize = new Vec2(0, 0);
|
||||||
this.tileSize = new Vec2(0, 0);
|
this.tileSize = new Vec2(0, 0);
|
||||||
this.parseTilemapData(tilemapData, layerData);
|
this.parseTilemapData(tilemapData, layerData);
|
||||||
|
this.scale = new Vec2(4, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
isCollidable(): boolean {
|
isCollidable(): boolean {
|
||||||
|
@ -39,9 +42,19 @@ export default abstract class Tilemap extends GameNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
getTileSize(): Vec2 {
|
getTileSize(): Vec2 {
|
||||||
return this.tileSize;
|
return this.tileSize.clone().scale(this.scale.x, this.scale.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getScale(): Vec2 {
|
||||||
|
return this.scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
setScale(scale: Vec2): void {
|
||||||
|
this.scale = scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract getTileAt(worldCoords: Vec2): number;
|
||||||
|
|
||||||
isReady(): boolean {
|
isReady(): boolean {
|
||||||
if(this.tilesets.length !== 0){
|
if(this.tilesets.length !== 0){
|
||||||
for(let tileset of this.tilesets){
|
for(let tileset of this.tilesets){
|
||||||
|
|
|
@ -10,6 +10,7 @@ export default class OrthogonalTilemap extends Tilemap {
|
||||||
this.worldSize.set(tilemapData.width, tilemapData.height);
|
this.worldSize.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.collisionData = this.data.map(tile => tile !== 0 ? 1 : 0);
|
||||||
this.visible = layer.visible;
|
this.visible = layer.visible;
|
||||||
this.collidable = false;
|
this.collidable = false;
|
||||||
if(layer.properties){
|
if(layer.properties){
|
||||||
|
@ -22,7 +23,40 @@ export default class OrthogonalTilemap extends Tilemap {
|
||||||
tilemapData.tilesets.forEach(tilesetData => this.tilesets.push(new Tileset(tilesetData)));
|
tilemapData.tilesets.forEach(tilesetData => this.tilesets.push(new Tileset(tilesetData)));
|
||||||
}
|
}
|
||||||
|
|
||||||
forEachTile(func: Function){
|
getTileAt(worldCoords: Vec2): number {
|
||||||
|
let localCoords = this.getColRowAt(worldCoords);
|
||||||
|
if(localCoords.x < 0 || localCoords.x >= this.worldSize.x || localCoords.y < 0 || localCoords.y >= this.worldSize.y){
|
||||||
|
// There are no tiles in negative positions or out of bounds positions
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.data[localCoords.y * this.worldSize.x + localCoords.x];
|
||||||
|
}
|
||||||
|
|
||||||
|
isTileCollidable(indexOrCol: number, row?: number): boolean {
|
||||||
|
if(row){
|
||||||
|
if(indexOrCol < 0 || indexOrCol >= this.worldSize.x || row < 0 || row >= this.worldSize.y){
|
||||||
|
// There are no tiles in negative positions or out of bounds positions
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.collisionData[row * this.worldSize.x + indexOrCol] === 1 && this.collidable;
|
||||||
|
} else {
|
||||||
|
if(indexOrCol < 0 || indexOrCol >= this.collisionData.length){
|
||||||
|
// Tiles that don't exist aren't collidable
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.collisionData[indexOrCol] === 1 && this.collidable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Should this throw an error if someone tries to access an out of bounds value?
|
||||||
|
getColRowAt(worldCoords: Vec2): Vec2 {
|
||||||
|
let col = Math.floor(worldCoords.x / this.tileSize.x / this.scale.x);
|
||||||
|
let row = Math.floor(worldCoords.y / this.tileSize.y / this.scale.y);
|
||||||
|
return new Vec2(col, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
forEachTile(func: Function): void {
|
||||||
for(let i = 0; i < this.data.length; i++){
|
for(let i = 0; i < this.data.length; i++){
|
||||||
func(this.data[i], i);
|
func(this.data[i], i);
|
||||||
}
|
}
|
||||||
|
@ -37,7 +71,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);
|
tileset.renderTile(ctx, tileIndex, i, this.worldSize, origin, this.scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,18 @@ import Vec2 from "../DataTypes/Vec2";
|
||||||
import StaticBody from "./StaticBody";
|
import StaticBody from "./StaticBody";
|
||||||
import Debug from "../Debug/Debug";
|
import Debug from "../Debug/Debug";
|
||||||
import MathUtils from "../Utils/MathUtils";
|
import MathUtils from "../Utils/MathUtils";
|
||||||
|
import Tilemap from "../Nodes/Tilemap";
|
||||||
|
import OrthogonalTilemap from "../Nodes/Tilemaps/OrthogonalTilemap";
|
||||||
|
|
||||||
export default class PhysicsManager {
|
export default class PhysicsManager {
|
||||||
|
|
||||||
physicsNodes: Array<PhysicsNode>;
|
physicsNodes: Array<PhysicsNode>;
|
||||||
|
tilemaps: Array<Tilemap>;
|
||||||
movements: Array<MovementData>;
|
movements: Array<MovementData>;
|
||||||
|
|
||||||
constructor(){
|
constructor(){
|
||||||
this.physicsNodes = new Array();
|
this.physicsNodes = new Array();
|
||||||
|
this.tilemaps = new Array();
|
||||||
this.movements = new Array();
|
this.movements = new Array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +22,10 @@ export default class PhysicsManager {
|
||||||
this.physicsNodes.push(node);
|
this.physicsNodes.push(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addTilemap(tilemap: Tilemap): void {
|
||||||
|
this.tilemaps.push(tilemap);
|
||||||
|
}
|
||||||
|
|
||||||
addMovement(node: PhysicsNode, velocity: Vec2){
|
addMovement(node: PhysicsNode, velocity: Vec2){
|
||||||
this.movements.push(new MovementData(node, velocity));
|
this.movements.push(new MovementData(node, velocity));
|
||||||
}
|
}
|
||||||
|
@ -51,8 +59,14 @@ export default class PhysicsManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for(let staticNode of staticSet){
|
// TODO handle collisions between dynamic nodes
|
||||||
this.handleCollision(movingNode, staticNode, velocity, (<StaticBody>staticNode).id);
|
// We probably want to sort them by their left edges
|
||||||
|
|
||||||
|
// TODO: handle collisions between dynamic nodes and static nodes
|
||||||
|
|
||||||
|
// Handle Collisions with the tilemaps
|
||||||
|
for(let tilemap of this.tilemaps){
|
||||||
|
this.collideWithTilemap(movingNode, tilemap, velocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
movingNode.finishMove(velocity);
|
movingNode.finishMove(velocity);
|
||||||
|
@ -62,102 +76,90 @@ export default class PhysicsManager {
|
||||||
this.movements = new Array();
|
this.movements = new Array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
collideWithTilemap(node: PhysicsNode, tilemap: Tilemap, velocity: Vec2){
|
||||||
|
if(tilemap instanceof OrthogonalTilemap){
|
||||||
|
this.collideWithOrthogonalTilemap(node, tilemap, velocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collideWithOrthogonalTilemap(node: PhysicsNode, tilemap: OrthogonalTilemap, velocity: Vec2){
|
||||||
|
let startPos = node.getPosition();
|
||||||
|
let endPos = startPos.clone().add(velocity);
|
||||||
|
let size = node.getCollider().getSize();
|
||||||
|
let min = new Vec2(Math.min(startPos.x, endPos.x), Math.min(startPos.y, endPos.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 minIndex = tilemap.getColRowAt(min);
|
||||||
|
let maxIndex = tilemap.getColRowAt(max);
|
||||||
|
|
||||||
|
let tilemapCollisions = new Array<TileCollisionData>();
|
||||||
|
let tileSize = tilemap.getTileSize();
|
||||||
|
|
||||||
|
Debug.getInstance().log("tilemapCollision", "");
|
||||||
|
// Loop over all possible tiles
|
||||||
|
for(let col = minIndex.x; col <= maxIndex.x; col++){
|
||||||
|
for(let row = minIndex.y; row <= maxIndex.y; row++){
|
||||||
|
if(tilemap.isTileCollidable(col, row)){
|
||||||
|
Debug.getInstance().log("tilemapCollision", "Colliding with Tile");
|
||||||
|
|
||||||
|
// Tile position
|
||||||
|
let tilePos = new Vec2(col * tileSize.x, row * tileSize.y);
|
||||||
|
|
||||||
|
// Calculate collision area
|
||||||
|
let dx = Math.min(startPos.x, tilePos.x) - Math.max(startPos.x + size.x, tilePos.x + size.x);
|
||||||
|
let dy = Math.min(startPos.y, tilePos.y) - Math.max(startPos.y + size.y, tilePos.y + size.y);
|
||||||
|
|
||||||
|
let overlap = 0;
|
||||||
|
if(dx * dy > 0){
|
||||||
|
overlap = dx * dy;
|
||||||
|
}
|
||||||
|
|
||||||
|
tilemapCollisions.push(new TileCollisionData(tilePos, overlap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we have all collisions, sort by collision area
|
||||||
|
tilemapCollisions = tilemapCollisions.sort((a, b) => a.overlapArea - b.overlapArea);
|
||||||
|
|
||||||
|
// Resolve the collisions
|
||||||
|
tilemapCollisions.forEach(collision => {
|
||||||
|
let [firstContact, _, collidingX, collidingY] = this.getTimeOfCollision(startPos, size, velocity, collision.position, tileSize, new Vec2(0, 0));
|
||||||
|
|
||||||
|
// Handle collision
|
||||||
|
if( (firstContact.x < 1 || collidingX) && (firstContact.y < 1 || collidingY)){
|
||||||
|
if(collidingX && collidingY){
|
||||||
|
// If we're already intersecting, freak out I guess?
|
||||||
|
} else {
|
||||||
|
// let contactTime = Math.min(firstContact.x, firstContact.y);
|
||||||
|
// velocity.scale(contactTime);
|
||||||
|
let xScale = MathUtils.clamp(firstContact.x, 0, 1);
|
||||||
|
let yScale = MathUtils.clamp(firstContact.y, 0, 1);
|
||||||
|
|
||||||
|
// Handle special case of stickiness on corner to corner collisions
|
||||||
|
if(xScale === yScale){
|
||||||
|
xScale = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(yScale !== 1){
|
||||||
|
node.setIsGrounded(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
velocity.scale(xScale, yScale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
handleCollision(movingNode: PhysicsNode, staticNode: PhysicsNode, velocity: Vec2, id: String){
|
handleCollision(movingNode: PhysicsNode, staticNode: PhysicsNode, velocity: Vec2, id: String){
|
||||||
let sizeA = movingNode.getCollider().getSize();
|
let sizeA = movingNode.getCollider().getSize();
|
||||||
let A = movingNode.getPosition();
|
let posA = movingNode.getPosition();
|
||||||
let velA = velocity;
|
let velA = velocity;
|
||||||
let sizeB = staticNode.getCollider().getSize();
|
let sizeB = staticNode.getCollider().getSize();
|
||||||
let B = staticNode.getPosition();
|
let posB = staticNode.getPosition();
|
||||||
let velB = new Vec2(0, 0);
|
let velB = new Vec2(0, 0);
|
||||||
|
|
||||||
let firstContact = new Vec2(0, 0);
|
|
||||||
let lastContact = new Vec2(0, 0);
|
|
||||||
|
|
||||||
let collidingX = false;
|
let [firstContact, _, collidingX, collidingY] = this.getTimeOfCollision(posA, sizeA, velA, posB, sizeB, velB);
|
||||||
let collidingY = false;
|
|
||||||
|
|
||||||
// Sort by position
|
|
||||||
if(B.x < A.x){
|
|
||||||
// Swap, because B is to the left of A
|
|
||||||
let temp: Vec2;
|
|
||||||
temp = sizeA;
|
|
||||||
sizeA = sizeB;
|
|
||||||
sizeB = temp;
|
|
||||||
|
|
||||||
temp = A;
|
|
||||||
A = B;
|
|
||||||
B = temp;
|
|
||||||
|
|
||||||
temp = velA;
|
|
||||||
velA = velB;
|
|
||||||
velB = temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A is left, B is right
|
|
||||||
firstContact.x = Infinity;
|
|
||||||
lastContact.x = Infinity;
|
|
||||||
|
|
||||||
if (B.x >= A.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 = (B.x - (A.x + (sizeA.x)))/(relVel);
|
|
||||||
lastContact.x = ((B.x + sizeB.x) - A.x)/(relVel);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
collidingX = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(B.y < A.y){
|
|
||||||
// Swap, because B is above A
|
|
||||||
let temp: Vec2;
|
|
||||||
temp = sizeA;
|
|
||||||
sizeA = sizeB;
|
|
||||||
sizeB = temp;
|
|
||||||
|
|
||||||
temp = A;
|
|
||||||
A = B;
|
|
||||||
B = temp;
|
|
||||||
|
|
||||||
temp = velA;
|
|
||||||
velA = velB;
|
|
||||||
velB = temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A is top, B is bottom
|
|
||||||
firstContact.y = Infinity;
|
|
||||||
lastContact.y = Infinity;
|
|
||||||
|
|
||||||
if (B.y >= A.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 = (B.y - (A.y + (sizeA.y)))/(relVel);
|
|
||||||
lastContact.y = ((B.y + sizeB.y) - A.y)/(relVel);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
collidingY = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(B.x < A.x){
|
|
||||||
// Swap, because B is to the left of A
|
|
||||||
let temp: Vec2;
|
|
||||||
temp = sizeA;
|
|
||||||
sizeA = sizeB;
|
|
||||||
sizeB = temp;
|
|
||||||
|
|
||||||
temp = A;
|
|
||||||
A = B;
|
|
||||||
B = temp;
|
|
||||||
|
|
||||||
temp = velA;
|
|
||||||
velA = velB;
|
|
||||||
velB = temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( (firstContact.x < 1 || collidingX) && (firstContact.y < 1 || collidingY)){
|
if( (firstContact.x < 1 || collidingX) && (firstContact.y < 1 || collidingY)){
|
||||||
if(collidingX && collidingY){
|
if(collidingX && collidingY){
|
||||||
|
@ -169,13 +171,96 @@ export default class PhysicsManager {
|
||||||
let yScale = MathUtils.clamp(firstContact.y, 0, 1);
|
let yScale = MathUtils.clamp(firstContact.y, 0, 1);
|
||||||
if(yScale !== 1){
|
if(yScale !== 1){
|
||||||
movingNode.setIsGrounded(true);
|
movingNode.setIsGrounded(true);
|
||||||
}
|
}
|
||||||
velocity.scale(xScale, yScale);
|
velocity.scale(xScale, yScale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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).
|
||||||
|
*/
|
||||||
|
getTimeOfCollision(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];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper classes for internal data
|
||||||
class MovementData{
|
class MovementData{
|
||||||
node: PhysicsNode;
|
node: PhysicsNode;
|
||||||
velocity: Vec2;
|
velocity: Vec2;
|
||||||
|
@ -183,4 +268,13 @@ class MovementData{
|
||||||
this.node = node;
|
this.node = node;
|
||||||
this.velocity = velocity;
|
this.velocity = velocity;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TileCollisionData {
|
||||||
|
position: Vec2;
|
||||||
|
overlapArea: number;
|
||||||
|
constructor(position: Vec2, overlapArea: number){
|
||||||
|
this.position = position;
|
||||||
|
this.overlapArea = overlapArea;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -16,7 +16,7 @@ export default class Player extends PhysicsNode {
|
||||||
super();
|
super();
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.velocity = new Vec2(0, 0);
|
this.velocity = new Vec2(0, 0);
|
||||||
this.speed = 500;
|
this.speed = 600;
|
||||||
this.size = new Vec2(50, 50);
|
this.size = new Vec2(50, 50);
|
||||||
this.collider = new AABB();
|
this.collider = new AABB();
|
||||||
this.collider.setSize(this.size);
|
this.collider.setSize(this.size);
|
||||||
|
|
10
src/main.ts
10
src/main.ts
|
@ -74,12 +74,12 @@ function main(){
|
||||||
pauseMenu.disable();
|
pauseMenu.disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
// backgroundScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/Background.json");
|
backgroundScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/Background.json");
|
||||||
// mainScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/Platformer.json");
|
mainScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/Platformer.json");
|
||||||
// let player = mainScene.physics.add(Player, "platformer");
|
let player = mainScene.physics.add(Player, "platformer");
|
||||||
|
|
||||||
mainScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/TopDown.json");
|
// mainScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/TopDown.json");
|
||||||
let player = mainScene.physics.add(Player, "topdown");
|
// let player = mainScene.physics.add(Player, "topdown");
|
||||||
|
|
||||||
mainScene.getViewport().follow(player);
|
mainScene.getViewport().follow(player);
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
"src/Loop/GameLoop.ts",
|
"src/Loop/GameLoop.ts",
|
||||||
|
|
||||||
"src/Nodes/Tilemaps/OrgthogonalTilemap.ts",
|
"src/Nodes/Tilemaps/OrthogonalTilemap.ts",
|
||||||
"src/Nodes/UIElements/Button.ts",
|
"src/Nodes/UIElements/Button.ts",
|
||||||
"src/Nodes/UIElements/Label.ts",
|
"src/Nodes/UIElements/Label.ts",
|
||||||
"src/Nodes/CanvasNode.ts",
|
"src/Nodes/CanvasNode.ts",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user