diff --git a/package.json b/package.json index 771f0a0..2d2503b 100644 --- a/package.json +++ b/package.json @@ -6,12 +6,11 @@ }, "dependencies": { "css-loader": "^0.23.1", - "html-webpack-plugin": "^1.7.0", "lodash": "^3.10.1", - "style-loader": "^0.13.0", + "style-loader": "^0.13.1", "ts-loader": "^0.7.2", - "typescript": "^1.7.5", - "webpack": "^1.12.9", - "webpack-dev-server": "^1.14.0" + "typescript": "^1.8.10", + "webpack": "^1.13.1", + "webpack-dev-server": "^1.14.1" } } diff --git a/src/app.ts b/src/app.ts index e47d41f..acd255a 100644 --- a/src/app.ts +++ b/src/app.ts @@ -11,6 +11,16 @@ import MapContext from './control/MapContext'; import MapData from './model/MapData'; import InputHandler from './view/InputHandler'; +import * as util from './util'; + + +var relevantKeys = [ + InputHandler.Up, + InputHandler.Right, + InputHandler.Down, + InputHandler.Left, +]; + var mapContext: MapContext; @@ -19,7 +29,7 @@ window.onload = () => { xhr.onload = function() { var mapDef = new MapData(JSON.parse(this.responseText)); - var inputHandler = new InputHandler(); + var inputHandler = new InputHandler(util.arrayToObject(relevantKeys)); mapContext = new MapContext(mapDef, inputHandler); } diff --git a/src/control/MapContext.ts b/src/control/MapContext.ts index 48e8b9e..1660ad4 100644 --- a/src/control/MapContext.ts +++ b/src/control/MapContext.ts @@ -13,13 +13,23 @@ import MapView from '../view/MapView'; export default class MapContext { - view: MapView; + private view: MapView; - entities: EntityPosition[] = []; - playerEntity: EntityPosition; + private entities: EntityPosition[] = []; + private playerEntity: EntityPosition; + + private collision: number[][]; constructor(private map: MapData, private inputHandler: InputHandler) { + this.collision = new Array(map.width); + for (let i = 0; i < map.width; i++) { + this.collision[i] = new Array(map.height); + + for (let j = 0; j < map.height; j++) + this.collision[i][j] = map.collision[j][i] == '0' ? 0 : 1; + } + this.playerEntity = new EntityPosition( new Entity('square'), new Position(8, 8), @@ -28,6 +38,12 @@ export default class MapContext { this.addEntity(this.playerEntity); + this.addEntity(new EntityPosition( + new Entity('square'), + new Position(10, 10), + Direction.East + )); + this.view = new MapView( map, this.entities, @@ -41,24 +57,50 @@ export default class MapContext { }); } - private addEntity(entity: EntityPosition) { + + private inMap(p: Position): boolean { + return p.x >= 0 && p.x < this.map.width && p.y >= 0 && p.y < this.map.height; + } + + private incCollision(p: Position): void { + if (this.inMap(p)) + this.collision[p.x][p.y]++; + } + + private decCollision(p: Position): void { + if (this.inMap(p)) + this.collision[p.x][p.y]--; + } + + private collides(p: Position): boolean { + return (!this.inMap(p)) || (this.collision[p.x][p.y] > 0); + } + + private addEntity(entity: EntityPosition): void { this.entities.push(entity); + + this.incCollision(entity.position); } - private addTransition(entity: EntityPosition, dest: Position, start: number, dur: number) { + private addTransition(entity: EntityPosition, dest: Position, start: number, dur: number): void { entity.transition = new Transition(start, start+dur, entity.position, dest); + + this.incCollision(dest); } - private finishTransition(entity: EntityPosition) { + private finishTransition(entity: EntityPosition): void { + this.decCollision(entity.position); + entity.position = entity.transition.dest; entity.transition = null; + } private updateState(time: number): boolean { var ret = false; while (true) { - var origTime = time; + let origTime = time; if (this.playerEntity.transition && this.playerEntity.transition.end <= time) { origTime = this.playerEntity.transition.end; @@ -69,7 +111,7 @@ export default class MapContext { if (this.playerEntity.transition) return true; - var dir: Direction = null; + let dir: Direction = null; if (this.inputHandler.keys[InputHandler.Up]) dir = Direction.North; @@ -84,8 +126,13 @@ export default class MapContext { return ret; this.playerEntity.direction = dir; - this.addTransition(this.playerEntity, this.playerEntity.position.translate(dir, 1), - origTime, 250); + ret = true; + + let dest = this.playerEntity.position.translate(dir, 1); + if (this.collides(dest)) + return true; + + this.addTransition(this.playerEntity, dest, origTime, 250); } } } diff --git a/src/model/EntityPosition.ts b/src/model/EntityPosition.ts index 28b3837..19bcad5 100644 --- a/src/model/EntityPosition.ts +++ b/src/model/EntityPosition.ts @@ -8,7 +8,7 @@ import Transition from './Transition'; export default class EntityPosition { - public transition: Transition = null; + transition: Transition = null; constructor(public entity: Entity, public position: Position, public direction: Direction) {} } diff --git a/src/model/MapData.ts b/src/model/MapData.ts index 54894c0..351632f 100644 --- a/src/model/MapData.ts +++ b/src/model/MapData.ts @@ -1,20 +1,29 @@ 'use strict'; +import TileData from './TileData'; + + interface Input { - tiles: {[key: string]: {file: string}}; + tiles: {[key: string]: TileData}; collision: string[]; layers: string[][][]; } export default class MapData { - tiles: {[key: string]: {file: string}}; - public collision: string[]; - public layers: string[][][]; + tiles: {[key: string]: TileData}; + collision: string[]; + layers: string[][][]; + + width: number; + height: number; constructor(data: Input) { this.tiles = data.tiles; this.collision = data.collision; this.layers = data.layers; + + this.height = this.collision.length; + this.width = this.collision[0].length; } } diff --git a/src/model/TileData.ts b/src/model/TileData.ts new file mode 100644 index 0000000..b736e5d --- /dev/null +++ b/src/model/TileData.ts @@ -0,0 +1,7 @@ +'use strict'; + + +export default class TileData { + file: string; + subtile: number; +} diff --git a/src/util.ts b/src/util.ts index 2292820..0761d2d 100644 --- a/src/util.ts +++ b/src/util.ts @@ -2,8 +2,8 @@ export function mapPromises(promises: {[key: string]: Promise}): Promise<{[key: string]: T}> { - var p: Promise[] = [] - var ret: {[key: string]: T} = {} + var p: Promise[] = []; + var ret: {[key: string]: T} = {}; _.forOwn(promises, (v, k) => { p.push(v.then(r => {ret[k] = r;})); @@ -11,3 +11,12 @@ export function mapPromises(promises: {[key: string]: Promise}): Promise<{ return Promise.all(p).then(() => ret); } + +export function arrayToObject(arr: (number|string)[]): {[key: string]: boolean} { + var ret: {[key: string]: boolean} = {}; + + for (let v of arr) + ret[v] = true; + + return ret; +} diff --git a/src/view/InputHandler.ts b/src/view/InputHandler.ts index dcfd85d..d06bcf8 100644 --- a/src/view/InputHandler.ts +++ b/src/view/InputHandler.ts @@ -7,14 +7,19 @@ import Direction from '../model/Direction'; class InputHandler { keys: {[key: number]: boolean} = {}; - listeners: (() => void)[] = []; + private listeners: (() => void)[] = []; - private callListeners() { + private callListeners(): void { this.listeners.forEach(l => l()); } - constructor() { + constructor(private relevantKeys: {[key: number]: boolean}) { window.addEventListener('keydown', (ev) => { + if (!relevantKeys[ev.keyCode]) + return; + + ev.preventDefault(); + if (!this.keys[ev.keyCode]) { this.keys[ev.keyCode] = true; this.callListeners(); @@ -22,6 +27,11 @@ class InputHandler { }); window.addEventListener('keyup', (ev) => { + if (!relevantKeys[ev.keyCode]) + return; + + ev.preventDefault(); + if (this.keys[ev.keyCode]) { delete this.keys[ev.keyCode]; this.callListeners(); diff --git a/src/view/MapView.ts b/src/view/MapView.ts index 230ecf3..0c7b8a3 100644 --- a/src/view/MapView.ts +++ b/src/view/MapView.ts @@ -5,6 +5,7 @@ import * as util from '../util'; import EntityPosition from '../model/EntityPosition'; import MapData from '../model/MapData'; import Position from '../model/Position'; +import TileData from '../model/TileData'; const tileSize = 32; @@ -25,8 +26,14 @@ function loadImages(imgs: {[key: string]: string}): Promise<{[key: string]: HTML return util.mapPromises(_.mapValues(imgs, loadImage)); } -function loadTiles(tiles: {[key: string]: {file: string}}): Promise<{[key: string]: HTMLImageElement}> { - return loadImages(_.mapValues(tiles, (t) => `resources/sprite/tile/${t.file}.png`)); +function loadTiles(tiles: {[key: string]: TileData}): Promise<{[key: string]: HTMLImageElement}> { + var imgs: {[key: string]: string} = {} + + _.forOwn(tiles, t => { + imgs[t.file] = `resources/sprite/tile/${t.file}.png` + }); + + return loadImages(imgs); } function loadEntities(entities: EntityPosition[]): Promise<{[key: string]: HTMLImageElement}> { @@ -100,7 +107,7 @@ export default class MapView { }); } - private setSize() { + private setSize(): void { var e = document.documentElement; var w = window.innerWidth || e.clientWidth || body.clientWidth; var h = window.innerHeight || e.clientHeight || body.clientHeight; @@ -114,11 +121,27 @@ export default class MapView { this.redraw() } - private drawTile(x: number, y: number, tile: HTMLImageElement) { + private drawSprite(img: HTMLImageElement, srcX: number, srcY: number, destX: number, destY: number): void { + this.ctx.drawImage( + img, + tiles(srcX), tiles(srcY), + tileSize, tileSize, + tiles(destX)*this.scale, + tiles(destY)*this.scale, + tileSize*this.scale, + tileSize*this.scale + ); + } + + private drawTile(x: number, y: number, tile: TileData): void { if (!tile) return; - this.ctx.drawImage(tile, tiles(x)*this.scale, tiles(y)*this.scale, tileSize*this.scale, tileSize*this.scale); + var img = this.tiles[tile.file]; + if (!img) + return; + + this.drawSprite(img, tile.subtile || 0, 0, x, y); } private drawEntity(e: EntityPosition, time: number): boolean { @@ -127,19 +150,12 @@ export default class MapView { return false; var p = entityPosition(e, time); - - this.ctx.drawImage( - sprite, - tiles(e.direction), 0, - tileSize, tileSize, - tiles(p.x)*this.scale, tiles(p.y)*this.scale, - tileSize*this.scale, tileSize*this.scale - ); + this.drawSprite(sprite, e.direction, 0, p.x, p.y); return !!e.transition; } - private setOrigin(time: number) { + private setOrigin(time: number): Rect { var origin = entityPosition(this.origin, time); var w = this.canvas.width; @@ -156,7 +172,7 @@ export default class MapView { ); } - private draw(time: number) { + private draw(time: number): void { this.updateState(time); this.redrawPending = false; @@ -186,7 +202,7 @@ export default class MapView { if (!tile) continue; - this.drawTile(x, y, this.tiles[tile]); + this.drawTile(x, y, this.map.tiles[tile]); } } }); @@ -204,7 +220,7 @@ export default class MapView { this.redraw(); } - redraw() { + redraw(): void { if (this.redrawPending) return; diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..3d4af86 --- /dev/null +++ b/static/index.html @@ -0,0 +1,10 @@ + + + + + RPGedit + + + + + diff --git a/static/resources/map/test.json b/static/resources/map/test.json index 12b1894..4689076 100644 --- a/static/resources/map/test.json +++ b/static/resources/map/test.json @@ -1,42 +1,42 @@ { "tiles": { - "G": {"rotate": 0, "file": "grass"}, - "<": {"rotate": 0, "file": "road_left"}, - ">": {"rotate": 0, "file": "road_right"} + "G": {"file": "grass"}, + "<": {"file": "road", "subtile": 0}, + ">": {"file": "road", "subtile": 1} }, "collision": [ - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111", - "11111111111111111111111111111111" + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000110000000000000000000", + "00000000000000000000000000000000" ], "layers": [ [ diff --git a/static/resources/sprite/tile/road.png b/static/resources/sprite/tile/road.png new file mode 100644 index 0000000..93e0a81 Binary files /dev/null and b/static/resources/sprite/tile/road.png differ diff --git a/tsconfig.json b/tsconfig.json index 1aef94c..f5c08b7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,10 +3,32 @@ "module": "commonjs", "target": "es5", "noImplicitAny": true, - "sourceMap": true + "sourceMap": true, + "noEmit": true }, "files": [ "require.d.ts", - "typings/tsd.d.ts" - ] + "typings/tsd.d.ts", + "src/app.ts", + "src/control/MapContext.ts", + "src/model/Direction.ts", + "src/model/Entity.ts", + "src/model/EntityPosition.ts", + "src/model/MapData.ts", + "src/model/Position.ts", + "src/model/TileData.ts", + "src/model/Transition.ts", + "src/util.ts", + "src/view/InputHandler.ts", + "src/view/MapView.ts" + ], + "filesGlob": [ + "require.d.ts", + "typings/tsd.d.ts", + "src/**/*.ts" + ], + "compileOnSave": false, + "atom": { + "rewriteTsconfig": true + } } diff --git a/webpack.config.js b/webpack.config.js index 1979538..3bbb7ba 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,11 +6,6 @@ module.exports = { path: './build', filename: 'bundle.js' }, - plugins: [ - new HtmlWebpackPlugin({ - title: 'RPGedit' - }) - ], module: { loaders: [ { test: /\.css$/, loader: 'style-loader!css-loader' }, @@ -19,5 +14,8 @@ module.exports = { }, resolve: { extensions: ['', '.ts'] + }, + ts: { + compilerOptions: { noEmit: false } } };