first attempt at movement
BIN
images/png/clouds.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
images/png/level1.png
Normal file
|
After Width: | Height: | Size: 412 KiB |
BIN
images/png/lyre.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
images/png/wind/wind1.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
images/png/wind/wind10.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
images/png/wind/wind11.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
images/png/wind/wind2.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
images/png/wind/wind3.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
images/png/wind/wind4.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
images/png/wind/wind5.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
images/png/wind/wind6.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
images/png/wind/wind7.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
images/png/wind/wind8.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
images/png/wind/wind9.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
5735
package-lock.json
generated
|
|
@ -23,6 +23,7 @@
|
|||
"workbox-strategies": "^6.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"fabric": "^4.3.1",
|
||||
"howler": "^2.2.1",
|
||||
"sirv-cli": "^1.0.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ html, body {
|
|||
|
||||
body {
|
||||
color: #333;
|
||||
background: #01021B;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
|
|
|
|||
BIN
public/sprite/clouds.webp
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
public/sprite/level1.webp
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
public/sprite/lyre.webp
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
public/sprite/michael.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
public/sprite/uriel.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
public/sprite/wind.gif
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/sprite/wind.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
|
|
@ -5,8 +5,8 @@
|
|||
import { characters } from "./stores/characters.js";
|
||||
import { dialog } from "./stores/dialog.js";
|
||||
import Game from "./pages/game.svelte";
|
||||
import { gameActive } from "./stores/gameActive";
|
||||
|
||||
var page = "game";
|
||||
var current = localStorage.getItem("dialog-page") || 0;
|
||||
|
||||
var preloads = new Map;
|
||||
|
|
@ -29,14 +29,11 @@
|
|||
autoplay: true
|
||||
});
|
||||
|
||||
var gameActive = true;
|
||||
function startPlaying(e) {
|
||||
if(!music.playing()) music.play();
|
||||
if(e.key === "Escape") {
|
||||
gameActive = !gameActive;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(dialog[current]);
|
||||
console.log("Pancake recipe at https://github.com/danbulant/heaventaker");
|
||||
</script>
|
||||
|
||||
|
|
@ -46,8 +43,10 @@
|
|||
<title>Heaventaker</title>
|
||||
</svelte:head>
|
||||
|
||||
<Game bind:current />
|
||||
{#if dialog[current].map}
|
||||
<Game bind:current />
|
||||
{/if}
|
||||
|
||||
<Overlay active={gameActive}>
|
||||
<Dialog bind:current page />
|
||||
<Overlay active={$gameActive}>
|
||||
<Dialog bind:current />
|
||||
</Overlay>
|
||||
|
|
|
|||
15
src/game/fpsCalc.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
var lastCalledTime;
|
||||
var last = [];
|
||||
|
||||
export function getFPS(time) {
|
||||
if(!lastCalledTime) {
|
||||
lastCalledTime = time
|
||||
return 0;
|
||||
}
|
||||
var delta = (time - lastCalledTime)/1000;
|
||||
lastCalledTime = time;
|
||||
last.push(1/delta);
|
||||
if(last.length >= 120) last.shift();
|
||||
return last.reduce((a, b) => a + b, 0) / last.length;
|
||||
}
|
||||
70
src/game/images.js
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
class ImageHandler {
|
||||
constructor() {
|
||||
/** @type {Map<string, HTMLImageElement>} */
|
||||
this.images = new Map();
|
||||
|
||||
/** @type {Function[]} */
|
||||
this.onloadHandlers = [];
|
||||
this.loaded = false;
|
||||
this.shouldFire = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the handler when all images are loaded
|
||||
* @param {(images: Map<string, Image>) => void} handler
|
||||
*/
|
||||
onload(handler) {
|
||||
this.onloadHandlers.push(handler);
|
||||
}
|
||||
|
||||
areLoaded() {
|
||||
return this.loaded && this.areAllLoaded();
|
||||
}
|
||||
|
||||
startLoad() {
|
||||
this.shouldFire = true;
|
||||
if(this.areAllLoaded()) {
|
||||
this.onloadHandlers.forEach(cb => cb(this.images));
|
||||
this.onloadHandlers = [];
|
||||
this.loaded = true;
|
||||
this.shouldFire = false;
|
||||
}
|
||||
}
|
||||
|
||||
areAllLoaded() {
|
||||
return [...this.images.values()].every(a => a.complete && a.naturalHeight !== 0);
|
||||
}
|
||||
|
||||
update() {
|
||||
if(!this.areAllLoaded() || !this.shouldFire) return; // not yet
|
||||
this.onloadHandlers.forEach(cb => cb(this.images));
|
||||
this.onloadHandlers = [];
|
||||
this.loaded = true;
|
||||
}
|
||||
|
||||
get(image) {
|
||||
return this.images.get(image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the image to requested load
|
||||
* @param {string} key
|
||||
* @param {string | Image} image
|
||||
*/
|
||||
load(key, image) {
|
||||
if(typeof image === "string") {
|
||||
var i = new Image();
|
||||
i.onload = () => this.update();
|
||||
i.src = image;
|
||||
image = i;
|
||||
} else {
|
||||
image.onload = () => this.update();
|
||||
}
|
||||
this.loaded = false;
|
||||
this.images.set(key, image);
|
||||
}
|
||||
}
|
||||
|
||||
var images = new ImageHandler;
|
||||
export { ImageHandler };
|
||||
export default images;
|
||||
273
src/game/index.js
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
import { fabric } from "fabric";
|
||||
import images from "./images";
|
||||
import { keys } from "./input";
|
||||
import { maps } from "./maps";
|
||||
import { Sprite } from "./sprite";
|
||||
|
||||
|
||||
/**
|
||||
* @typedef Sprite
|
||||
* @property {"sprite"} type
|
||||
* @property {number} spriteWidth
|
||||
* @property {number} spriteHeight
|
||||
* @property {number} spriteIndex
|
||||
* @property {number} frameTime
|
||||
*
|
||||
* @property {(element: HTMLImageElement, options: any) => Sprite} constructor
|
||||
*/
|
||||
|
||||
/** @type {HTMLCanvasElement} */
|
||||
var htmlcanvas;
|
||||
/** @type {fabric.StaticCanvas} */
|
||||
var canvas;
|
||||
export function setCanvas(htmlCanvas) {
|
||||
htmlcanvas = htmlCanvas;
|
||||
canvas = new fabric.StaticCanvas(htmlcanvas);
|
||||
canvas.backgroundColor = "#01021B";
|
||||
load();
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Map<string, fabric.Object>}
|
||||
*/
|
||||
const objects = new Map;
|
||||
|
||||
function load() {
|
||||
objects.set("loadingText", new fabric.Text("Loading", {
|
||||
left: canvas.getWidth() / 2,
|
||||
top: 0,
|
||||
fill: "white",
|
||||
textAlign: "center",
|
||||
originX: "center",
|
||||
fontFamily: "monospace"
|
||||
}))
|
||||
canvas.add(objects.get("loadingText"));
|
||||
images.load("level1", "/sprite/level1.webp");
|
||||
images.load("lyre", "/sprite/lyre.webp");
|
||||
images.load("wind", "/sprite/wind.png");
|
||||
images.load("cloud", "/sprite/clouds.webp");
|
||||
images.load("uriel", "/sprite/uriel.png");
|
||||
images.load("michael", "/sprite/michael.png");
|
||||
images.load("spawn", "/sprite/michael.png");
|
||||
images.startLoad();
|
||||
loading = true;
|
||||
}
|
||||
|
||||
var map;
|
||||
/** @type {{
|
||||
background: string,
|
||||
sprite: string,
|
||||
offset: { x: number, y: nunber },
|
||||
size: { x: number, y: number },
|
||||
px: number,
|
||||
map: string[][]
|
||||
}}
|
||||
*/
|
||||
var mapdata;
|
||||
var mapName;
|
||||
export function setMap(name) {
|
||||
if(mapName === name) return;
|
||||
mapdata = maps[name];
|
||||
mapName = name;
|
||||
|
||||
map = mapdata.map.map(m => m.map(piece => typeof piece === "string" && { type: piece } || piece));
|
||||
|
||||
console.table(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {{
|
||||
* object: fabric.Object,
|
||||
* property: string,
|
||||
* value: number,
|
||||
* onComplete: Function,
|
||||
* start: Date,
|
||||
* update: Function
|
||||
* }[]}
|
||||
*/
|
||||
var animations = [];
|
||||
|
||||
/**
|
||||
* Animates given property
|
||||
* @param {fabric.Object} object
|
||||
* @param {string} property
|
||||
* @param {number} value
|
||||
* @param {Function} onComplete
|
||||
*/
|
||||
function animate(object, property, value, onComplete) {
|
||||
const length = 400;
|
||||
animations.push({
|
||||
object,
|
||||
property,
|
||||
value,
|
||||
onComplete,
|
||||
start: new Date,
|
||||
initial: object[property],
|
||||
update() {
|
||||
var diff = (new Date - this.start) / length;
|
||||
var toUpdate = {
|
||||
originX: "center",
|
||||
originY: "center"
|
||||
};
|
||||
if(diff > 1) {
|
||||
onComplete();
|
||||
animations.splice(animations.indexOf(this), 1);
|
||||
toUpdate[this.property] = this.value
|
||||
this.object.set(toUpdate);
|
||||
return;
|
||||
}
|
||||
toUpdate[this.property] = (this.value - this.initial) * diff + this.initial;
|
||||
this.object.set(toUpdate);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves given object with animation
|
||||
* @param {fabric.Object} source
|
||||
* @param {number} fromX
|
||||
* @param {number} fromY
|
||||
* @param {number} toX
|
||||
* @param {number} toY
|
||||
* @param {Function} done
|
||||
*/
|
||||
function move(source, fromX, fromY, toX, toY, done) {
|
||||
if(fromX !== toX) animate(source, "left", toX * mapdata.px + (mapdata.px / 2), done);
|
||||
if(fromY !== toY) animate(source, "top", toY * mapdata.px + (mapdata.px / 2), done);
|
||||
console.log(arguments);
|
||||
// map[toY][toX] = map[fromY][fromX];
|
||||
// map[fromY][fromX] = undefined;
|
||||
}
|
||||
|
||||
export function resize() {
|
||||
canvas.setWidth(htmlcanvas.parentElement.clientWidth);
|
||||
canvas.setHeight(htmlcanvas.parentElement.clientHeight - 7);
|
||||
}
|
||||
|
||||
var canMove = true;
|
||||
function tryMove(toX, toY) {
|
||||
const player = objects.get("player");
|
||||
// if(toX > mapdata.size.x - 1 || toY > mapdata.size.y - 1 || toX < 0 || toY < 0) return;
|
||||
if(!canMove) return;
|
||||
|
||||
canMove = false;
|
||||
move(player, position.x, position.y, toX, toY, () => canMove = true);
|
||||
position.x = toX;
|
||||
position.y = toY;
|
||||
console.log(position, player.left / mapdata.px, player.top / mapdata.px);
|
||||
}
|
||||
|
||||
keys.addEventListener("keyDown", key => {
|
||||
console.log(key);
|
||||
const { x, y } = position;
|
||||
switch(key) {
|
||||
case "right":
|
||||
tryMove(x + 1, y);
|
||||
break;
|
||||
case "left":
|
||||
tryMove(x - 1, y);
|
||||
break;
|
||||
case "up":
|
||||
tryMove(x, y - 1);
|
||||
break;
|
||||
case "down":
|
||||
tryMove(x, y + 1);
|
||||
break;
|
||||
default:
|
||||
console.error("Unrecognized key", key);
|
||||
}
|
||||
});
|
||||
|
||||
var position = {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
|
||||
var loading = true;
|
||||
export function render(delta) {
|
||||
if(images.areAllLoaded() && loading) {
|
||||
loading = false;
|
||||
|
||||
objects.set("background", new fabric.Image(images.get(mapdata.background), {
|
||||
left: canvas.getWidth() / 2,
|
||||
top: canvas.getHeight() / 2,
|
||||
originX: "center",
|
||||
originY: "center",
|
||||
}));
|
||||
const field = new fabric.Group([], {
|
||||
left: canvas.getWidth() / 2 - (mapdata.offset.x / 2) - (mapdata.size.x * mapdata.px / 2),
|
||||
top: canvas.getHeight() / 2 - (mapdata.offset.y / 2) - (mapdata.size.y * mapdata.px / 2),
|
||||
originX: "left",
|
||||
originY: "top",
|
||||
width: mapdata.size.x * mapdata.px,
|
||||
height: mapdata.size.y * mapdata.px
|
||||
});
|
||||
|
||||
objects.set("field", field);
|
||||
|
||||
for(const y in map) {
|
||||
const pieces = map[y];
|
||||
for(const x in pieces) {
|
||||
const piece = pieces[x];
|
||||
if(!piece || piece.type === "barrier") {
|
||||
objects.set("object-" + x + "-" + y, null);
|
||||
continue;
|
||||
}
|
||||
let type = piece.type;
|
||||
if(type === "angel") type = mapName;
|
||||
/** @type {fabric.Image || Sprite} */
|
||||
let object;
|
||||
if(piece.type === "angel" || piece.type === "spawn" || piece.type === "wind") {
|
||||
object = new Sprite(images.get(type), {
|
||||
spriteWidth: 100,
|
||||
spriteHeight: 100
|
||||
});
|
||||
object.play();
|
||||
if(piece.type === "spawn") {
|
||||
objects.set("player", object);
|
||||
position = { x: parseInt(x), y: parseInt(y) };
|
||||
console.log(position);
|
||||
}
|
||||
} else {
|
||||
object = new fabric.Image(images.get(type));
|
||||
}
|
||||
object.set({
|
||||
originX: "center",
|
||||
originY: "center",
|
||||
left: x * mapdata.px + (mapdata.px / 2),
|
||||
top: y * mapdata.px + (mapdata.px / 2),
|
||||
angle: 90 * (piece.direction || 0)
|
||||
});
|
||||
console.log(object.left / mapdata.px, object.top / mapdata.px);
|
||||
objects.set("object-" + x + "-" + y, object);
|
||||
field.addWithUpdate(object);
|
||||
}
|
||||
}
|
||||
field.set({
|
||||
left: canvas.getWidth() / 2 - (mapdata.offset.x / 2),
|
||||
top: canvas.getHeight() / 2 - (mapdata.offset.y / 2),
|
||||
width: mapdata.size.x * mapdata.px,
|
||||
height: mapdata.size.y * mapdata.px
|
||||
});
|
||||
|
||||
canvas.add(objects.get("background"));
|
||||
canvas.add(field);
|
||||
canvas.remove(objects.get("loadingText"));
|
||||
} else if(loading) return canvas.renderAll();
|
||||
|
||||
var background = objects.get("background");
|
||||
background.set({
|
||||
left: canvas.getWidth() / 2,
|
||||
top: canvas.getHeight() / 2
|
||||
});
|
||||
/** @type {fabric.Group} */
|
||||
var field = objects.get("field");
|
||||
field.set({
|
||||
left: canvas.getWidth() / 2 - (mapdata.offset.x / 2) - (mapdata.size.x * mapdata.px / 2),
|
||||
top: canvas.getHeight() / 2 - (mapdata.offset.y / 2) - (mapdata.size.y * mapdata.px / 2) + mapdata.px / 2,
|
||||
});
|
||||
for(var animation of animations) {
|
||||
animation.update();
|
||||
}
|
||||
canvas.renderAll();
|
||||
}
|
||||
190
src/game/input.js
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
const keybinds = {
|
||||
"right": "ArrowRight",
|
||||
"left": "ArrowLeft",
|
||||
"up": "ArrowUp",
|
||||
"down": "ArrowDown"
|
||||
};
|
||||
|
||||
class KeyHandler {
|
||||
constructor() {
|
||||
this.keys = new Map();
|
||||
this.treshold = 0.3;
|
||||
|
||||
this.axis = new Map([
|
||||
["x", ["moveRight", "moveLeft"]],
|
||||
["y", ["moveUp", "moveDown"]],
|
||||
["rotation", 0]
|
||||
]);
|
||||
|
||||
/** @type {{ type: keyof DocumentEventMap, listener: (this: Document, ev: Event) => any, options?: boolean | EventListenerOptions}} */
|
||||
this.handlers = [];
|
||||
this.addHandlers();
|
||||
this.mounted = false;
|
||||
this.mountHandlers();
|
||||
/** @type {Map<string, Function[]>} */
|
||||
this.listeners = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {K} type
|
||||
* @param {(this: Document, ev: DocumentEventMap[K]) => any} listener
|
||||
* @param {boolean | EventListenerOptions} [options]
|
||||
* @template {keyof DocumentEventMap} K
|
||||
*/
|
||||
addDocumentEventListener(type, listener, options) {
|
||||
this.handlers.push({
|
||||
type,
|
||||
listener,
|
||||
options
|
||||
});
|
||||
if(this.mounted) document.addEventListener(type, listener, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an event listener
|
||||
* @param {string} type
|
||||
* @param {(ev: keyof keybinds) => any} listener
|
||||
*/
|
||||
addEventListener(type, listener) {
|
||||
if(!this.listeners.has(type)) this.listeners.set(type, []);
|
||||
this.listeners.get(type).push(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event
|
||||
* @param {string} type
|
||||
* @param {keyof keybinds} data
|
||||
*/
|
||||
emit(type, data) {
|
||||
if(!this.listeners.has(type)) return;
|
||||
this.listeners.get(type).forEach(listener => listener(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {K} type
|
||||
* @param {(this: Document, ev: DocumentEventMap[K]) => any} listener
|
||||
* @param {boolean | EventListenerOptions} [options]
|
||||
* @template {keyof DocumentEventMap} K
|
||||
*/
|
||||
removeEventListener(type, listener, options) {
|
||||
var handler = this.handlers.findIndex(e => e.type === type && e.listener === listener && e.options === options);
|
||||
if(handler !== -1) this.handlers.splice(handler, 1);
|
||||
document.removeEventListener(type, listener, options);
|
||||
}
|
||||
addHandlers() {
|
||||
this.addDocumentEventListener("keydown", (ev) => {
|
||||
this.pressKeyBind(ev.key);
|
||||
});
|
||||
this.addDocumentEventListener("keyup", (ev) => {
|
||||
this.unpressKeyBind(ev.key);
|
||||
});
|
||||
// this.addDocumentEventListener("mousemove", (ev) => {
|
||||
// var rotation = Math.atan2(ev.pageY - window.innerHeight / 2, ev.pageX - window.innerWidth / 2) * 180 / Math.PI;
|
||||
// rotation += 180;
|
||||
// this.setAxis("rotation", rotation);
|
||||
// });
|
||||
// this.addDocumentEventListener("mousedown", (ev) => {
|
||||
// switch(ev.button) {
|
||||
// case 0:
|
||||
// if(this.pressKeyBind("mouseLeft")) ev.preventDefault();
|
||||
// break;
|
||||
// case 1:
|
||||
// if(this.pressKeyBind("mouseMiddle")) ev.preventDefault();
|
||||
// break;
|
||||
// case 2:
|
||||
// if(this.pressKeyBind("mouseRight")) ev.preventDefault();
|
||||
// break;
|
||||
// case 3:
|
||||
// if(this.pressKeyBind("mouseSpecial1")) ev.preventDefault();
|
||||
// break;
|
||||
// case 4:
|
||||
// if(this.pressKeyBind("mouseSpecial2")) ev.preventDefault();
|
||||
// break;
|
||||
// }
|
||||
// });
|
||||
// this.addDocumentEventListener("mouseup", (ev) => {
|
||||
// switch(ev.button) {
|
||||
// case 0:
|
||||
// if(this.unpressKeyBind("mouseLeft")) ev.preventDefault();
|
||||
// break;
|
||||
// case 1:
|
||||
// if(this.unpressKeyBind("mouseMiddle")) ev.preventDefault();
|
||||
// break;
|
||||
// case 2:
|
||||
// if(this.unpressKeyBind("mouseRight")) ev.preventDefault();
|
||||
// break;
|
||||
// case 3:
|
||||
// if(this.unpressKeyBind("mouseSpecial1")) ev.preventDefault();
|
||||
// break;
|
||||
// case 4:
|
||||
// if(this.unpressKeyBind("mouseSpecial2")) ev.preventDefault();
|
||||
// break;
|
||||
// }
|
||||
// });
|
||||
}
|
||||
mountHandlers() {
|
||||
for(var { type, listener, options } of this.handlers) {
|
||||
document.addEventListener(type, listener, options);
|
||||
}
|
||||
this.mounted = true;
|
||||
}
|
||||
unmountHandlers() {
|
||||
for(var { type, listener, options } of this.handlers) {
|
||||
document.removeEventListener(type, listener, options);
|
||||
}
|
||||
this.mounted = false;
|
||||
}
|
||||
pressKeyBind(key) {
|
||||
var kb = this.getKeyBind(key);
|
||||
if(!kb) return null;
|
||||
this.emit("keyDown", kb);
|
||||
return this.pressKey(kb);
|
||||
}
|
||||
unpressKeyBind(key) {
|
||||
var kb = this.getKeyBind(key);
|
||||
if(!kb) return null;
|
||||
this.emit("keyUp", kb);
|
||||
return this.unpressKey(kb);
|
||||
}
|
||||
setKeyBindAxis(key, value) {
|
||||
var kb = this.getKeyBind(key);
|
||||
if(!kb) return null;
|
||||
return this.setAxis(kb, value);
|
||||
}
|
||||
getKeyBind(key) {
|
||||
var index = Object.values(keybinds).indexOf(key);
|
||||
if(index === -1) return null;
|
||||
return Object.keys(keybinds)[index];
|
||||
}
|
||||
|
||||
isKeyPressed(key) {
|
||||
return this.keys.has(key) && this.keys.get(key) > this.treshold;
|
||||
}
|
||||
|
||||
getAxis(key) {
|
||||
if(!this.axis.has(key)) return;
|
||||
var val = this.axis.get(key);
|
||||
if(typeof val === "number") return val;
|
||||
if(typeof val === "string") return this.keys.get(val);
|
||||
var res = 0;
|
||||
res += this.isKeyPressed(val[0]) ? 1 : 0;
|
||||
res -= this.isKeyPressed(val[1]) ? 1 : 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
pressKey(key) {
|
||||
return this.keys.set(key, 1);
|
||||
}
|
||||
|
||||
unpressKey(key) {
|
||||
return this.keys.set(key, 0);
|
||||
}
|
||||
|
||||
setAxis(key, val) {
|
||||
return this.axis.set(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
var keys = new KeyHandler;
|
||||
|
||||
export { keys };
|
||||
47
src/game/maps.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
function wind(direction) {
|
||||
return {
|
||||
type: "wind",
|
||||
direction
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {
|
||||
[key: string]: {
|
||||
background: string,
|
||||
sprite: string,
|
||||
offset: { x: number, y: nunber },
|
||||
size: { x: number, y: number },
|
||||
px: number,
|
||||
map: {
|
||||
x: number,
|
||||
y: number,
|
||||
type: string
|
||||
}[]
|
||||
}
|
||||
}
|
||||
*/
|
||||
export const maps = {
|
||||
uriel: {
|
||||
background: "level1",
|
||||
sprite: "/sprite/uriel.gif",
|
||||
offset: { // map offset for alignment
|
||||
x: 90,
|
||||
y: 0
|
||||
},
|
||||
size: { // map size (per block)
|
||||
x: 5,
|
||||
y: 7
|
||||
},
|
||||
px: 100, // block size
|
||||
map: [
|
||||
["barrier", "barrier", "angel" , "barrier", "barrier"],
|
||||
["barrier", "barrier", null , null , "barrier"],
|
||||
[null , null , wind(1) , null , null ],
|
||||
[null , "lyre" , wind(1) , null , null ],
|
||||
["lyre" , null , "cloud" , null , null ],
|
||||
[null , null , null , "lyre" , null ],
|
||||
["spawn" , null , null , null , null ]
|
||||
]
|
||||
}
|
||||
};
|
||||
95
src/game/sprite.js
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { fabric } from "fabric";
|
||||
|
||||
/**
|
||||
* @typedef ISprite
|
||||
* @property {"sprite"} type
|
||||
* @property {number} spriteWidth
|
||||
* @property {number} spriteHeight
|
||||
* @property {number} spriteIndex
|
||||
* @property {number} frameTime
|
||||
*
|
||||
* @property {(element: HTMLImageElement, options: any) => Sprite} constructor
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {ISprite}
|
||||
*/
|
||||
const Sprite = fabric.util.createClass(fabric.Image, {
|
||||
type: 'sprite',
|
||||
|
||||
spriteWidth: 50,
|
||||
spriteHeight: 72,
|
||||
spriteIndex: 0,
|
||||
frameTime: 100,
|
||||
|
||||
initialize: function (element, options) {
|
||||
options || (options = {});
|
||||
|
||||
this.spriteWidth = options.spriteWidth;
|
||||
this.spriteHeight = options.spriteHeight;
|
||||
this.frameTime = options.frameTime || 100;
|
||||
options.width = this.spriteWidth;
|
||||
options.height = this.spriteHeight;
|
||||
|
||||
this.callSuper('initialize', element, options);
|
||||
|
||||
this.createTmpCanvas();
|
||||
this.createSpriteImages();
|
||||
},
|
||||
|
||||
createTmpCanvas: function () {
|
||||
this.tmpCanvasEl = fabric.util.createCanvasElement();
|
||||
this.tmpCanvasEl.width = this.spriteWidth || this.width;
|
||||
this.tmpCanvasEl.height = this.spriteHeight || this.height;
|
||||
},
|
||||
|
||||
createSpriteImages: function () {
|
||||
this.spriteImages = [];
|
||||
|
||||
var steps = this._element.width / this.spriteWidth;
|
||||
for (var i = 0; i < steps; i++) {
|
||||
this.createSpriteImage(i);
|
||||
}
|
||||
},
|
||||
|
||||
createSpriteImage: function (i) {
|
||||
var tmpCtx = this.tmpCanvasEl.getContext('2d');
|
||||
tmpCtx.clearRect(0, 0, this.tmpCanvasEl.width, this.tmpCanvasEl.height);
|
||||
tmpCtx.drawImage(this._element, -i * this.spriteWidth, 0);
|
||||
|
||||
var dataURL = this.tmpCanvasEl.toDataURL('image/png');
|
||||
var tmpImg = fabric.util.createImage();
|
||||
|
||||
tmpImg.src = dataURL;
|
||||
|
||||
this.spriteImages.push(tmpImg);
|
||||
},
|
||||
|
||||
_render: function (ctx) {
|
||||
ctx.drawImage(
|
||||
this.spriteImages[this.spriteIndex],
|
||||
-this.width / 2,
|
||||
-this.height / 2
|
||||
);
|
||||
},
|
||||
|
||||
play: function () {
|
||||
var _this = this;
|
||||
this.animInterval = setInterval(function () {
|
||||
_this.onPlay && _this.onPlay();
|
||||
_this.set({
|
||||
dirty: true
|
||||
});
|
||||
_this.spriteIndex++;
|
||||
if (_this.spriteIndex === _this.spriteImages.length) {
|
||||
_this.spriteIndex = 0;
|
||||
}
|
||||
}, this.frameTime);
|
||||
},
|
||||
|
||||
stop: function () {
|
||||
clearInterval(this.animInterval);
|
||||
}
|
||||
});
|
||||
|
||||
export { Sprite };
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
import GameOverlay from "./gameOverlay.svelte";
|
||||
import { dialog } from "../stores/dialog.js";
|
||||
import { characters } from "../stores/characters.js";
|
||||
import { onMount } from "svelte";
|
||||
import { setCanvas, render, setMap, resize } from "../game";
|
||||
|
||||
export var current;
|
||||
|
||||
|
|
@ -28,7 +30,25 @@
|
|||
}
|
||||
|
||||
var steps = 11;
|
||||
var canvas;
|
||||
|
||||
onMount(() => {
|
||||
setMap(dialog[current].map);
|
||||
setCanvas(canvas);
|
||||
resize();
|
||||
function update(delta) {
|
||||
render(delta);
|
||||
frame = requestAnimationFrame(update);
|
||||
}
|
||||
var frame = requestAnimationFrame(update);
|
||||
return () => cancelAnimationFrame(frame);
|
||||
});
|
||||
|
||||
$: setMap(dialog[current].map);
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={resize} />
|
||||
|
||||
<GameOverlay {steps} chapter={toRoman(characterIndex + 1)} />
|
||||
<GameOverlay {steps} chapter={toRoman(characterIndex + 1)} />
|
||||
|
||||
<canvas bind:this={canvas} />
|
||||
|
|
@ -4,6 +4,7 @@ export const dialog = [{
|
|||
character: "Uriel",
|
||||
pose: "side_normal",
|
||||
text: "Ummm... I don't want to be rude so just leave or go to the main gate.",
|
||||
map: "uriel",
|
||||
buttons: [{
|
||||
text: "Step aside, I got heaven to conquer and angels to take.",
|
||||
next: "uriel_restart"
|
||||
|
|
|
|||
3
src/stores/gameActive.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { writable } from "svelte/store";
|
||||
|
||||
export const gameActive = writable(false);
|
||||