Compare commits

...

10 commits

14 changed files with 223 additions and 86 deletions

View file

@ -6,12 +6,11 @@
}, },
"dependencies": { "dependencies": {
"css-loader": "^0.23.1", "css-loader": "^0.23.1",
"html-webpack-plugin": "^1.7.0",
"lodash": "^3.10.1", "lodash": "^3.10.1",
"style-loader": "^0.13.0", "style-loader": "^0.13.1",
"ts-loader": "^0.7.2", "ts-loader": "^0.7.2",
"typescript": "^1.7.5", "typescript": "^1.8.10",
"webpack": "^1.12.9", "webpack": "^1.13.1",
"webpack-dev-server": "^1.14.0" "webpack-dev-server": "^1.14.1"
} }
} }

View file

@ -11,6 +11,16 @@ import MapContext from './control/MapContext';
import MapData from './model/MapData'; import MapData from './model/MapData';
import InputHandler from './view/InputHandler'; import InputHandler from './view/InputHandler';
import * as util from './util';
var relevantKeys = [
InputHandler.Up,
InputHandler.Right,
InputHandler.Down,
InputHandler.Left,
];
var mapContext: MapContext; var mapContext: MapContext;
@ -19,7 +29,7 @@ window.onload = () => {
xhr.onload = function() { xhr.onload = function() {
var mapDef = new MapData(JSON.parse(this.responseText)); var mapDef = new MapData(JSON.parse(this.responseText));
var inputHandler = new InputHandler(); var inputHandler = new InputHandler(util.arrayToObject(relevantKeys));
mapContext = new MapContext(mapDef, inputHandler); mapContext = new MapContext(mapDef, inputHandler);
} }

View file

@ -13,13 +13,23 @@ import MapView from '../view/MapView';
export default class MapContext { export default class MapContext {
view: MapView; private view: MapView;
entities: EntityPosition[] = []; private entities: EntityPosition[] = [];
playerEntity: EntityPosition; private playerEntity: EntityPosition;
private collision: number[][];
constructor(private map: MapData, private inputHandler: InputHandler) { 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( this.playerEntity = new EntityPosition(
new Entity('square'), new Entity('square'),
new Position(8, 8), new Position(8, 8),
@ -28,6 +38,12 @@ export default class MapContext {
this.addEntity(this.playerEntity); this.addEntity(this.playerEntity);
this.addEntity(new EntityPosition(
new Entity('square'),
new Position(10, 10),
Direction.East
));
this.view = new MapView( this.view = new MapView(
map, map,
this.entities, 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.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); 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.position = entity.transition.dest;
entity.transition = null; entity.transition = null;
} }
private updateState(time: number): boolean { private updateState(time: number): boolean {
var ret = false; var ret = false;
while (true) { while (true) {
var origTime = time; let origTime = time;
if (this.playerEntity.transition && this.playerEntity.transition.end <= time) { if (this.playerEntity.transition && this.playerEntity.transition.end <= time) {
origTime = this.playerEntity.transition.end; origTime = this.playerEntity.transition.end;
@ -69,7 +111,7 @@ export default class MapContext {
if (this.playerEntity.transition) if (this.playerEntity.transition)
return true; return true;
var dir: Direction = null; let dir: Direction = null;
if (this.inputHandler.keys[InputHandler.Up]) if (this.inputHandler.keys[InputHandler.Up])
dir = Direction.North; dir = Direction.North;
@ -84,8 +126,13 @@ export default class MapContext {
return ret; return ret;
this.playerEntity.direction = dir; this.playerEntity.direction = dir;
this.addTransition(this.playerEntity, this.playerEntity.position.translate(dir, 1), ret = true;
origTime, 250);
let dest = this.playerEntity.position.translate(dir, 1);
if (this.collides(dest))
return true;
this.addTransition(this.playerEntity, dest, origTime, 250);
} }
} }
} }

View file

@ -8,7 +8,7 @@ import Transition from './Transition';
export default class EntityPosition { export default class EntityPosition {
public transition: Transition = null; transition: Transition = null;
constructor(public entity: Entity, public position: Position, public direction: Direction) {} constructor(public entity: Entity, public position: Position, public direction: Direction) {}
} }

View file

@ -1,20 +1,29 @@
'use strict'; 'use strict';
import TileData from './TileData';
interface Input { interface Input {
tiles: {[key: string]: {file: string}}; tiles: {[key: string]: TileData};
collision: string[]; collision: string[];
layers: string[][][]; layers: string[][][];
} }
export default class MapData { export default class MapData {
tiles: {[key: string]: {file: string}}; tiles: {[key: string]: TileData};
public collision: string[]; collision: string[];
public layers: string[][][]; layers: string[][][];
width: number;
height: number;
constructor(data: Input) { constructor(data: Input) {
this.tiles = data.tiles; this.tiles = data.tiles;
this.collision = data.collision; this.collision = data.collision;
this.layers = data.layers; this.layers = data.layers;
this.height = this.collision.length;
this.width = this.collision[0].length;
} }
} }

7
src/model/TileData.ts Normal file
View file

@ -0,0 +1,7 @@
'use strict';
export default class TileData {
file: string;
subtile: number;
}

View file

@ -2,8 +2,8 @@
export function mapPromises<T>(promises: {[key: string]: Promise<T>}): Promise<{[key: string]: T}> { export function mapPromises<T>(promises: {[key: string]: Promise<T>}): Promise<{[key: string]: T}> {
var p: Promise<void>[] = [] var p: Promise<void>[] = [];
var ret: {[key: string]: T} = {} var ret: {[key: string]: T} = {};
_.forOwn(promises, (v, k) => { _.forOwn(promises, (v, k) => {
p.push(v.then(r => {ret[k] = r;})); p.push(v.then(r => {ret[k] = r;}));
@ -11,3 +11,12 @@ export function mapPromises<T>(promises: {[key: string]: Promise<T>}): Promise<{
return Promise.all(p).then(() => ret); 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;
}

View file

@ -7,14 +7,19 @@ import Direction from '../model/Direction';
class InputHandler { class InputHandler {
keys: {[key: number]: boolean} = {}; keys: {[key: number]: boolean} = {};
listeners: (() => void)[] = []; private listeners: (() => void)[] = [];
private callListeners() { private callListeners(): void {
this.listeners.forEach(l => l()); this.listeners.forEach(l => l());
} }
constructor() { constructor(private relevantKeys: {[key: number]: boolean}) {
window.addEventListener('keydown', (ev) => { window.addEventListener('keydown', (ev) => {
if (!relevantKeys[ev.keyCode])
return;
ev.preventDefault();
if (!this.keys[ev.keyCode]) { if (!this.keys[ev.keyCode]) {
this.keys[ev.keyCode] = true; this.keys[ev.keyCode] = true;
this.callListeners(); this.callListeners();
@ -22,6 +27,11 @@ class InputHandler {
}); });
window.addEventListener('keyup', (ev) => { window.addEventListener('keyup', (ev) => {
if (!relevantKeys[ev.keyCode])
return;
ev.preventDefault();
if (this.keys[ev.keyCode]) { if (this.keys[ev.keyCode]) {
delete this.keys[ev.keyCode]; delete this.keys[ev.keyCode];
this.callListeners(); this.callListeners();

View file

@ -5,6 +5,7 @@ import * as util from '../util';
import EntityPosition from '../model/EntityPosition'; import EntityPosition from '../model/EntityPosition';
import MapData from '../model/MapData'; import MapData from '../model/MapData';
import Position from '../model/Position'; import Position from '../model/Position';
import TileData from '../model/TileData';
const tileSize = 32; const tileSize = 32;
@ -25,8 +26,14 @@ function loadImages(imgs: {[key: string]: string}): Promise<{[key: string]: HTML
return util.mapPromises(_.mapValues(imgs, loadImage)); return util.mapPromises(_.mapValues(imgs, loadImage));
} }
function loadTiles(tiles: {[key: string]: {file: string}}): Promise<{[key: string]: HTMLImageElement}> { function loadTiles(tiles: {[key: string]: TileData}): Promise<{[key: string]: HTMLImageElement}> {
return loadImages(_.mapValues(tiles, (t) => `resources/sprite/tile/${t.file}.png`)); 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}> { 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 e = document.documentElement;
var w = window.innerWidth || e.clientWidth || body.clientWidth; var w = window.innerWidth || e.clientWidth || body.clientWidth;
var h = window.innerHeight || e.clientHeight || body.clientHeight; var h = window.innerHeight || e.clientHeight || body.clientHeight;
@ -114,11 +121,27 @@ export default class MapView {
this.redraw() 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) if (!tile)
return; 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 { private drawEntity(e: EntityPosition, time: number): boolean {
@ -127,19 +150,12 @@ export default class MapView {
return false; return false;
var p = entityPosition(e, time); var p = entityPosition(e, time);
this.drawSprite(sprite, e.direction, 0, p.x, p.y);
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
);
return !!e.transition; return !!e.transition;
} }
private setOrigin(time: number) { private setOrigin(time: number): Rect {
var origin = entityPosition(this.origin, time); var origin = entityPosition(this.origin, time);
var w = this.canvas.width; 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.updateState(time);
this.redrawPending = false; this.redrawPending = false;
@ -186,7 +202,7 @@ export default class MapView {
if (!tile) if (!tile)
continue; 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(); this.redraw();
} }
redraw() { redraw(): void {
if (this.redrawPending) if (this.redrawPending)
return; return;

10
static/index.html Normal file
View file

@ -0,0 +1,10 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>RPGedit</title>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>

View file

@ -1,42 +1,42 @@
{ {
"tiles": { "tiles": {
"G": {"rotate": 0, "file": "grass"}, "G": {"file": "grass"},
"<": {"rotate": 0, "file": "road_left"}, "<": {"file": "road", "subtile": 0},
">": {"rotate": 0, "file": "road_right"} ">": {"file": "road", "subtile": 1}
}, },
"collision": [ "collision": [
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111", "00000000000110000000000000000000",
"11111111111111111111111111111111" "00000000000000000000000000000000"
], ],
"layers": [ "layers": [
[ [

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

View file

@ -3,10 +3,32 @@
"module": "commonjs", "module": "commonjs",
"target": "es5", "target": "es5",
"noImplicitAny": true, "noImplicitAny": true,
"sourceMap": true "sourceMap": true,
"noEmit": true
}, },
"files": [ "files": [
"require.d.ts", "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
}
} }

View file

@ -6,11 +6,6 @@ module.exports = {
path: './build', path: './build',
filename: 'bundle.js' filename: 'bundle.js'
}, },
plugins: [
new HtmlWebpackPlugin({
title: 'RPGedit'
})
],
module: { module: {
loaders: [ loaders: [
{ test: /\.css$/, loader: 'style-loader!css-loader' }, { test: /\.css$/, loader: 'style-loader!css-loader' },
@ -19,5 +14,8 @@ module.exports = {
}, },
resolve: { resolve: {
extensions: ['', '.ts'] extensions: ['', '.ts']
},
ts: {
compilerOptions: { noEmit: false }
} }
}; };