finished implementing physics layers and added support for tilemap editing with code
This commit is contained in:
parent
9dc8cd29d1
commit
6149b983a5
|
@ -1,4 +1,3 @@
|
||||||
import GameEvent from "../../Events/GameEvent";
|
|
||||||
import Map from "../Map";
|
import Map from "../Map";
|
||||||
import AABB from "../Shapes/AABB";
|
import AABB from "../Shapes/AABB";
|
||||||
import Shape from "../Shapes/Shape";
|
import Shape from "../Shapes/Shape";
|
||||||
|
@ -77,6 +76,12 @@ export interface Physical {
|
||||||
/** The rectangle swept by the movement of this object, if dynamic */
|
/** The rectangle swept by the movement of this object, if dynamic */
|
||||||
sweptRect: AABB;
|
sweptRect: AABB;
|
||||||
|
|
||||||
|
/** A boolean representing whether or not the node just collided with the tilemap */
|
||||||
|
collidedWithTilemap: boolean;
|
||||||
|
|
||||||
|
/** The physics layer this node belongs to */
|
||||||
|
physicsLayer: number;
|
||||||
|
|
||||||
isPlayer: boolean;
|
isPlayer: boolean;
|
||||||
|
|
||||||
/*---------- FUNCTIONS ----------*/
|
/*---------- FUNCTIONS ----------*/
|
||||||
|
@ -107,6 +112,12 @@ export interface Physical {
|
||||||
* @param eventType The name of the event to send when this trigger is activated
|
* @param eventType The name of the event to send when this trigger is activated
|
||||||
*/
|
*/
|
||||||
addTrigger: (group: string, eventType: string) => void;
|
addTrigger: (group: string, eventType: string) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the physics layer of this node
|
||||||
|
* @param layer The name of the layer
|
||||||
|
*/
|
||||||
|
setPhysicsLayer: (layer: String) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -66,4 +66,12 @@ export default class Map<T> implements Collection {
|
||||||
clear(): void {
|
clear(): void {
|
||||||
this.forEach(key => delete this.map[key]);
|
this.forEach(key => delete this.map[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
let str = "";
|
||||||
|
|
||||||
|
this.forEach((key) => str += key + " -> " + this.get(key).toString() + "\n");
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -234,6 +234,10 @@ export default class AABB extends Shape {
|
||||||
clone(): AABB {
|
clone(): AABB {
|
||||||
return new AABB(this.center.clone(), this.halfSize.clone());
|
return new AABB(this.center.clone(), this.halfSize.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return "(center: " + this.center.toString() + ", half-size: " + this.halfSize.toString() + ")"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Hit {
|
export class Hit {
|
||||||
|
|
|
@ -74,6 +74,10 @@ export default class Tileset {
|
||||||
return this.numCols;
|
return this.numCols;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTileCount(): number {
|
||||||
|
return this.endIndex - this.startIndex + 1;
|
||||||
|
}
|
||||||
|
|
||||||
hasTile(tileIndex: number): boolean {
|
hasTile(tileIndex: number): boolean {
|
||||||
return tileIndex >= this.startIndex && tileIndex <= this.endIndex;
|
return tileIndex >= this.startIndex && tileIndex <= this.endIndex;
|
||||||
}
|
}
|
||||||
|
@ -87,7 +91,7 @@ export default class Tileset {
|
||||||
* @param origin The viewport origin in the current layer
|
* @param origin The viewport origin in the current layer
|
||||||
* @param scale The scale of the tilemap
|
* @param scale The scale of the tilemap
|
||||||
*/
|
*/
|
||||||
renderTile(ctx: CanvasRenderingContext2D, tileIndex: number, dataIndex: number, worldSize: Vec2, origin: Vec2, scale: Vec2, zoom: number): void {
|
renderTile(ctx: CanvasRenderingContext2D, tileIndex: number, dataIndex: number, maxCols: number, origin: Vec2, scale: Vec2, zoom: number): void {
|
||||||
let image = ResourceManager.getInstance().getImage(this.imageKey);
|
let image = ResourceManager.getInstance().getImage(this.imageKey);
|
||||||
|
|
||||||
// Get the true index
|
// Get the true index
|
||||||
|
@ -102,8 +106,8 @@ export default class Tileset {
|
||||||
let top = row * height;
|
let top = row * height;
|
||||||
|
|
||||||
// Calculate the position in the world to render the tile
|
// Calculate the position in the world to render the tile
|
||||||
let x = Math.floor((dataIndex % worldSize.x) * width * scale.x);
|
let x = Math.floor((dataIndex % maxCols) * width * scale.x);
|
||||||
let y = Math.floor(Math.floor(dataIndex / worldSize.x) * height * scale.y);
|
let y = Math.floor(Math.floor(dataIndex / maxCols) * height * scale.y);
|
||||||
ctx.drawImage(image, left, top, width, height, Math.floor((x - origin.x)*zoom), Math.floor((y - origin.y)*zoom), Math.ceil(width * scale.x * zoom), Math.ceil(height * scale.y * zoom));
|
ctx.drawImage(image, left, top, width, height, Math.floor((x - origin.x)*zoom), Math.floor((y - origin.y)*zoom), Math.ceil(width * scale.x * zoom), Math.ceil(height * scale.y * zoom));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -286,29 +286,12 @@ export default class GameLoop {
|
||||||
|
|
||||||
class GameOptions {
|
class GameOptions {
|
||||||
viewportSize: {x: number, y: number}
|
viewportSize: {x: number, y: number}
|
||||||
physics: {
|
|
||||||
numPhysicsLayers: number,
|
|
||||||
physicsLayerNames: Array<string>,
|
|
||||||
physicsLayerCollisions: Array<Array<number>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
static parse(options: Record<string, any>): GameOptions {
|
static parse(options: Record<string, any>): GameOptions {
|
||||||
let gOpt = new GameOptions();
|
let gOpt = new GameOptions();
|
||||||
|
|
||||||
gOpt.viewportSize = options.viewportSize ? options.viewportSize : {x: 800, y: 600};
|
gOpt.viewportSize = options.viewportSize ? options.viewportSize : {x: 800, y: 600};
|
||||||
|
|
||||||
gOpt.physics = {
|
|
||||||
numPhysicsLayers: 10,
|
|
||||||
physicsLayerNames: null,
|
|
||||||
physicsLayerCollisions: ArrayUtils.ones2d(10, 10)
|
|
||||||
};
|
|
||||||
|
|
||||||
if(options.physics){
|
|
||||||
if(options.physics.numPhysicsLayers) gOpt.physics.numPhysicsLayers = options.physics.numPhysicsLayers;
|
|
||||||
if(options.physics.physicsLayerNames) gOpt.physics.physicsLayerNames = options.physics.physicsLayerNames;
|
|
||||||
if(options.physics.physicsLayerCollisions) gOpt.physics.physicsLayerCollisions = options.physics.physicsLayerCollisions;
|
|
||||||
}
|
|
||||||
|
|
||||||
return gOpt;
|
return gOpt;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -35,6 +35,8 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
|
||||||
triggers: Map<string>;
|
triggers: Map<string>;
|
||||||
_velocity: Vec2;
|
_velocity: Vec2;
|
||||||
sweptRect: AABB;
|
sweptRect: AABB;
|
||||||
|
collidedWithTilemap: boolean;
|
||||||
|
physicsLayer: number;
|
||||||
isPlayer: boolean;
|
isPlayer: boolean;
|
||||||
|
|
||||||
/*---------- ACTOR ----------*/
|
/*---------- ACTOR ----------*/
|
||||||
|
@ -124,6 +126,8 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
|
||||||
this.triggers = new Map();
|
this.triggers = new Map();
|
||||||
this._velocity = Vec2.ZERO;
|
this._velocity = Vec2.ZERO;
|
||||||
this.sweptRect = new AABB();
|
this.sweptRect = new AABB();
|
||||||
|
this.collidedWithTilemap = false;
|
||||||
|
this.physicsLayer = -1;
|
||||||
|
|
||||||
if(collisionShape){
|
if(collisionShape){
|
||||||
this.collisionShape = collisionShape;
|
this.collisionShape = collisionShape;
|
||||||
|
@ -147,6 +151,10 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
|
||||||
this.triggers.add(group, eventType);
|
this.triggers.add(group, eventType);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setPhysicsLayer = (layer: string): void => {
|
||||||
|
this.scene.getPhysicsManager().setLayer(this, layer);
|
||||||
|
}
|
||||||
|
|
||||||
/*---------- ACTOR ----------*/
|
/*---------- ACTOR ----------*/
|
||||||
get ai(): AI {
|
get ai(): AI {
|
||||||
return this._ai;
|
return this._ai;
|
||||||
|
|
|
@ -1,62 +1,83 @@
|
||||||
import Vec2 from "../DataTypes/Vec2";
|
import Vec2 from "../DataTypes/Vec2";
|
||||||
import GameNode from "./GameNode";
|
|
||||||
import Tileset from "../DataTypes/Tilesets/Tileset";
|
import Tileset from "../DataTypes/Tilesets/Tileset";
|
||||||
import { TiledTilemapData, TiledLayerData } from "../DataTypes/Tilesets/TiledData"
|
import { TiledTilemapData, TiledLayerData } from "../DataTypes/Tilesets/TiledData"
|
||||||
|
import CanvasNode from "./CanvasNode";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The representation of a tilemap - this can consist of a combination of tilesets in one layer
|
* The representation of a tilemap - this can consist of a combination of tilesets in one layer
|
||||||
*/
|
*/
|
||||||
export default abstract class Tilemap extends GameNode {
|
export default abstract class Tilemap extends CanvasNode {
|
||||||
// A tileset represents the tiles within one specific image loaded from a file
|
|
||||||
protected tilesets: Array<Tileset>;
|
protected tilesets: Array<Tileset>;
|
||||||
protected size: Vec2;
|
|
||||||
protected tileSize: Vec2;
|
protected tileSize: Vec2;
|
||||||
protected scale: Vec2;
|
protected data: Array<number>;
|
||||||
public data: Array<number>;
|
protected collisionMap: Array<boolean>;
|
||||||
public visible: boolean;
|
name: string;
|
||||||
|
|
||||||
// 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>, scale: Vec2) {
|
constructor(tilemapData: TiledTilemapData, layer: TiledLayerData, tilesets: Array<Tileset>, scale: Vec2) {
|
||||||
super();
|
super();
|
||||||
this.tilesets = tilesets;
|
this.tilesets = tilesets;
|
||||||
this.size = new Vec2(0, 0);
|
|
||||||
this.tileSize = new Vec2(0, 0);
|
this.tileSize = new Vec2(0, 0);
|
||||||
|
this.name = layer.name;
|
||||||
|
|
||||||
|
let tilecount = 0;
|
||||||
|
for(let tileset of tilesets){
|
||||||
|
tilecount += tileset.getTileCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.collisionMap = new Array(tilecount);
|
||||||
|
for(let i = 0; i < this.collisionMap.length; i++){
|
||||||
|
this.collisionMap[i] = false;
|
||||||
|
}
|
||||||
|
|
||||||
// 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 = scale.clone();
|
this.scale.set(scale.x, scale.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of the tilesets associated with this tilemap
|
||||||
|
*/
|
||||||
getTilesets(): Tileset[] {
|
getTilesets(): Tileset[] {
|
||||||
return this.tilesets;
|
return this.tilesets;
|
||||||
}
|
}
|
||||||
|
|
||||||
getsize(): Vec2 {
|
/**
|
||||||
return this.size;
|
* Returns the size of tiles in this tilemap as they appear in the game world after scaling
|
||||||
}
|
*/
|
||||||
|
|
||||||
getTileSize(): Vec2 {
|
getTileSize(): Vec2 {
|
||||||
return this.tileSize.clone().scale(this.scale.x, this.scale.y);
|
return this.tileSize.scaled(this.scale.x, this.scale.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
getScale(): Vec2 {
|
/** Adds this tilemap to the physics system */
|
||||||
return this.scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
setScale(scale: Vec2): void {
|
|
||||||
this.scale = scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
isVisible(): boolean {
|
|
||||||
return this.visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Adds this tilemaps to the physics system */
|
|
||||||
addPhysics = (): void => {
|
addPhysics = (): void => {
|
||||||
this.scene.getPhysicsManager().registerTilemap(this);
|
this.scene.getPhysicsManager().registerTilemap(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract getTileAt(worldCoords: Vec2): number;
|
/**
|
||||||
|
* Returns the value of the tile at the specified position
|
||||||
|
* @param worldCoords The position in world coordinates
|
||||||
|
*/
|
||||||
|
abstract getTileAtWorldPosition(worldCoords: Vec2): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the world position of the top left corner of the tile at the specified index
|
||||||
|
* @param index
|
||||||
|
*/
|
||||||
|
abstract getTileWorldPosition(index: number): Vec2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of the tile at the specified index
|
||||||
|
* @param index
|
||||||
|
*/
|
||||||
|
abstract getTile(index: number): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value of the tile at the specified index
|
||||||
|
* @param index
|
||||||
|
* @param type
|
||||||
|
*/
|
||||||
|
abstract setTile(index: number, type: number): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up the tileset using the data loaded from file
|
* Sets up the tileset using the data loaded from file
|
||||||
|
|
|
@ -8,38 +8,84 @@ import Tileset from "../../DataTypes/Tilesets/Tileset";
|
||||||
*/
|
*/
|
||||||
export default class OrthogonalTilemap extends Tilemap {
|
export default class OrthogonalTilemap extends Tilemap {
|
||||||
|
|
||||||
|
protected numCols: number;
|
||||||
|
protected numRows: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the tilemap data loaded from the json file. DOES NOT process images automatically - the ResourceManager class does this while loading tilemaps
|
* Parses the tilemap data loaded from the json file. DOES NOT process images automatically - the ResourceManager class does this while loading tilemaps
|
||||||
* @param tilemapData
|
* @param tilemapData
|
||||||
* @param layer
|
* @param layer
|
||||||
*/
|
*/
|
||||||
protected parseTilemapData(tilemapData: TiledTilemapData, layer: TiledLayerData): void {
|
protected parseTilemapData(tilemapData: TiledTilemapData, layer: TiledLayerData): void {
|
||||||
this.size.set(tilemapData.width, tilemapData.height);
|
// The size of the tilemap in local space
|
||||||
|
this.numCols = tilemapData.width;
|
||||||
|
this.numRows = tilemapData.height;
|
||||||
|
|
||||||
|
// The size of tiles
|
||||||
this.tileSize.set(tilemapData.tilewidth, tilemapData.tileheight);
|
this.tileSize.set(tilemapData.tilewidth, tilemapData.tileheight);
|
||||||
|
|
||||||
|
// The size of the tilemap on the canvas
|
||||||
|
this.size.set(this.numCols * this.tileSize.x, this.numRows * this.tileSize.y);
|
||||||
|
this.position.copy(this.size);
|
||||||
this.data = layer.data;
|
this.data = layer.data;
|
||||||
this.visible = layer.visible;
|
this.visible = layer.visible;
|
||||||
|
|
||||||
|
// Whether the tilemap is collidable or not
|
||||||
this.isCollidable = false;
|
this.isCollidable = false;
|
||||||
if(layer.properties){
|
if(layer.properties){
|
||||||
for(let item of layer.properties){
|
for(let item of layer.properties){
|
||||||
if(item.name === "Collidable"){
|
if(item.name === "Collidable"){
|
||||||
this.isCollidable = item.value;
|
this.isCollidable = item.value;
|
||||||
|
|
||||||
|
// Set all tiles besides "empty: 0" to be collidable
|
||||||
|
for(let i = 1; i < this.collisionMap.length; i++){
|
||||||
|
this.collisionMap[i] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getTileAtWorldPosition(worldCoords: Vec2): number {
|
||||||
|
let localCoords = this.getColRowAt(worldCoords);
|
||||||
|
return this.getTileAtRowCol(localCoords);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value of the tile at the coordinates in the vector worldCoords
|
* Get the tile at the specified row and column
|
||||||
* @param worldCoords
|
* @param rowCol
|
||||||
*/
|
*/
|
||||||
getTileAt(worldCoords: Vec2): number {
|
getTileAtRowCol(rowCol: Vec2): number {
|
||||||
let localCoords = this.getColRowAt(worldCoords);
|
if(rowCol.x < 0 || rowCol.x >= this.numCols || rowCol.y < 0 || rowCol.y >= this.numRows){
|
||||||
if(localCoords.x < 0 || localCoords.x >= this.size.x || localCoords.y < 0 || localCoords.y >= this.size.y){
|
return -1;
|
||||||
// There are no tiles in negative positions or out of bounds positions
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.data[localCoords.y * this.size.x + localCoords.x]
|
return this.data[rowCol.y * this.numCols + rowCol.x];
|
||||||
|
}
|
||||||
|
|
||||||
|
getTileWorldPosition(index: number): Vec2 {
|
||||||
|
// Get the local position
|
||||||
|
let col = index % this.numCols;
|
||||||
|
let row = Math.floor(index / this.numCols);
|
||||||
|
|
||||||
|
// Get the world position
|
||||||
|
let x = col * this.tileSize.x;
|
||||||
|
let y = row * this.tileSize.y;
|
||||||
|
|
||||||
|
return new Vec2(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTile(index: number): number {
|
||||||
|
return this.data[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
setTile(index: number, type: number): void {
|
||||||
|
this.data[index] = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTileAtRowCol(rowCol: Vec2, type: number): void {
|
||||||
|
let index = rowCol.y * this.numCols + rowCol.x;
|
||||||
|
this.setTile(index, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,33 +94,36 @@ export default class OrthogonalTilemap extends Tilemap {
|
||||||
* @param row
|
* @param row
|
||||||
*/
|
*/
|
||||||
isTileCollidable(indexOrCol: number, row?: number): boolean {
|
isTileCollidable(indexOrCol: number, row?: number): boolean {
|
||||||
let index = 0;
|
// The value of the tile
|
||||||
|
let tile = 0;
|
||||||
|
|
||||||
if(row){
|
if(row){
|
||||||
if(indexOrCol < 0 || indexOrCol >= this.size.x || row < 0 || row >= this.size.y){
|
// We have a column and a row
|
||||||
// There are no tiles in negative positions or out of bounds positions
|
tile = this.getTileAtRowCol(new Vec2(indexOrCol, row));
|
||||||
|
|
||||||
|
if(tile < 0){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
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
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
index = indexOrCol;
|
// We have an index
|
||||||
|
tile = this.getTile(indexOrCol);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - Currently, all tiles in a collidable layer are collidable
|
return this.collisionMap[tile];
|
||||||
return this.data[index] !== 0 && this.isCollidable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes in world coordinates and returns the row and column of the tile at that position
|
* Takes in world coordinates and returns the row and column of the tile at that position
|
||||||
* @param worldCoords
|
* @param worldCoords
|
||||||
*/
|
*/
|
||||||
// TODO: Should this throw an error if someone tries to access an out of bounds value?
|
|
||||||
getColRowAt(worldCoords: Vec2): Vec2 {
|
getColRowAt(worldCoords: Vec2): Vec2 {
|
||||||
let col = Math.floor(worldCoords.x / this.tileSize.x / this.scale.x);
|
let col = Math.floor(worldCoords.x / this.tileSize.x / this.scale.x);
|
||||||
let row = Math.floor(worldCoords.y / this.tileSize.y / this.scale.y);
|
let row = Math.floor(worldCoords.y / this.tileSize.y / this.scale.y);
|
||||||
|
|
||||||
return new Vec2(col, row);
|
return new Vec2(col, row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +143,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.size, origin, this.scale, zoom);
|
tileset.renderTile(ctx, tileIndex, i, this.numCols, origin, this.scale, zoom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,7 @@ import SweepAndPrune from "./BroadPhaseAlgorithms/SweepAndPrune";
|
||||||
import Shape from "../DataTypes/Shapes/Shape";
|
import Shape from "../DataTypes/Shapes/Shape";
|
||||||
import MathUtils from "../Utils/MathUtils";
|
import MathUtils from "../Utils/MathUtils";
|
||||||
import OrthogonalTilemap from "../Nodes/Tilemaps/OrthogonalTilemap";
|
import OrthogonalTilemap from "../Nodes/Tilemaps/OrthogonalTilemap";
|
||||||
import Debug from "../Debug/Debug";
|
|
||||||
import AABB from "../DataTypes/Shapes/AABB";
|
import AABB from "../DataTypes/Shapes/AABB";
|
||||||
import Map from "../DataTypes/Map";
|
|
||||||
|
|
||||||
export default class BasicPhysicsManager extends PhysicsManager {
|
export default class BasicPhysicsManager extends PhysicsManager {
|
||||||
|
|
||||||
|
@ -26,8 +24,8 @@ export default class BasicPhysicsManager extends PhysicsManager {
|
||||||
/** The broad phase collision detection algorithm used by this physics system */
|
/** The broad phase collision detection algorithm used by this physics system */
|
||||||
protected broadPhase: BroadPhase;
|
protected broadPhase: BroadPhase;
|
||||||
|
|
||||||
protected layerMap: Map<number>;
|
/** A 2D array that contains information about which layers interact with each other */
|
||||||
protected layerNames: Array<string>;
|
protected layerMask: number[][];
|
||||||
|
|
||||||
constructor(physicsOptions: Record<string, any>){
|
constructor(physicsOptions: Record<string, any>){
|
||||||
super();
|
super();
|
||||||
|
@ -35,8 +33,6 @@ export default class BasicPhysicsManager extends PhysicsManager {
|
||||||
this.dynamicNodes = new Array();
|
this.dynamicNodes = new Array();
|
||||||
this.tilemaps = new Array();
|
this.tilemaps = new Array();
|
||||||
this.broadPhase = new SweepAndPrune();
|
this.broadPhase = new SweepAndPrune();
|
||||||
this.layerMap = new Map();
|
|
||||||
this.layerNames = new Array();
|
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
if(physicsOptions.physicsLayerNames !== null){
|
if(physicsOptions.physicsLayerNames !== null){
|
||||||
|
@ -56,7 +52,7 @@ export default class BasicPhysicsManager extends PhysicsManager {
|
||||||
this.layerMap.add("" + i, i);
|
this.layerMap.add("" + i, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(this.layerNames);
|
this.layerMask = physicsOptions.physicsLayerCollisions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -101,14 +97,12 @@ export default class BasicPhysicsManager extends PhysicsManager {
|
||||||
// TODO - This is problematic if a collision happens, but it is later learned that another collision happens before it
|
// TODO - This is problematic if a collision happens, but it is later learned that another collision happens before it
|
||||||
if(node1.triggers.has(group2)){
|
if(node1.triggers.has(group2)){
|
||||||
// Node1 should send an event
|
// Node1 should send an event
|
||||||
console.log("Trigger")
|
|
||||||
let eventType = node1.triggers.get(group2);
|
let eventType = node1.triggers.get(group2);
|
||||||
this.emitter.fireEvent(eventType, {node: node1, other: node2, collision: {firstContact: firstContact}});
|
this.emitter.fireEvent(eventType, {node: node1, other: node2, collision: {firstContact: firstContact}});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(node2.triggers.has(group1)){
|
if(node2.triggers.has(group1)){
|
||||||
// Node2 should send an event
|
// Node2 should send an event
|
||||||
console.log("Trigger")
|
|
||||||
let eventType = node2.triggers.get(group1);
|
let eventType = node2.triggers.get(group1);
|
||||||
this.emitter.fireEvent(eventType, {node: node2, other: node1, collision: {firstContact: firstContact}});
|
this.emitter.fireEvent(eventType, {node: node2, other: node1, collision: {firstContact: firstContact}});
|
||||||
}
|
}
|
||||||
|
@ -191,13 +185,10 @@ export default class BasicPhysicsManager extends PhysicsManager {
|
||||||
let tilemapCollisions = new Array<TileCollisionData>();
|
let tilemapCollisions = new Array<TileCollisionData>();
|
||||||
let tileSize = tilemap.getTileSize();
|
let tileSize = tilemap.getTileSize();
|
||||||
|
|
||||||
Debug.log("tilemapCollision", "");
|
|
||||||
|
|
||||||
// Loop over all possible tiles (which isn't many in the scope of the velocity per frame)
|
// Loop over all possible tiles (which isn't many in the scope of the velocity per frame)
|
||||||
for(let col = minIndex.x; col <= maxIndex.x; col++){
|
for(let col = minIndex.x; col <= maxIndex.x; col++){
|
||||||
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");
|
|
||||||
|
|
||||||
// Get the position of this tile
|
// Get the position of this tile
|
||||||
let tilePos = new Vec2(col * tileSize.x + tileSize.x/2, row * tileSize.y + tileSize.y/2);
|
let tilePos = new Vec2(col * tileSize.x + tileSize.x/2, row * tileSize.y + tileSize.y/2);
|
||||||
|
@ -223,16 +214,15 @@ export default class BasicPhysicsManager extends PhysicsManager {
|
||||||
// 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] = Shape.getTimeOfCollision(node.collisionShape, velocity, collision.collider, Vec2.ZERO);
|
let [firstContact, _, collidingX, collidingY] = Shape.getTimeOfCollision(node.collisionShape, 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)){
|
||||||
|
// We are definitely colliding, so add to this node's tilemap collision list
|
||||||
|
node.collidedWithTilemap = true;
|
||||||
|
|
||||||
if(collidingX && collidingY){
|
if(collidingX && collidingY){
|
||||||
// If we're already intersecting, freak out I guess? Probably should handle this in some way for if nodes get spawned inside of tiles
|
// If we're already intersecting, freak out I guess? Probably should handle this in some way for if nodes get spawned inside of tiles
|
||||||
} else {
|
} else {
|
||||||
|
@ -265,7 +255,6 @@ export default class BasicPhysicsManager extends PhysicsManager {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
update(deltaT: number): void {
|
update(deltaT: number): void {
|
||||||
/*---------- INITIALIZATION PHASE ----------*/
|
/*---------- INITIALIZATION PHASE ----------*/
|
||||||
for(let node of this.dynamicNodes){
|
for(let node of this.dynamicNodes){
|
||||||
|
@ -273,6 +262,7 @@ export default class BasicPhysicsManager extends PhysicsManager {
|
||||||
node.onGround = false;
|
node.onGround = false;
|
||||||
node.onCeiling = false;
|
node.onCeiling = false;
|
||||||
node.onWall = false;
|
node.onWall = false;
|
||||||
|
node.collidedWithTilemap = false;
|
||||||
|
|
||||||
// Update the swept shapes of each node
|
// Update the swept shapes of each node
|
||||||
if(node.moving){
|
if(node.moving){
|
||||||
|
@ -303,17 +293,15 @@ export default class BasicPhysicsManager extends PhysicsManager {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure both nodes can collide with each other based on their physics layer
|
||||||
|
if(!(node1.physicsLayer === -1 || node2.physicsLayer === -1 || this.layerMask[node1.physicsLayer][node2.physicsLayer] === 1)){
|
||||||
|
// Nodes do not collide. Continue onto the next pair
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Get Collision (which may or may not happen)
|
// Get Collision (which may or may not happen)
|
||||||
let [firstContact, lastContact, collidingX, collidingY] = Shape.getTimeOfCollision(node1.collisionShape, node1._velocity, node2.collisionShape, node2._velocity);
|
let [firstContact, lastContact, collidingX, collidingY] = Shape.getTimeOfCollision(node1.collisionShape, node1._velocity, node2.collisionShape, node2._velocity);
|
||||||
|
|
||||||
if(node1.isPlayer){
|
|
||||||
if(firstContact.x !== Infinity || firstContact.y !== Infinity)
|
|
||||||
Debug.log("playercol", "First Contact: " + firstContact.toFixed(4))
|
|
||||||
} else if(node2.isPlayer) {
|
|
||||||
if(firstContact.x !== Infinity || firstContact.y !== Infinity)
|
|
||||||
Debug.log("playercol", "First Contact: " + firstContact.toFixed(4))
|
|
||||||
}
|
|
||||||
|
|
||||||
this.resolveCollision(node1, node2, firstContact, lastContact, collidingX, collidingY);
|
this.resolveCollision(node1, node2, firstContact, lastContact, collidingX, collidingY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,10 +310,13 @@ export default class BasicPhysicsManager extends PhysicsManager {
|
||||||
if(node.moving && node.isCollidable){
|
if(node.moving && node.isCollidable){
|
||||||
// If a node is moving and can collide, check it against every tilemap
|
// If a node is moving and can collide, check it against every tilemap
|
||||||
for(let tilemap of this.tilemaps){
|
for(let tilemap of this.tilemaps){
|
||||||
|
// Check if there could even be a collision
|
||||||
|
if(node.sweptRect.overlaps(tilemap.boundary)){
|
||||||
this.collideWithTilemap(node, tilemap, node._velocity);
|
this.collideWithTilemap(node, tilemap, node._velocity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*---------- ENDING PHASE ----------*/
|
/*---------- ENDING PHASE ----------*/
|
||||||
for(let node of this.dynamicNodes){
|
for(let node of this.dynamicNodes){
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { Physical } from "../DataTypes/Interfaces/Descriptors";
|
import { Physical } from "../DataTypes/Interfaces/Descriptors";
|
||||||
import AABB from "../DataTypes/Shapes/AABB";
|
|
||||||
import Vec2 from "../DataTypes/Vec2";
|
import Vec2 from "../DataTypes/Vec2";
|
||||||
|
|
||||||
export class Collision {
|
export class Collision {
|
||||||
|
|
|
@ -4,21 +4,52 @@ import { Debug_Renderable, Updateable } from "../DataTypes/Interfaces/Descriptor
|
||||||
import Tilemap from "../Nodes/Tilemap";
|
import Tilemap from "../Nodes/Tilemap";
|
||||||
import Receiver from "../Events/Receiver";
|
import Receiver from "../Events/Receiver";
|
||||||
import Emitter from "../Events/Emitter";
|
import Emitter from "../Events/Emitter";
|
||||||
|
import Map from "../DataTypes/Map";
|
||||||
|
|
||||||
export default abstract class PhysicsManager implements Updateable, Debug_Renderable {
|
export default abstract class PhysicsManager implements Updateable, Debug_Renderable {
|
||||||
protected receiver: Receiver;
|
protected receiver: Receiver;
|
||||||
protected emitter: Emitter;
|
protected emitter: Emitter;
|
||||||
|
|
||||||
|
/** Layer names to numbers */
|
||||||
|
protected layerMap: Map<number>;
|
||||||
|
|
||||||
|
/** Layer numbers to names */
|
||||||
|
protected layerNames: Array<string>;
|
||||||
|
|
||||||
constructor(){
|
constructor(){
|
||||||
this.receiver = new Receiver();
|
this.receiver = new Receiver();
|
||||||
this.emitter = new Emitter();
|
this.emitter = new Emitter();
|
||||||
|
|
||||||
|
// The creation and implementation of layers is deferred to the subclass
|
||||||
|
this.layerMap = new Map();
|
||||||
|
this.layerNames = new Array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a gamenode with this physics manager
|
||||||
|
* @param object
|
||||||
|
*/
|
||||||
abstract registerObject(object: GameNode): void;
|
abstract registerObject(object: GameNode): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a tilemap with this physics manager
|
||||||
|
* @param tilemap
|
||||||
|
*/
|
||||||
abstract registerTilemap(tilemap: Tilemap): void;
|
abstract registerTilemap(tilemap: Tilemap): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the physics
|
||||||
|
* @param deltaT
|
||||||
|
*/
|
||||||
abstract update(deltaT: number): void;
|
abstract update(deltaT: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders any debug shapes or graphics
|
||||||
|
* @param ctx
|
||||||
|
*/
|
||||||
abstract debug_render(ctx: CanvasRenderingContext2D): void;
|
abstract debug_render(ctx: CanvasRenderingContext2D): void;
|
||||||
|
|
||||||
|
setLayer(node: GameNode, layer: string): void {
|
||||||
|
node.physicsLayer = this.layerMap.get(layer);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -20,6 +20,7 @@ import ParallaxLayer from "./Layers/ParallaxLayer";
|
||||||
import UILayer from "./Layers/UILayer";
|
import UILayer from "./Layers/UILayer";
|
||||||
import CanvasNode from "../Nodes/CanvasNode";
|
import CanvasNode from "../Nodes/CanvasNode";
|
||||||
import GameNode from "../Nodes/GameNode";
|
import GameNode from "../Nodes/GameNode";
|
||||||
|
import ArrayUtils from "../Utils/ArrayUtils";
|
||||||
|
|
||||||
export default class Scene implements Updateable, Renderable {
|
export default class Scene implements Updateable, Renderable {
|
||||||
/** The size of the game world. */
|
/** The size of the game world. */
|
||||||
|
@ -73,7 +74,12 @@ export default class Scene implements Updateable, Renderable {
|
||||||
/** An interface that allows the loading of different files for use in the scene */
|
/** An interface that allows the loading of different files for use in the scene */
|
||||||
public load: ResourceManager;
|
public load: ResourceManager;
|
||||||
|
|
||||||
constructor(viewport: Viewport, sceneManager: SceneManager, game: GameLoop){
|
/** The configuration options for this scene */
|
||||||
|
public sceneOptions: SceneOptions;
|
||||||
|
|
||||||
|
constructor(viewport: Viewport, sceneManager: SceneManager, game: GameLoop, options: Record<string, any>){
|
||||||
|
this.sceneOptions = SceneOptions.parse(options);
|
||||||
|
|
||||||
this.worldSize = new Vec2(500, 500);
|
this.worldSize = new Vec2(500, 500);
|
||||||
this.viewport = viewport;
|
this.viewport = viewport;
|
||||||
this.viewport.setBounds(0, 0, 2560, 1280);
|
this.viewport.setBounds(0, 0, 2560, 1280);
|
||||||
|
@ -90,7 +96,7 @@ export default class Scene implements Updateable, Renderable {
|
||||||
this.uiLayers = new Map();
|
this.uiLayers = new Map();
|
||||||
this.parallaxLayers = new Map();
|
this.parallaxLayers = new Map();
|
||||||
|
|
||||||
this.physicsManager = new BasicPhysicsManager(this.game.gameOptions.physics);
|
this.physicsManager = new BasicPhysicsManager(this.sceneOptions.physics);
|
||||||
this.navManager = new NavigationManager();
|
this.navManager = new NavigationManager();
|
||||||
this.aiManager = new AIManager();
|
this.aiManager = new AIManager();
|
||||||
|
|
||||||
|
@ -325,4 +331,40 @@ export default class Scene implements Updateable, Renderable {
|
||||||
generateId(): number {
|
generateId(): number {
|
||||||
return this.sceneManager.generateId();
|
return this.sceneManager.generateId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTilemap(name: string): Tilemap {
|
||||||
|
for(let tilemap of this .tilemaps){
|
||||||
|
if(tilemap.name === name){
|
||||||
|
return tilemap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SceneOptions {
|
||||||
|
physics: {
|
||||||
|
numPhysicsLayers: number,
|
||||||
|
physicsLayerNames: Array<string>,
|
||||||
|
physicsLayerCollisions: Array<Array<number>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
static parse(options: Record<string, any>): SceneOptions{
|
||||||
|
let sOpt = new SceneOptions();
|
||||||
|
|
||||||
|
sOpt.physics = {
|
||||||
|
numPhysicsLayers: 10,
|
||||||
|
physicsLayerNames: null,
|
||||||
|
physicsLayerCollisions: ArrayUtils.ones2d(10, 10)
|
||||||
|
};
|
||||||
|
|
||||||
|
if(options.physics){
|
||||||
|
if(options.physics.numPhysicsLayers) sOpt.physics.numPhysicsLayers = options.physics.numPhysicsLayers;
|
||||||
|
if(options.physics.physicsLayerNames) sOpt.physics.physicsLayerNames = options.physics.physicsLayerNames;
|
||||||
|
if(options.physics.physicsLayerCollisions) sOpt.physics.physicsLayerCollisions = options.physics.physicsLayerCollisions;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sOpt;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -22,9 +22,8 @@ export default class SceneManager {
|
||||||
* Add a scene as the main scene
|
* Add a scene as the main scene
|
||||||
* @param constr The constructor of the scene to add
|
* @param constr The constructor of the scene to add
|
||||||
*/
|
*/
|
||||||
public addScene<T extends Scene>(constr: new (...args: any) => T): void {
|
public addScene<T extends Scene>(constr: new (...args: any) => T, options: Record<string, any>): void {
|
||||||
console.log("Adding Scene");
|
let scene = new constr(this.viewport, this, this.game, options);
|
||||||
let scene = new constr(this.viewport, this, this.game);
|
|
||||||
this.currentScene = scene;
|
this.currentScene = scene;
|
||||||
|
|
||||||
// Enqueue all scene asset loads
|
// Enqueue all scene asset loads
|
||||||
|
@ -43,7 +42,7 @@ export default class SceneManager {
|
||||||
* Change from the current scene to this new scene
|
* Change from the current scene to this new scene
|
||||||
* @param constr The constructor of the scene to change to
|
* @param constr The constructor of the scene to change to
|
||||||
*/
|
*/
|
||||||
public changeScene<T extends Scene>(constr: new (...args: any) => T): void {
|
public changeScene<T extends Scene>(constr: new (...args: any) => T, options: Record<string, any>): void {
|
||||||
// unload current scene
|
// unload current scene
|
||||||
this.currentScene.unloadScene();
|
this.currentScene.unloadScene();
|
||||||
|
|
||||||
|
@ -51,7 +50,7 @@ export default class SceneManager {
|
||||||
|
|
||||||
this.viewport.setCenter(0, 0);
|
this.viewport.setCenter(0, 0);
|
||||||
|
|
||||||
this.addScene(constr);
|
this.addScene(constr, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public generateId(): number {
|
public generateId(): number {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import ParallaxLayer from "../../Scene/Layers/ParallaxLayer";
|
||||||
import Scene from "../../Scene/Scene";
|
import Scene from "../../Scene/Scene";
|
||||||
import PlayerController from "../Player/PlayerController";
|
import PlayerController from "../Player/PlayerController";
|
||||||
import GoombaController from "../Enemies/GoombaController";
|
import GoombaController from "../Enemies/GoombaController";
|
||||||
|
import OrthogonalTilemap from "../../Nodes/Tilemaps/OrthogonalTilemap";
|
||||||
|
|
||||||
export enum MarioEvents {
|
export enum MarioEvents {
|
||||||
PLAYER_HIT_COIN = "PlayerHitCoin",
|
PLAYER_HIT_COIN = "PlayerHitCoin",
|
||||||
|
@ -27,7 +28,11 @@ export default class Level1 extends Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
startScene(): void {
|
startScene(): void {
|
||||||
this.add.tilemap("level1", new Vec2(2, 2));
|
let tilemap = this.add.tilemap("level1", new Vec2(2, 2))[0].getItems()[0];
|
||||||
|
console.log(tilemap);
|
||||||
|
console.log((tilemap as OrthogonalTilemap).getTileAtRowCol(new Vec2(8, 17)));
|
||||||
|
(tilemap as OrthogonalTilemap).setTileAtRowCol(new Vec2(8, 17), 1);
|
||||||
|
console.log((tilemap as OrthogonalTilemap).getTileAtRowCol(new Vec2(8, 17)));
|
||||||
this.viewport.setBounds(0, 0, 150*64, 20*64);
|
this.viewport.setBounds(0, 0, 150*64, 20*64);
|
||||||
|
|
||||||
// Give parallax to the parallax layers
|
// Give parallax to the parallax layers
|
||||||
|
@ -37,33 +42,34 @@ export default class Level1 extends Scene {
|
||||||
// Add the player (a rect for now)
|
// Add the player (a rect for now)
|
||||||
this.player = this.add.graphic(GraphicType.RECT, "Main", {position: new Vec2(192, 1152), size: new Vec2(64, 64)});
|
this.player = this.add.graphic(GraphicType.RECT, "Main", {position: new Vec2(192, 1152), size: new Vec2(64, 64)});
|
||||||
this.player.addPhysics();
|
this.player.addPhysics();
|
||||||
this.player.addAI(PlayerController, {playerType: "platformer"});
|
this.player.addAI(PlayerController, {playerType: "platformer", tilemap: "Main"});
|
||||||
|
|
||||||
// Add triggers on colliding with coins or coinBlocks
|
// Add triggers on colliding with coins or coinBlocks
|
||||||
this.player.addTrigger("coin", MarioEvents.PLAYER_HIT_COIN);
|
this.player.addTrigger("coin", MarioEvents.PLAYER_HIT_COIN);
|
||||||
this.player.addTrigger("coinBlock", MarioEvents.PLAYER_HIT_COIN_BLOCK);
|
this.player.addTrigger("coinBlock", MarioEvents.PLAYER_HIT_COIN_BLOCK);
|
||||||
|
this.player.setPhysicsLayer("player");
|
||||||
|
|
||||||
this.receiver.subscribe([MarioEvents.PLAYER_HIT_COIN, MarioEvents.PLAYER_HIT_COIN_BLOCK]);
|
this.receiver.subscribe([MarioEvents.PLAYER_HIT_COIN, MarioEvents.PLAYER_HIT_COIN_BLOCK]);
|
||||||
|
|
||||||
this.viewport.follow(this.player);
|
this.viewport.follow(this.player);
|
||||||
|
|
||||||
// Add enemies
|
// Add enemies
|
||||||
// Goombas
|
|
||||||
for(let pos of [{x: 21, y: 18}, {x: 30, y: 18}, {x: 37, y: 18}, {x: 41, y: 18}, {x: 105, y: 8}, {x: 107, y: 8}, {x: 125, y: 18}]){
|
for(let pos of [{x: 21, y: 18}, {x: 30, y: 18}, {x: 37, y: 18}, {x: 41, y: 18}, {x: 105, y: 8}, {x: 107, y: 8}, {x: 125, y: 18}]){
|
||||||
let goomba = this.add.sprite("goomba", "Main");
|
let goomba = this.add.sprite("goomba", "Main");
|
||||||
goomba.position.set(pos.x*64, pos.y*64);
|
goomba.position.set(pos.x*64, pos.y*64);
|
||||||
goomba.scale.set(2, 2);
|
goomba.scale.set(2, 2);
|
||||||
goomba.addPhysics();
|
goomba.addPhysics();
|
||||||
goomba.addAI(GoombaController, {jumpy: false});
|
goomba.addAI(GoombaController, {jumpy: false});
|
||||||
|
goomba.setPhysicsLayer("enemy");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
for(let pos of [{x: 67, y: 18}, {x: 86, y: 21}, {x: 128, y: 18}]){
|
for(let pos of [{x: 67, y: 18}, {x: 86, y: 21}, {x: 128, y: 18}]){
|
||||||
let koopa = this.add.sprite("koopa", "Main");
|
let koopa = this.add.sprite("koopa", "Main");
|
||||||
koopa.position.set(pos.x*64, pos.y*64);
|
koopa.position.set(pos.x*64, pos.y*64);
|
||||||
koopa.scale.set(2, 2);
|
koopa.scale.set(2, 2);
|
||||||
koopa.addPhysics();
|
koopa.addPhysics();
|
||||||
koopa.addAI(GoombaController, {jumpy: true});
|
koopa.addAI(GoombaController, {jumpy: true});
|
||||||
|
koopa.setPhysicsLayer("enemy");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add UI
|
// Add UI
|
||||||
|
|
|
@ -2,6 +2,7 @@ import StateMachineAI from "../../AI/StateMachineAI";
|
||||||
import Vec2 from "../../DataTypes/Vec2";
|
import Vec2 from "../../DataTypes/Vec2";
|
||||||
import Debug from "../../Debug/Debug";
|
import Debug from "../../Debug/Debug";
|
||||||
import GameNode from "../../Nodes/GameNode";
|
import GameNode from "../../Nodes/GameNode";
|
||||||
|
import OrthogonalTilemap from "../../Nodes/Tilemaps/OrthogonalTilemap";
|
||||||
import IdleTopDown from "./PlayerStates/IdleTopDown";
|
import IdleTopDown from "./PlayerStates/IdleTopDown";
|
||||||
import MoveTopDown from "./PlayerStates/MoveTopDown";
|
import MoveTopDown from "./PlayerStates/MoveTopDown";
|
||||||
import Idle from "./PlayerStates/Platformer/Idle";
|
import Idle from "./PlayerStates/Platformer/Idle";
|
||||||
|
@ -29,6 +30,7 @@ export default class PlayerController extends StateMachineAI {
|
||||||
speed: number = 400;
|
speed: number = 400;
|
||||||
MIN_SPEED: number = 400;
|
MIN_SPEED: number = 400;
|
||||||
MAX_SPEED: number = 1000;
|
MAX_SPEED: number = 1000;
|
||||||
|
tilemap: OrthogonalTilemap;
|
||||||
|
|
||||||
initializeAI(owner: GameNode, options: Record<string, any>){
|
initializeAI(owner: GameNode, options: Record<string, any>){
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
|
@ -38,6 +40,8 @@ export default class PlayerController extends StateMachineAI {
|
||||||
} else {
|
} else {
|
||||||
this.initializePlatformer();
|
this.initializePlatformer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.tilemap = this.owner.getScene().getTilemap(options.tilemap) as OrthogonalTilemap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -14,6 +14,23 @@ export default class Jump extends PlayerState {
|
||||||
update(deltaT: number): void {
|
update(deltaT: number): void {
|
||||||
super.update(deltaT);
|
super.update(deltaT);
|
||||||
|
|
||||||
|
if(this.owner.collidedWithTilemap && this.owner.onCeiling){
|
||||||
|
// We collided with a tilemap above us. First, get the tile right above us
|
||||||
|
let pos = this.owner.position.clone();
|
||||||
|
|
||||||
|
// Go up plus some extra
|
||||||
|
pos.y -= (this.owner.collisionShape.halfSize.y + 10);
|
||||||
|
pos = this.parent.tilemap.getColRowAt(pos);
|
||||||
|
let tile = this.parent.tilemap.getTileAtRowCol(pos);
|
||||||
|
|
||||||
|
console.log("Hit tile: " + tile);
|
||||||
|
|
||||||
|
// If coin block, change to empty coin block
|
||||||
|
if(tile === 4){
|
||||||
|
this.parent.tilemap.setTileAtRowCol(pos, 12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(this.owner.onGround){
|
if(this.owner.onGround){
|
||||||
this.finished(PlayerStates.PREVIOUS);
|
this.finished(PlayerStates.PREVIOUS);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import StateMachine from "../../../../DataTypes/State/StateMachine";
|
||||||
import Vec2 from "../../../../DataTypes/Vec2";
|
import Vec2 from "../../../../DataTypes/Vec2";
|
||||||
import InputReceiver from "../../../../Input/InputReceiver";
|
import InputReceiver from "../../../../Input/InputReceiver";
|
||||||
import GameNode from "../../../../Nodes/GameNode";
|
import GameNode from "../../../../Nodes/GameNode";
|
||||||
import PlayerController from "./PlayerController";
|
import PlayerController from "../../PlayerController";
|
||||||
|
|
||||||
|
|
||||||
export default abstract class PlayerState extends State {
|
export default abstract class PlayerState extends State {
|
||||||
|
|
20
src/main.ts
20
src/main.ts
|
@ -6,15 +6,27 @@ function main(){
|
||||||
// Create the game object
|
// Create the game object
|
||||||
let options = {
|
let options = {
|
||||||
viewportSize: {x: 800, y: 600},
|
viewportSize: {x: 800, y: 600},
|
||||||
physics: {
|
|
||||||
physicsLayerNames: ["ground", "player", "enemy", "coin"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let game = new GameLoop(options);
|
let game = new GameLoop(options);
|
||||||
game.start();
|
game.start();
|
||||||
|
|
||||||
|
let sceneOptions = {
|
||||||
|
physics: {
|
||||||
|
physicsLayerNames: ["ground", "player", "enemy", "coin"],
|
||||||
|
numPhyiscsLayers: 4,
|
||||||
|
physicsLayerCollisions:
|
||||||
|
[
|
||||||
|
[0, 1, 1, 1],
|
||||||
|
[1, 0, 0, 1],
|
||||||
|
[1, 0, 0, 1],
|
||||||
|
[1, 1, 1, 0]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let sm = game.getSceneManager();
|
let sm = game.getSceneManager();
|
||||||
sm.addScene(Level1);
|
sm.addScene(Level1, sceneOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
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