added tilemaps and a resource loader
This commit is contained in:
parent
851fd050a7
commit
f0bb31f61e
33
src/DataTypes/Tilesets/TiledData.ts
Normal file
33
src/DataTypes/Tilesets/TiledData.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
export class TiledTilemapData {
|
||||
height: number;
|
||||
width: number;
|
||||
orientation: string;
|
||||
layers: TiledLayerData[];
|
||||
tilesets: TiledTilesetData[];
|
||||
}
|
||||
|
||||
export class TiledTilesetData {
|
||||
columns: number;
|
||||
tilewidth: number;
|
||||
tileheight: number;
|
||||
tilecount: number;
|
||||
firstgid: number;
|
||||
imageheight: number;
|
||||
imagewidth: number;
|
||||
margin: number;
|
||||
spacing: number;
|
||||
name: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
export class TiledLayerData {
|
||||
data: number[];
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
name: string;
|
||||
opacity: number;
|
||||
visible: boolean;
|
||||
}
|
||||
|
80
src/DataTypes/Tilesets/Tileset.ts
Normal file
80
src/DataTypes/Tilesets/Tileset.ts
Normal file
|
@ -0,0 +1,80 @@
|
|||
import Vec2 from "../Vec2";
|
||||
import { TiledTilesetData } from "./TiledData";
|
||||
|
||||
/**
|
||||
* The data representation of a Tileset for the game engine. This represents one image,
|
||||
* with a startIndex if required (as it is with Tiled using two images in one tilset).
|
||||
*/
|
||||
export default class Tileset {
|
||||
protected imageUrl: string;
|
||||
protected image: HTMLImageElement = null;
|
||||
protected imageSize: Vec2;
|
||||
protected startIndex: number;
|
||||
protected endIndex: number;
|
||||
protected tileSize: Vec2;
|
||||
protected numRows: number;
|
||||
protected numCols: number;
|
||||
|
||||
constructor(tilesetData: TiledTilesetData){
|
||||
this.initFromTiledData(tilesetData);
|
||||
}
|
||||
|
||||
initFromTiledData(tiledData: TiledTilesetData){
|
||||
this.numRows = tiledData.tilecount/tiledData.columns;
|
||||
this.numCols = tiledData.columns;
|
||||
this.startIndex = tiledData.firstgid;
|
||||
this.endIndex = this.startIndex + tiledData.tilecount - 1;
|
||||
this.tileSize = new Vec2(tiledData.tilewidth, tiledData.tilewidth);
|
||||
this.imageUrl = tiledData.image;
|
||||
this.imageSize = new Vec2(tiledData.imagewidth, tiledData.imageheight);
|
||||
}
|
||||
|
||||
getImageUrl(): string {
|
||||
return this.imageUrl
|
||||
}
|
||||
|
||||
getImage(): HTMLImageElement {
|
||||
return this.image;
|
||||
}
|
||||
|
||||
setImage(image: HTMLImageElement){
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
getStartIndex(): number {
|
||||
return this.startIndex;
|
||||
}
|
||||
|
||||
getTileSize(): Vec2 {
|
||||
return this.tileSize;
|
||||
}
|
||||
|
||||
getNumRows(): number {
|
||||
return this.numRows;
|
||||
}
|
||||
|
||||
getNumCols(): number {
|
||||
return this.numCols;
|
||||
}
|
||||
|
||||
isReady(): boolean {
|
||||
return this.image !== null;
|
||||
}
|
||||
|
||||
hasTile(tileIndex: number): boolean {
|
||||
return tileIndex >= this.startIndex && tileIndex <= this.endIndex;
|
||||
}
|
||||
|
||||
renderTile(ctx: CanvasRenderingContext2D, tileIndex: number, dataIndex: number, worldSize: Vec2, origin: Vec2): void {
|
||||
let index = tileIndex - this.startIndex;
|
||||
let row = Math.floor(index / this.numCols);
|
||||
let col = index % this.numCols;
|
||||
let width = this.tileSize.x;
|
||||
let height = this.tileSize.y;
|
||||
let left = col * width;
|
||||
let top = row * height;
|
||||
let x = (dataIndex % worldSize.x) * width * 4;
|
||||
let y = Math.floor(dataIndex / worldSize.x) * height * 4;
|
||||
ctx.drawImage(this.image, left, top, width, height, x - origin.x, y - origin.y, width * 4, height * 4);
|
||||
}
|
||||
}
|
37
src/GameState/Factories/TilemapFactory.ts
Normal file
37
src/GameState/Factories/TilemapFactory.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import Scene from "../Scene";
|
||||
import Viewport from "../../SceneGraph/Viewport";
|
||||
import Tilemap from "../../Nodes/Tilemap"
|
||||
import ResourceManager from "../../ResourceManager/ResourceManager";
|
||||
import { TiledTilemapData } from "../../DataTypes/Tilesets/TiledData";
|
||||
import StringUtils from "../../Utils/StringUtils";
|
||||
|
||||
export default class TilemapFactory {
|
||||
private scene: Scene;
|
||||
private viewport: Viewport;
|
||||
private resourceManager: ResourceManager;
|
||||
|
||||
constructor(scene: Scene, viewport: Viewport){
|
||||
this.scene = scene;
|
||||
this.resourceManager = ResourceManager.getInstance();
|
||||
}
|
||||
|
||||
add<T extends Tilemap>(constr: new (...a: any) => T, path: string, ...args: any): void {
|
||||
this.resourceManager.loadTilemap(path, (tilemapData: TiledTilemapData) => {
|
||||
// For each of the layers in the tilemap, create a tilemap
|
||||
for(let layer of tilemapData.layers){
|
||||
let tilemap = new constr(tilemapData, layer);
|
||||
|
||||
// Add to scene
|
||||
this.scene.addTilemap(tilemap);
|
||||
|
||||
// Load images for the tilesets
|
||||
tilemap.getTilesets().forEach(tileset => {
|
||||
let imagePath = StringUtils.getPathFromFilePath(path) + tileset.getImageUrl();
|
||||
this.resourceManager.loadImage(imagePath, (path: string, image: HTMLImageElement) => {
|
||||
tileset.setImage(image);
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -3,30 +3,36 @@ import Viewport from "../SceneGraph/Viewport";
|
|||
import SceneGraph from "../SceneGraph/SceneGraph";
|
||||
import SceneGraphArray from "../SceneGraph/SceneGraphArray";
|
||||
import CanvasNode from "../Nodes/CanvasNode";
|
||||
import CavnasNodeFactory from "./Factories/CanvasNodeFactory";
|
||||
import CanvasNodeFactory from "./Factories/CanvasNodeFactory";
|
||||
import GameState from "./GameState";
|
||||
import Tilemap from "../Nodes/Tilemap";
|
||||
import TilemapFactory from "./Factories/TilemapFactory";
|
||||
|
||||
export default class Scene {
|
||||
private gameState: GameState;
|
||||
private viewport: Viewport
|
||||
private parallax: Vec2;
|
||||
sceneGraph: SceneGraph;
|
||||
private tilemaps: Array<Tilemap>;
|
||||
private paused: boolean;
|
||||
private hidden: boolean;
|
||||
|
||||
// Factories
|
||||
public canvas: CavnasNodeFactory;
|
||||
public canvasNode: CanvasNodeFactory;
|
||||
public tilemap: TilemapFactory;
|
||||
|
||||
constructor(viewport: Viewport, gameState: GameState){
|
||||
this.gameState = gameState;
|
||||
this.viewport = viewport;
|
||||
this.parallax = new Vec2(1, 1);
|
||||
this.sceneGraph = new SceneGraphArray(this.viewport, this);
|
||||
this.tilemaps = new Array<Tilemap>();
|
||||
this.paused = false;
|
||||
this.hidden = false;
|
||||
|
||||
this.canvas = new CanvasNodeFactory(this, this.viewport);
|
||||
// Factories
|
||||
this.canvasNode = new CanvasNodeFactory(this, this.viewport);
|
||||
this.tilemap = new TilemapFactory(this, this.viewport);
|
||||
}
|
||||
|
||||
setPaused(pauseValue: boolean): void {
|
||||
|
@ -71,6 +77,10 @@ export default class Scene {
|
|||
this.sceneGraph.addNode(children);
|
||||
}
|
||||
|
||||
addTilemap(tilemap: Tilemap): void {
|
||||
this.tilemaps.push(tilemap);
|
||||
}
|
||||
|
||||
update(deltaT: number): void {
|
||||
if(!this.paused){
|
||||
this.viewport.update(deltaT);
|
||||
|
@ -83,7 +93,17 @@ export default class Scene {
|
|||
let visibleSet = this.sceneGraph.getVisibleSet();
|
||||
let viewportOrigin = this.viewport.getPosition();
|
||||
let origin = new Vec2(viewportOrigin.x*this.parallax.x, viewportOrigin.y*this.parallax.y);
|
||||
let size = this.viewport.getSize();
|
||||
|
||||
// Render visible set
|
||||
visibleSet.forEach(node => node.render(ctx, origin));
|
||||
|
||||
// Render tilemaps
|
||||
this.tilemaps.forEach(tilemap => {
|
||||
if(tilemap.isReady()){
|
||||
tilemap.render(ctx, origin, size);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import InputHandler from "../Input/InputHandler";
|
|||
import Recorder from "../Playback/Recorder";
|
||||
import GameState from "../GameState/GameState";
|
||||
import Debug from "../Debug/Debug";
|
||||
import ResourceManager from "../ResourceManager/ResourceManager";
|
||||
|
||||
export default class GameLoop{
|
||||
// The amount of time to spend on a physics step
|
||||
|
@ -36,6 +37,7 @@ export default class GameLoop{
|
|||
private recorder: Recorder;
|
||||
private gameState: GameState;
|
||||
private debug: Debug;
|
||||
private resourceManager: ResourceManager;
|
||||
|
||||
constructor(){
|
||||
this.maxFPS = 60;
|
||||
|
@ -62,12 +64,15 @@ export default class GameLoop{
|
|||
this.recorder = new Recorder();
|
||||
this.gameState = new GameState();
|
||||
this.debug = Debug.getInstance();
|
||||
this.resourceManager = ResourceManager.getInstance();
|
||||
}
|
||||
|
||||
private initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): CanvasRenderingContext2D {
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
return canvas.getContext("2d");
|
||||
let ctx = canvas.getContext("2d");
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
return ctx;
|
||||
}
|
||||
|
||||
setMaxFPS(initMax: number): void {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import GameNode from "./GameNode";
|
||||
import Vec2 from "../DataTypes/Vec2";
|
||||
import Viewport from "../SceneGraph/Viewport";
|
||||
import Scene from "../GameState/Scene";
|
||||
|
||||
export default abstract class CanvasNode extends GameNode{
|
||||
|
|
|
@ -10,7 +10,6 @@ export default class ColoredCircle extends CanvasNode{
|
|||
super();
|
||||
this.position = new Vec2(RandUtils.randInt(0, 1000), RandUtils.randInt(0, 1000));
|
||||
this.color = RandUtils.randColor();
|
||||
console.log(this.color.toStringRGB());
|
||||
this.size = new Vec2(50, 50);
|
||||
}
|
||||
|
||||
|
|
42
src/Nodes/Tilemap.ts
Normal file
42
src/Nodes/Tilemap.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import Vec2 from "../DataTypes/Vec2";
|
||||
import GameNode from "./GameNode";
|
||||
import Tileset from "../DataTypes/Tilesets/Tileset";
|
||||
import { TiledTilemapData, TiledLayerData } from "../DataTypes/Tilesets/TiledData"
|
||||
|
||||
/**
|
||||
* Represents one layer of tiles
|
||||
*/
|
||||
export default abstract class Tilemap extends GameNode {
|
||||
protected data: number[];
|
||||
protected tilesets: Tileset[];
|
||||
protected worldSize: Vec2;
|
||||
|
||||
constructor(tilemapData: TiledTilemapData, layerData: TiledLayerData){
|
||||
super();
|
||||
this.tilesets = new Array<Tileset>();
|
||||
this.worldSize = new Vec2(0, 0);
|
||||
this.init(tilemapData, layerData);
|
||||
}
|
||||
|
||||
getTilesets(): Tileset[] {
|
||||
return this.tilesets;
|
||||
}
|
||||
|
||||
isReady(): boolean {
|
||||
if(this.tilesets.length !== 0){
|
||||
for(let tileset of this.tilesets){
|
||||
if(!tileset.isReady()){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the tileset using the data loaded from file
|
||||
*/
|
||||
abstract init(tilemapData: TiledTilemapData, layerData: TiledLayerData): void;
|
||||
|
||||
abstract render(ctx: CanvasRenderingContext2D, origin: Vec2, viewportSize: Vec2): void;
|
||||
}
|
27
src/Nodes/Tilemaps/OrthogonalTilemap.ts
Normal file
27
src/Nodes/Tilemaps/OrthogonalTilemap.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import Tilemap from "../Tilemap";
|
||||
import Vec2 from "../../DataTypes/Vec2";
|
||||
import { TiledTilemapData, TiledLayerData } from "../../DataTypes/Tilesets/TiledData";
|
||||
import Tileset from "../../DataTypes/Tilesets/Tileset";
|
||||
|
||||
|
||||
export default class OrthogonalTilemap extends Tilemap {
|
||||
init(tilemapData: TiledTilemapData, layer: TiledLayerData): void {
|
||||
this.worldSize.set(tilemapData.width, tilemapData.height);
|
||||
this.data = layer.data;
|
||||
tilemapData.tilesets.forEach(tilesetData => this.tilesets.push(new Tileset(tilesetData)));
|
||||
}
|
||||
|
||||
update(deltaT: number): void {}
|
||||
|
||||
render(ctx: CanvasRenderingContext2D, origin: Vec2, viewportSize: Vec2) {
|
||||
for(let i = 0; i < this.data.length; i++){
|
||||
let tileIndex = this.data[i];
|
||||
|
||||
for(let tileset of this.tilesets){
|
||||
if(tileset.hasTile(tileIndex)){
|
||||
tileset.renderTile(ctx, tileIndex, i, this.worldSize, origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
43
src/ResourceManager/ResourceManager.ts
Normal file
43
src/ResourceManager/ResourceManager.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
export default class ResourceManager {
|
||||
private static instance: ResourceManager;
|
||||
|
||||
private constructor(){};
|
||||
|
||||
static getInstance(): ResourceManager {
|
||||
if(!this.instance){
|
||||
this.instance = new ResourceManager();
|
||||
}
|
||||
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
public loadTilemap(pathToTilemapJSON: string, callback: Function): void {
|
||||
this.loadTextFile(pathToTilemapJSON, (fileText: string) => {
|
||||
let tilemapObject = JSON.parse(fileText);
|
||||
callback(tilemapObject);
|
||||
});
|
||||
}
|
||||
|
||||
private loadTextFile(textFilePath: string, callback: Function): void {
|
||||
let xobj: XMLHttpRequest = new XMLHttpRequest();
|
||||
xobj.overrideMimeType("application/json");
|
||||
xobj.open('GET', textFilePath, true);
|
||||
xobj.onreadystatechange = function () {
|
||||
if ((xobj.readyState == 4) && (xobj.status == 200)) {
|
||||
callback(xobj.responseText);
|
||||
}
|
||||
};
|
||||
xobj.send(null);
|
||||
}
|
||||
|
||||
// TODO: When you switch to WebGL, make sure to make this private and make a "loadTexture" function
|
||||
public loadImage(path: string, callback: Function): void {
|
||||
var image = new Image();
|
||||
|
||||
image.onload = function () {
|
||||
callback(path, image);
|
||||
}
|
||||
|
||||
image.src = path;
|
||||
}
|
||||
}
|
8
src/Utils/StringUtils.ts
Normal file
8
src/Utils/StringUtils.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
export default class StringUtils {
|
||||
static getPathFromFilePath(filePath: string): string {
|
||||
let splitPath = filePath.split("/");
|
||||
splitPath.pop();
|
||||
splitPath.push("");
|
||||
return splitPath.join("/");
|
||||
}
|
||||
}
|
28
src/main.ts
28
src/main.ts
|
@ -6,6 +6,7 @@ import ColoredCircle from "./Nodes/ColoredCircle";
|
|||
import Color from "./Utils/Color";
|
||||
import Button from "./Nodes/UIElements/Button";
|
||||
import {} from "./index";
|
||||
import OrthogonalTilemap from "./Nodes/Tilemaps/OrthogonalTilemap";
|
||||
|
||||
function main(){
|
||||
// Create the game object
|
||||
|
@ -23,28 +24,28 @@ function main(){
|
|||
pauseMenu.setParallax(0, 0);
|
||||
|
||||
// Initialize GameObjects
|
||||
let player = mainScene.canvas.add(Player);
|
||||
let player = mainScene.canvasNode.add(Player);
|
||||
mainScene.getViewport().follow(player);
|
||||
|
||||
let recordButton = uiLayer.canvas.add(Button);
|
||||
let recordButton = uiLayer.canvasNode.add(Button);
|
||||
recordButton.setSize(100, 50);
|
||||
recordButton.setText("Record");
|
||||
recordButton.setPosition(400, 30);
|
||||
recordButton.onClickEventId = "record_button_press";
|
||||
|
||||
let stopButton = uiLayer.canvas.add(Button);
|
||||
let stopButton = uiLayer.canvasNode.add(Button);
|
||||
stopButton.setSize(100, 50);
|
||||
stopButton.setText("Stop");
|
||||
stopButton.setPosition(550, 30);
|
||||
stopButton.onClickEventId = "stop_button_press";
|
||||
|
||||
let playButton = uiLayer.canvas.add(Button);
|
||||
let playButton = uiLayer.canvasNode.add(Button);
|
||||
playButton.setSize(100, 50);
|
||||
playButton.setText("Play");
|
||||
playButton.setPosition(700, 30);
|
||||
playButton.onClickEventId = "play_button_press";
|
||||
|
||||
let cycleFramerateButton = uiLayer.canvas.add(Button);
|
||||
let cycleFramerateButton = uiLayer.canvasNode.add(Button);
|
||||
cycleFramerateButton.setSize(150, 50);
|
||||
cycleFramerateButton.setText("Cycle FPS");
|
||||
cycleFramerateButton.setPosition(5, 400);
|
||||
|
@ -55,7 +56,7 @@ function main(){
|
|||
i = (i + 1) % 3;
|
||||
}
|
||||
|
||||
let pauseButton = uiLayer.canvas.add(Button);
|
||||
let pauseButton = uiLayer.canvasNode.add(Button);
|
||||
pauseButton.setSize(100, 50);
|
||||
pauseButton.setText("Pause");
|
||||
pauseButton.setPosition(700, 400);
|
||||
|
@ -64,12 +65,12 @@ function main(){
|
|||
pauseMenu.enable();
|
||||
}
|
||||
|
||||
let modalBackground = pauseMenu.canvas.add(UIElement);
|
||||
let modalBackground = pauseMenu.canvasNode.add(UIElement);
|
||||
modalBackground.setSize(400, 200);
|
||||
modalBackground.setBackgroundColor(new Color(0, 0, 0, 0.4));
|
||||
modalBackground.setPosition(200, 100);
|
||||
|
||||
let resumeButton = pauseMenu.canvas.add(Button);
|
||||
let resumeButton = pauseMenu.canvasNode.add(Button);
|
||||
resumeButton.setSize(100, 50);
|
||||
resumeButton.setText("Resume");
|
||||
resumeButton.setPosition(400, 200);
|
||||
|
@ -79,18 +80,13 @@ function main(){
|
|||
}
|
||||
|
||||
for(let i = 0; i < 10; i++){
|
||||
mainScene.canvas.add(ColoredCircle);
|
||||
mainScene.canvasNode.add(ColoredCircle);
|
||||
}
|
||||
|
||||
for(let i = 0; i < 20; i++){
|
||||
let cc = backgroundScene.canvas.add(ColoredCircle);
|
||||
cc.setSize(30, 30);
|
||||
cc.setColor(cc.getColor().darken().darken())
|
||||
cc.getColor().a = 0.8;
|
||||
}
|
||||
backgroundScene.tilemap.add(OrthogonalTilemap, "assets/tilemaps/MultiLayer.json");
|
||||
|
||||
for(let i = 0; i < 30; i++){
|
||||
let cc = foregroundLayer.canvas.add(ColoredCircle);
|
||||
let cc = foregroundLayer.canvasNode.add(ColoredCircle);
|
||||
cc.setSize(80, 80);
|
||||
cc.setColor(cc.getColor().lighten().lighten())
|
||||
cc.getColor().a = 0.5;
|
||||
|
|
Loading…
Reference in New Issue
Block a user