added demo level and started work on physics layer support
This commit is contained in:
parent
ff5a2896fe
commit
9dc8cd29d1
|
@ -114,7 +114,7 @@ export interface Physical {
|
||||||
*/
|
*/
|
||||||
export interface AI extends Updateable {
|
export interface AI extends Updateable {
|
||||||
/** Initializes the AI with the actor and any additional config */
|
/** Initializes the AI with the actor and any additional config */
|
||||||
initializeAI: (owner: GameNode, config: Record<string, any>) => void;
|
initializeAI: (owner: GameNode, options: Record<string, any>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Actor {
|
export interface Actor {
|
||||||
|
|
|
@ -8,8 +8,11 @@ import Viewport from "../SceneGraph/Viewport";
|
||||||
import SceneManager from "../Scene/SceneManager";
|
import SceneManager from "../Scene/SceneManager";
|
||||||
import AudioManager from "../Sound/AudioManager";
|
import AudioManager from "../Sound/AudioManager";
|
||||||
import Stats from "../Debug/Stats";
|
import Stats from "../Debug/Stats";
|
||||||
|
import ArrayUtils from "../Utils/ArrayUtils";
|
||||||
|
|
||||||
export default class GameLoop {
|
export default class GameLoop {
|
||||||
|
gameOptions: GameOptions;
|
||||||
|
|
||||||
/** The max allowed update fps.*/
|
/** The max allowed update fps.*/
|
||||||
private maxUpdateFPS: number;
|
private maxUpdateFPS: number;
|
||||||
|
|
||||||
|
@ -68,9 +71,9 @@ export default class GameLoop {
|
||||||
private sceneManager: SceneManager;
|
private sceneManager: SceneManager;
|
||||||
private audioManager: AudioManager;
|
private audioManager: AudioManager;
|
||||||
|
|
||||||
constructor(config?: object){
|
constructor(options?: Record<string, any>){
|
||||||
// Typecast the config object to a GameConfig object
|
// Typecast the config object to a GameConfig object
|
||||||
let gameConfig = config ? <GameConfig>config : new GameConfig();
|
this.gameOptions = GameOptions.parse(options);
|
||||||
|
|
||||||
this.maxUpdateFPS = 60;
|
this.maxUpdateFPS = 60;
|
||||||
this.simulationTimestep = Math.floor(1000/this.maxUpdateFPS);
|
this.simulationTimestep = Math.floor(1000/this.maxUpdateFPS);
|
||||||
|
@ -95,8 +98,8 @@ export default class GameLoop {
|
||||||
this.GAME_CANVAS.style.setProperty("background-color", "whitesmoke");
|
this.GAME_CANVAS.style.setProperty("background-color", "whitesmoke");
|
||||||
|
|
||||||
// Give the canvas a size and get the rendering context
|
// Give the canvas a size and get the rendering context
|
||||||
this.WIDTH = gameConfig.canvasSize ? gameConfig.canvasSize.x : 800;
|
this.WIDTH = this.gameOptions.viewportSize.x;
|
||||||
this.HEIGHT = gameConfig.canvasSize ? gameConfig.canvasSize.y : 500;
|
this.HEIGHT = this.gameOptions.viewportSize.y;
|
||||||
this.ctx = this.initializeCanvas(this.GAME_CANVAS, this.WIDTH, this.HEIGHT);
|
this.ctx = this.initializeCanvas(this.GAME_CANVAS, this.WIDTH, this.HEIGHT);
|
||||||
|
|
||||||
// Size the viewport to the game canvas
|
// Size the viewport to the game canvas
|
||||||
|
@ -281,6 +284,31 @@ export default class GameLoop {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GameConfig {
|
class GameOptions {
|
||||||
canvasSize: {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 {
|
||||||
|
let gOpt = new GameOptions();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -11,6 +11,8 @@ export default abstract class CanvasNode extends GameNode implements Region {
|
||||||
private _scale: Vec2;
|
private _scale: Vec2;
|
||||||
private _boundary: AABB;
|
private _boundary: AABB;
|
||||||
|
|
||||||
|
visible = true;
|
||||||
|
|
||||||
constructor(){
|
constructor(){
|
||||||
super();
|
super();
|
||||||
this.position.setOnChange(this.positionChanged);
|
this.position.setOnChange(this.positionChanged);
|
||||||
|
|
|
@ -10,6 +10,7 @@ import MathUtils from "../Utils/MathUtils";
|
||||||
import OrthogonalTilemap from "../Nodes/Tilemaps/OrthogonalTilemap";
|
import OrthogonalTilemap from "../Nodes/Tilemaps/OrthogonalTilemap";
|
||||||
import Debug from "../Debug/Debug";
|
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 {
|
||||||
|
|
||||||
|
@ -25,12 +26,37 @@ 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;
|
||||||
|
|
||||||
constructor(){
|
protected layerMap: Map<number>;
|
||||||
|
protected layerNames: Array<string>;
|
||||||
|
|
||||||
|
constructor(physicsOptions: Record<string, any>){
|
||||||
super();
|
super();
|
||||||
this.staticNodes = new Array();
|
this.staticNodes = new Array();
|
||||||
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;
|
||||||
|
if(physicsOptions.physicsLayerNames !== null){
|
||||||
|
for(let layer of physicsOptions.physicsLayerNames){
|
||||||
|
if(i >= physicsOptions.numPhysicsLayers){
|
||||||
|
// If we have too many string layers, don't add extras
|
||||||
|
}
|
||||||
|
|
||||||
|
this.layerNames[i] = layer;
|
||||||
|
this.layerMap.add(layer, i);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i; i < physicsOptions.numPhysicsLayers; i++){
|
||||||
|
this.layerNames[i] = "" + i;
|
||||||
|
this.layerMap.add("" + i, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(this.layerNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -271,14 +297,15 @@ export default class BasicPhysicsManager extends PhysicsManager {
|
||||||
for(let pair of potentialCollidingPairs){
|
for(let pair of potentialCollidingPairs){
|
||||||
let node1 = pair[0];
|
let node1 = pair[0];
|
||||||
let node2 = pair[1];
|
let node2 = pair[1];
|
||||||
|
|
||||||
|
// Make sure both nodes are active
|
||||||
|
if(!node1.active || !node2.active){
|
||||||
|
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(collidingX && collidingY){
|
|
||||||
console.log("overlapping")
|
|
||||||
}
|
|
||||||
|
|
||||||
if(node1.isPlayer){
|
if(node1.isPlayer){
|
||||||
if(firstContact.x !== Infinity || firstContact.y !== Infinity)
|
if(firstContact.x !== Infinity || firstContact.y !== Infinity)
|
||||||
Debug.log("playercol", "First Contact: " + firstContact.toFixed(4))
|
Debug.log("playercol", "First Contact: " + firstContact.toFixed(4))
|
||||||
|
|
|
@ -21,6 +21,10 @@ export default class TilemapFactory {
|
||||||
this.resourceManager = ResourceManager.getInstance();
|
this.resourceManager = ResourceManager.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO - This is specifically catered to Tiled tilemaps right now. In the future,
|
||||||
|
// it would be good to have a "parseTilemap" function that would convert the tilemap
|
||||||
|
// data into a standard format. This could allow for support from other programs
|
||||||
|
// or the development of an internal level builder tool
|
||||||
/**
|
/**
|
||||||
* Adds a tilemap to the scene
|
* Adds a tilemap to the scene
|
||||||
* @param key The key of the loaded tilemap to load
|
* @param key The key of the loaded tilemap to load
|
||||||
|
@ -63,8 +67,24 @@ export default class TilemapFactory {
|
||||||
|
|
||||||
// Loop over the layers of the tilemap and create tiledlayers or object layers
|
// Loop over the layers of the tilemap and create tiledlayers or object layers
|
||||||
for(let layer of tilemapData.layers){
|
for(let layer of tilemapData.layers){
|
||||||
|
|
||||||
|
let sceneLayer;
|
||||||
|
let isParallaxLayer = false;
|
||||||
|
|
||||||
let sceneLayer = this.scene.addLayer(layer.name);
|
if(layer.properties){
|
||||||
|
for(let prop of layer.properties){
|
||||||
|
if(prop.name === "Parallax"){
|
||||||
|
isParallaxLayer = prop.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isParallaxLayer){
|
||||||
|
console.log("Adding parallax layer: " + layer.name)
|
||||||
|
sceneLayer = this.scene.addParallaxLayer(layer.name, new Vec2(1, 1));
|
||||||
|
} else {
|
||||||
|
sceneLayer = this.scene.addLayer(layer.name);
|
||||||
|
}
|
||||||
|
|
||||||
if(layer.type === "tilelayer"){
|
if(layer.type === "tilelayer"){
|
||||||
// Create a new tilemap object for the layer
|
// Create a new tilemap object for the layer
|
||||||
|
|
|
@ -90,7 +90,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.physicsManager = new BasicPhysicsManager(this.game.gameOptions.physics);
|
||||||
this.navManager = new NavigationManager();
|
this.navManager = new NavigationManager();
|
||||||
this.aiManager = new AIManager();
|
this.aiManager = new AIManager();
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@ export default class Scene implements Updateable, Renderable {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Render visible set
|
// Render visible set
|
||||||
visibleSet.forEach(node => node.render(ctx));
|
visibleSet.forEach(node => node.visible ? node.render(ctx) : "");
|
||||||
|
|
||||||
// Debug render the physicsManager
|
// Debug render the physicsManager
|
||||||
this.physicsManager.debug_render(ctx);
|
this.physicsManager.debug_render(ctx);
|
||||||
|
|
34
src/Utils/ArrayUtils.ts
Normal file
34
src/Utils/ArrayUtils.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
export default class ArrayUtils {
|
||||||
|
/**
|
||||||
|
* Returns a 2d array of dim1 x dim2 filled with 1s
|
||||||
|
* @param dim1
|
||||||
|
* @param dim2
|
||||||
|
*/
|
||||||
|
static ones2d(dim1: number, dim2: number): number[][] {
|
||||||
|
let arr = new Array<Array<number>>(dim1);
|
||||||
|
|
||||||
|
for(let i = 0; i < arr.length; i++){
|
||||||
|
arr[i] = new Array<number>(dim2);
|
||||||
|
|
||||||
|
for(let j = 0; j < arr[i].length; j++){
|
||||||
|
arr[i][j] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool2d(dim1: number, dim2: number, flag: boolean): boolean[][] {
|
||||||
|
let arr = new Array<Array<boolean>>(dim1);
|
||||||
|
|
||||||
|
for(let i = 0; i < arr.length; i++){
|
||||||
|
arr[i] = new Array<boolean>(dim2);
|
||||||
|
|
||||||
|
for(let j = 0; j < arr[i].length; j++){
|
||||||
|
arr[i][j] = flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
import StateMachine from "../../DataTypes/State/StateMachine";
|
|
||||||
import { CustomGameEventType } from "../CustomGameEventType";
|
import { CustomGameEventType } from "../CustomGameEventType";
|
||||||
import Idle from "../Enemies/Idle";
|
import Idle from "../Enemies/Idle";
|
||||||
import Jump from "../Enemies/Jump";
|
import Jump from "../Enemies/Jump";
|
||||||
import Walk from "../Enemies/Walk";
|
import Walk from "../Enemies/Walk";
|
||||||
import Afraid from "../Enemies/Afraid";
|
import Afraid from "../Enemies/Afraid";
|
||||||
import Debug from "../../Debug/Debug";
|
|
||||||
import GameNode from "../../Nodes/GameNode";
|
import GameNode from "../../Nodes/GameNode";
|
||||||
import Vec2 from "../../DataTypes/Vec2";
|
import Vec2 from "../../DataTypes/Vec2";
|
||||||
|
import StateMachineAI from "../../AI/StateMachineAI";
|
||||||
|
import GoombaState from "./GoombaState";
|
||||||
|
|
||||||
export enum GoombaStates {
|
export enum GoombaStates {
|
||||||
IDLE = "idle",
|
IDLE = "idle",
|
||||||
|
@ -16,18 +16,16 @@ export enum GoombaStates {
|
||||||
AFRAID = "afraid"
|
AFRAID = "afraid"
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class GoombaController extends StateMachine {
|
export default class GoombaController extends StateMachineAI {
|
||||||
owner: GameNode;
|
owner: GameNode;
|
||||||
jumpy: boolean;
|
jumpy: boolean;
|
||||||
direction: Vec2 = Vec2.ZERO;
|
direction: Vec2 = Vec2.ZERO;
|
||||||
velocity: Vec2 = Vec2.ZERO;
|
velocity: Vec2 = Vec2.ZERO;
|
||||||
speed: number = 200;
|
speed: number = 200;
|
||||||
|
|
||||||
constructor(owner: GameNode, jumpy: boolean){
|
initializeAI(owner: GameNode, options: Record<string, any>){
|
||||||
super();
|
|
||||||
|
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.jumpy = jumpy;
|
this.jumpy = options.jumpy ? options.jumpy : false;
|
||||||
|
|
||||||
this.receiver.subscribe(CustomGameEventType.PLAYER_MOVE);
|
this.receiver.subscribe(CustomGameEventType.PLAYER_MOVE);
|
||||||
this.receiver.subscribe("playerHitCoinBlock");
|
this.receiver.subscribe("playerHitCoinBlock");
|
||||||
|
@ -41,8 +39,8 @@ export default class GoombaController extends StateMachine {
|
||||||
this.addState(GoombaStates.WALK, walk);
|
this.addState(GoombaStates.WALK, walk);
|
||||||
let jump = new Jump(this, owner);
|
let jump = new Jump(this, owner);
|
||||||
this.addState(GoombaStates.JUMP, jump);
|
this.addState(GoombaStates.JUMP, jump);
|
||||||
let afraid = new Afraid(this, owner);
|
|
||||||
this.addState(GoombaStates.AFRAID, afraid);
|
this.initialize(GoombaStates.IDLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
changeState(stateName: string): void {
|
changeState(stateName: string): void {
|
||||||
|
|
|
@ -15,13 +15,7 @@ export default abstract class GoombaState extends State {
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInput(event: GameEvent): void {
|
handleInput(event: GameEvent): void {}
|
||||||
if(event.type === "playerHitCoinBlock") {
|
|
||||||
if(event.data.get("collision").firstContact.y < 1 && event.data.get("node").collisionShape.center.y > event.data.get("other").collisionShape.center.y){
|
|
||||||
this.finished(GoombaStates.AFRAID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
update(deltaT: number): void {
|
update(deltaT: number): void {
|
||||||
// Do gravity
|
// Do gravity
|
||||||
|
|
|
@ -1,17 +1,11 @@
|
||||||
import GameEvent from "../../Events/GameEvent";
|
import GameEvent from "../../Events/GameEvent";
|
||||||
import { CustomGameEventType } from "../CustomGameEventType";
|
import { GoombaStates } from "./GoombaController";
|
||||||
import GoombaController, { GoombaStates } from "./GoombaController";
|
|
||||||
import GoombaState from "./GoombaState";
|
import GoombaState from "./GoombaState";
|
||||||
|
|
||||||
export default class OnGround extends GoombaState {
|
export default class OnGround extends GoombaState {
|
||||||
onEnter(): void {}
|
onEnter(): void {}
|
||||||
|
|
||||||
handleInput(event: GameEvent): void {
|
handleInput(event: GameEvent): void {
|
||||||
if(event.type === CustomGameEventType.PLAYER_JUMP && (<GoombaController>this.parent).jumpy){
|
|
||||||
this.finished(GoombaStates.JUMP);
|
|
||||||
this.parent.velocity.y = -2000;
|
|
||||||
}
|
|
||||||
|
|
||||||
super.handleInput(event);
|
super.handleInput(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
import Vec2 from "../../DataTypes/Vec2";
|
import Vec2 from "../../DataTypes/Vec2";
|
||||||
|
import { GoombaStates } from "./GoombaController";
|
||||||
import OnGround from "./OnGround";
|
import OnGround from "./OnGround";
|
||||||
|
|
||||||
export default class Walk extends OnGround {
|
export default class Walk extends OnGround {
|
||||||
|
time: number;
|
||||||
|
|
||||||
onEnter(): void {
|
onEnter(): void {
|
||||||
if(this.parent.direction.isZero()){
|
if(this.parent.direction.isZero()){
|
||||||
this.parent.direction = new Vec2(-1, 0);
|
this.parent.direction = new Vec2(-1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.time = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
update(deltaT: number): void {
|
update(deltaT: number): void {
|
||||||
|
@ -16,6 +21,12 @@ export default class Walk extends OnGround {
|
||||||
this.parent.direction.x *= -1;
|
this.parent.direction.x *= -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(this.parent.jumpy && (Date.now() - this.time > 500)){
|
||||||
|
console.log("Jump");
|
||||||
|
this.finished(GoombaStates.JUMP);
|
||||||
|
this.parent.velocity.y = -2000;
|
||||||
|
}
|
||||||
|
|
||||||
this.parent.velocity.x = this.parent.direction.x * this.parent.speed;
|
this.parent.velocity.x = this.parent.direction.x * this.parent.speed;
|
||||||
|
|
||||||
this.owner.move(this.parent.velocity.scaled(deltaT));
|
this.owner.move(this.parent.velocity.scaled(deltaT));
|
||||||
|
|
110
src/_DemoClasses/Mario/Level1.ts
Normal file
110
src/_DemoClasses/Mario/Level1.ts
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
import Vec2 from "../../DataTypes/Vec2";
|
||||||
|
import GameNode from "../../Nodes/GameNode";
|
||||||
|
import { GraphicType } from "../../Nodes/Graphics/GraphicTypes";
|
||||||
|
import Label from "../../Nodes/UIElements/Label";
|
||||||
|
import { UIElementType } from "../../Nodes/UIElements/UIElementTypes";
|
||||||
|
import ParallaxLayer from "../../Scene/Layers/ParallaxLayer";
|
||||||
|
import Scene from "../../Scene/Scene";
|
||||||
|
import PlayerController from "../Player/PlayerController";
|
||||||
|
import GoombaController from "../Enemies/GoombaController";
|
||||||
|
|
||||||
|
export enum MarioEvents {
|
||||||
|
PLAYER_HIT_COIN = "PlayerHitCoin",
|
||||||
|
PLAYER_HIT_COIN_BLOCK = "PlayerHitCoinBlock"
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Level1 extends Scene {
|
||||||
|
player: GameNode;
|
||||||
|
coinCount: number = 0;
|
||||||
|
coinCountLabel: Label;
|
||||||
|
livesCount: number = 3;
|
||||||
|
livesCountLabel: Label;
|
||||||
|
|
||||||
|
loadScene(): void {
|
||||||
|
this.load.tilemap("level1", "/assets/tilemaps/level1.json");
|
||||||
|
this.load.image("goomba", "assets/sprites/Goomba.png");
|
||||||
|
this.load.image("koopa", "assets/sprites/Koopa.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
startScene(): void {
|
||||||
|
this.add.tilemap("level1", new Vec2(2, 2));
|
||||||
|
this.viewport.setBounds(0, 0, 150*64, 20*64);
|
||||||
|
|
||||||
|
// Give parallax to the parallax layers
|
||||||
|
(this.getLayer("Clouds") as ParallaxLayer).parallax.set(0.5, 1);
|
||||||
|
(this.getLayer("Hills") as ParallaxLayer).parallax.set(0.8, 1);
|
||||||
|
|
||||||
|
// 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.addPhysics();
|
||||||
|
this.player.addAI(PlayerController, {playerType: "platformer"});
|
||||||
|
|
||||||
|
// Add triggers on colliding with coins or coinBlocks
|
||||||
|
this.player.addTrigger("coin", MarioEvents.PLAYER_HIT_COIN);
|
||||||
|
this.player.addTrigger("coinBlock", MarioEvents.PLAYER_HIT_COIN_BLOCK);
|
||||||
|
|
||||||
|
this.receiver.subscribe([MarioEvents.PLAYER_HIT_COIN, MarioEvents.PLAYER_HIT_COIN_BLOCK]);
|
||||||
|
|
||||||
|
this.viewport.follow(this.player);
|
||||||
|
|
||||||
|
// 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}]){
|
||||||
|
let goomba = this.add.sprite("goomba", "Main");
|
||||||
|
goomba.position.set(pos.x*64, pos.y*64);
|
||||||
|
goomba.scale.set(2, 2);
|
||||||
|
goomba.addPhysics();
|
||||||
|
goomba.addAI(GoombaController, {jumpy: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for(let pos of [{x: 67, y: 18}, {x: 86, y: 21}, {x: 128, y: 18}]){
|
||||||
|
let koopa = this.add.sprite("koopa", "Main");
|
||||||
|
koopa.position.set(pos.x*64, pos.y*64);
|
||||||
|
koopa.scale.set(2, 2);
|
||||||
|
koopa.addPhysics();
|
||||||
|
koopa.addAI(GoombaController, {jumpy: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add UI
|
||||||
|
this.addUILayer("UI");
|
||||||
|
|
||||||
|
this.coinCountLabel = this.add.uiElement(UIElementType.LABEL, "UI", {position: new Vec2(80, 30), text: "Coins: 0"});
|
||||||
|
this.livesCountLabel = this.add.uiElement(UIElementType.LABEL, "UI", {position: new Vec2(600, 30), text: "Lives: 3"});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateScene(deltaT: number): void {
|
||||||
|
while(this.receiver.hasNextEvent()){
|
||||||
|
let event = this.receiver.getNextEvent();
|
||||||
|
|
||||||
|
if(event.type === MarioEvents.PLAYER_HIT_COIN){
|
||||||
|
let coin;
|
||||||
|
if(event.data.get("node") === this.player){
|
||||||
|
// Other is coin, disable
|
||||||
|
coin = event.data.get("other");
|
||||||
|
} else {
|
||||||
|
// Node is coin, disable
|
||||||
|
coin = event.data.get("node");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from physics and scene
|
||||||
|
coin.active = false;
|
||||||
|
coin.visible = false;
|
||||||
|
this.coinCount += 1;
|
||||||
|
|
||||||
|
this.coinCountLabel.setText("Coins: " + this.coinCount);
|
||||||
|
|
||||||
|
} else if(event.type === MarioEvents.PLAYER_HIT_COIN_BLOCK){
|
||||||
|
console.log("Hit Coin Block")
|
||||||
|
console.log(event.data.get("node") === this.player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If player falls into a pit, kill them off and reset their position
|
||||||
|
if(this.player.position.y > 21*64){
|
||||||
|
this.player.position.set(192, 1152);
|
||||||
|
this.livesCount -= 1
|
||||||
|
this.livesCountLabel.setText("Lives: " + this.livesCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,11 +30,11 @@ export default class PlayerController extends StateMachineAI {
|
||||||
MIN_SPEED: number = 400;
|
MIN_SPEED: number = 400;
|
||||||
MAX_SPEED: number = 1000;
|
MAX_SPEED: number = 1000;
|
||||||
|
|
||||||
initializeAI(owner: GameNode, config: Record<string, any>){
|
initializeAI(owner: GameNode, options: Record<string, any>){
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
|
|
||||||
if(config.playerType === PlayerType.TOPDOWN){
|
if(options.playerType === PlayerType.TOPDOWN){
|
||||||
this.initializeTopDown(config.speed);
|
this.initializeTopDown(options.speed);
|
||||||
} else {
|
} else {
|
||||||
this.initializePlatformer();
|
this.initializePlatformer();
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,9 @@ export default class PlayerController extends StateMachineAI {
|
||||||
let run = new Run(this, this.owner);
|
let run = new Run(this, this.owner);
|
||||||
this.addState(PlayerStates.RUN, run);
|
this.addState(PlayerStates.RUN, run);
|
||||||
let jump = new Jump(this, this.owner);
|
let jump = new Jump(this, this.owner);
|
||||||
this.addState(PlayerStates.JUMP, jump);
|
this.addState(PlayerStates.JUMP, jump);
|
||||||
|
|
||||||
|
this.initialize(PlayerStates.IDLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
changeState(stateName: string): void {
|
changeState(stateName: string): void {
|
||||||
|
|
15
src/main.ts
15
src/main.ts
|
@ -1,15 +1,20 @@
|
||||||
import GameLoop from "./Loop/GameLoop";
|
import GameLoop from "./Loop/GameLoop";
|
||||||
import {} from "./index";
|
import {} from "./index";
|
||||||
import BoidDemo from "./BoidDemo";
|
import Level1 from "./_DemoClasses/Mario/Level1";
|
||||||
import MarioClone from "./_DemoClasses/MarioClone/MarioClone";
|
|
||||||
import PathfindingScene from "./_DemoClasses/Pathfinding/PathfindingScene";
|
|
||||||
|
|
||||||
function main(){
|
function main(){
|
||||||
// Create the game object
|
// Create the game object
|
||||||
let game = new GameLoop({canvasSize: {x: 800, y: 600}});
|
let options = {
|
||||||
|
viewportSize: {x: 800, y: 600},
|
||||||
|
physics: {
|
||||||
|
physicsLayerNames: ["ground", "player", "enemy", "coin"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let game = new GameLoop(options);
|
||||||
game.start();
|
game.start();
|
||||||
let sm = game.getSceneManager();
|
let sm = game.getSceneManager();
|
||||||
sm.addScene(PathfindingScene);
|
sm.addScene(Level1);
|
||||||
}
|
}
|
||||||
|
|
||||||
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