Compare commits

...

19 Commits

40 changed files with 16243 additions and 601 deletions

View File

@ -10,7 +10,7 @@
"name": "Moon",
"tileset": "moon.jpg",
"tiles": 2,
"backgroundColor": "black",
"backgroundImage": null
"backgroundColor": "#000000",
"backgroundImage": "background_earth.jpg"
}
]
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
graphics/tileset/nature.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -17,9 +17,10 @@
}
</style>
<link rel="shortcut icon" type="image/png" href="favicon.png">
<script type="module" src="js/module.js"></script>
<link rel="stylesheet" type="text/css" href="tilorswift/style.css">
<script type="module" src="js/module.js" defer></script>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
</html>
</html>

View File

@ -4,7 +4,8 @@ export default class FileLoader
{
this.filename = filename;
this.content = '';
this.loadContent();
this.onLoad = () => {};
}
getContent()
@ -19,10 +20,11 @@ export default class FileLoader
request.onreadystatechange = () => {
if (request.status === 200 && request.readyState === 4) {
this.content = request.responseText;
this.onLoad(this.content);
}
};
request.open('GET', this.filename, false);
request.send();
}
}
}

View File

@ -1,84 +0,0 @@
import FrameRateMeasuredEvent from "./events/FrameRateMeasuredEvent.js";
export default class FrameRateMeasurer
{
constructor(rounds = 30)
{
this.rounds = rounds;
this.round = 0;
this.lastTimestamp = undefined;
this.frameDurations = [];
window.requestAnimationFrame(
(timestamp) => {
this.measureFrameRate(timestamp);
}
);
}
measureFrameRate(timestamp)
{
if (this.lastTimestamp !== undefined) {
this.round++;
this.frameDurations.push(timestamp - this.lastTimestamp);
this.lastTimestamp = timestamp;
if (this.round < this.rounds) {
window.requestAnimationFrame(
(timestamp) => {
this.measureFrameRate(timestamp);
}
);
} else {
window.dispatchEvent(new FrameRateMeasuredEvent(this.getAverageFrameRate()));
}
} else {
this.round = 0;
this.lastTimestamp = timestamp;
window.requestAnimationFrame(
(timestamp) => {
this.measureFrameRate(timestamp);
}
);
}
this.lastTimestamp = timestamp
}
getAverageFrameRate()
{
let frameDurationSum = 0;
this.frameDurations.forEach(
(duration) => {
frameDurationSum += duration;
}
);
let durationAverage = frameDurationSum / this.frameDurations.length;
return 1000 / durationAverage;
}
getFrameRate()
{
const DEFAULT_FRAME_RATES = [30, 60, 120];
let averageFrameRate = this.getAverageFrameRate();
let closestDistance = {frameRate: 0, distance: 99999};
DEFAULT_FRAME_RATES.forEach(
(frameRate) => {
let distance = Math.abs(frameRate - averageFrameRate);
if (closestDistance.distance > distance) {
closestDistance.frameRate = frameRate;
closestDistance.distance = distance;
}
}
);
return closestDistance.frameRate;
}
}

226
js/Game.js Normal file
View File

@ -0,0 +1,226 @@
import MrCroc from "./MrCroc.js";
import Gisela from "./Gisela.js";
import RetroArchitecture from "./retro/RetroArchitecture.js";
import Camera from "./Camera.js";
import TextMessageMrCroc from "./ui/TextMessageMrCroc.js";
import TextMessageGisela from "./ui/TextMessageGisela.js";
import UserInterface from "./ui/UserInterface.js";
import Key from "./Key.js";
import Setting from "./Setting.js";
import {EventBus} from "./events/EventBus.js";
export class Game
{
static GAME_SPEED = 1
constructor(level) {
this.level = level;
this.fps = 60;
this.frameDuration = 1000.0 / this.fps;
this.lastRendered = undefined;
this.lastTimestamp = undefined;
this.canvas = document.getElementById('canvas');
this.context = this.canvas.getContext('2d');
this.mrCroc = new MrCroc();
this.gisela = new Gisela();
this.architecture = RetroArchitecture.createFromData(this.level);
const cameraPosition = this.architecture.getStartPosition();
this.camera = new Camera(cameraPosition.x, cameraPosition.y);
this.gameFinished = false;
this.hasPlayerLeftArchitecture = false;
this.textBoxGameStart = new TextMessageMrCroc('Mr. Croc: "Where is Gisela? I have to find her!"', this.context);
this.textBoxGameFinished = new TextMessageGisela(
'Gisela: "Thanks for showing up, Mr. Croc, but I\'m not in danger."',
this.context
);
this.userInterface = new UserInterface();
this.isPaused = true;
this.KeyLeft = new Key('ArrowLeft');
this.KeyRight = new Key('ArrowRight');
this.KeyJump = new Key('Space');
this.KeyLoad = new Key('KeyL');
}
render(timestamp)
{
if (timestamp - this.lastRendered < this.frameDuration) {
return;
}
if (this.gisela.currentAnimation !== 'LOOK_LEFT' && this.mrCroc.position.x < this.gisela.position.x) {
this.gisela.currentAnimation = 'LOOK_LEFT';
} else if (this.gisela.currentAnimation !== 'LOOK_RIGHT' && this.mrCroc.position.x >= this.gisela.position.x) {
this.gisela.currentAnimation = 'LOOK_RIGHT';
}
this.context.clearRect(0, 0, window.innerWidth, window.innerHeight);
this.architecture.draw(this.context, this.camera);
this.mrCroc.draw(this.context, this.camera);
this.gisela.draw(this.context, this.camera);
this.userInterface.draw(this.context);
this.lastRendered = timestamp;
}
canBeFinished()
{
return (
!this.gameFinished &&
this.mrCroc.isJumping === false &&
this.architecture.isMovableInsideTargetPosition(this.mrCroc)
);
}
handlePhysics(delta, timestamp)
{
const ceilingHeight = Math.max(
this.architecture.getCeilingHeight(this.mrCroc.getPositionHeadLeft()),
this.architecture.getCeilingHeight(this.mrCroc.getPositionHeadRight()),
);
const groundHeight = Math.min(
this.architecture.getGroundHeight(this.mrCroc.getPositionFootLeft()),
this.architecture.getGroundHeight(this.mrCroc.getPositionFootRight())
);
/* Handle falling */
this.mrCroc.position.y += this.mrCroc.fallSpeed;
this.mrCroc.fallSpeed += this.level.gravity * delta;
/* Handle ground collision */
if (this.mrCroc.position.y > groundHeight && this.mrCroc.fallSpeed > 0) {
this.mrCroc.position.y = groundHeight;
this.mrCroc.fallSpeed = 0;
}
/* Handle ceiling collision */
if (this.mrCroc.position.y - this.mrCroc.getHeight() <= ceilingHeight) {
this.mrCroc.fallSpeed = 0;
this.mrCroc.position.y = ceilingHeight + this.mrCroc.getHeight() + 1;
}
this.handlePlayerMovement(delta, timestamp, groundHeight);
}
updateCamera()
{
this.camera.focusPosition(
this.mrCroc.position.x - this.mrCroc.getWidth() * 0.5,
this.mrCroc.position.y - this.mrCroc.getHeight() * 0.5,
20
);
this.camera.lockCameraIntoBorders();
}
handlePlayerMovement(delta, timestamp, groundHeight)
{
/* Jumping */
if (!this.mrCroc.isJumping && this.mrCroc.fallSpeed === 0 && this.mrCroc.position.y === groundHeight && this.KeyJump.isPressed()) {
this.mrCroc.jump();
} else if (!this.KeyJump.isPressed()) {
this.mrCroc.isJumping = false;
}
/* Movement left and right */
if (!this.hasPlayerLeftArchitecture && this.KeyLeft.isPressed()) {
const lastWallLeft = Math.min(
this.architecture.getWallLeft(this.mrCroc.getPositionHeadLeft()),
this.architecture.getWallLeft(this.mrCroc.getPositionFootLeft())
);
this.mrCroc.moveLeft(timestamp, delta);
if (this.mrCroc.position.x <= lastWallLeft + this.mrCroc.getWidth() * 0.5) {
this.mrCroc.position.x = lastWallLeft + this.mrCroc.getWidth() * 0.5 + 1;
}
} else if (!this.hasPlayerLeftArchitecture && this.KeyRight.isPressed()) {
const lastWallRight = Math.max(
this.architecture.getWallRight(this.mrCroc.getPositionHeadRight()),
this.architecture.getWallRight(this.mrCroc.getPositionFootRight())
);
this.mrCroc.moveRight(timestamp, delta);
if (this.mrCroc.position.x >= lastWallRight - this.mrCroc.getWidth() * 0.5) {
this.mrCroc.position.x = lastWallRight - this.mrCroc.getWidth() * 0.5 - 1;
}
}
if (!this.hasPlayerLeftArchitecture && !this.architecture.isInsideArchitecture(this.mrCroc.position)) {
this.hasPlayerLeftArchitecture = true;
setTimeout(
() => {
this.architecture.setMovableToStartPosition(this.mrCroc);
this.hasPlayerLeftArchitecture = false;
}, 2000
);
}
}
finish()
{
this.gameFinished = true;
this.KeyLeft.pressed = false;
this.KeyRight.pressed = false;
this.KeyJump.pressed = false;
this.lastTimestamp = undefined;
this.lastRendered = undefined;
this.textBoxGameFinished.updateLines(window.innerWidth - 40, this.context);
this.textBoxGameFinished.animate(75);
this.userInterface.addTextBox(this.textBoxGameFinished);
}
init(loopFunction) {
if (this.isChromeBrowser()) {
this.canvas.width = window.innerWidth - 1;
this.canvas.height = window.innerHeight - 1;
}
this.canvas.style.backgroundAttachment = 'fixed';
this.canvas.style.backgroundSize = 'cover';
this.canvas.style.backgroundPosition = 'center center';
if (this.level.getBackgroundImage() !== undefined) {
this.canvas.style.backgroundImage = "url("+ Setting.GRAPHICS_LOCATION + this.level.getBackgroundImage() + ")";
} else {
this.canvas.style.backgroundImage = 'none';
}
this.canvas.style.backgroundColor = this.level.getBackgroundColor();
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
EventBus.addEventListener(
'resize',
function () {
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
}
);
this.textBoxGameStart.animate(75, 1000);
this.textBoxGameStart.show(1000);
this.textBoxGameStart.hide(10000);
this.userInterface.addTextBox(this.textBoxGameStart);
this.camera.borderRight = this.architecture.columns * this.architecture.tileWidth;
this.camera.borderBottom = this.architecture.rows * this.architecture.tileHeight;
this.architecture.setMovableToStartPosition(this.mrCroc);
this.architecture.setMovableToTargetPosition(this.gisela);
this.isPaused = false;
window.requestAnimationFrame(loopFunction);
}
isChromeBrowser()
{
return navigator.userAgent.match('Chrome') !== null;
}
}

View File

@ -5,7 +5,10 @@ let GraphicSet = [
tiles: 8,
scale: 3,
backgroundColor: '#6096ff',
backgroundImage: null
backgroundImage: null,
tilePreview: 5,
primaryTiles: 8,
gravity: 2,
},
{
name: 'Moon',
@ -13,8 +16,33 @@ let GraphicSet = [
tiles: 2,
scale: 3,
backgroundColor: 'black',
backgroundImage: 'background_earth.jpg'
}
backgroundImage: 'background_earth.jpg',
tilePreview: 1,
primaryTiles: 2,
gravity: 0.5,
},
{
name: 'Death Star',
tileset: 'deathstar.png',
tiles: 96,
scale: 1,
backgroundColor: '#171721',
backgroundImage: null,
tilePreview: 3,
primaryTiles: 6,
gravity: 2,
},
{
name: 'Nature 2.0',
tileset: 'nature.png',
tiles: 48,
scale: 3,
backgroundColor: '#6096ff',
backgroundImage: null,
tilePreview: 46,
primaryTiles: 3,
gravity: 2,
}
];
export default GraphicSet;
export default GraphicSet;

View File

@ -3,15 +3,25 @@ export default class ImageLoader
images = [];
numberImagesLoaded = 0;
constructor() {
this.onLoad = () => {}
}
update()
{
this.numberImagesLoaded++;
if (this.numberImagesLoaded === this.images.length) {
window.dispatchEvent(new Event('imagesloaded'));
this.onLoad();
}
}
isComplete()
{
return this.numberImagesLoaded === this.images.length;
}
getCurrentProgress()
{
return this.numberImagesLoaded / this.images.length;
@ -19,14 +29,19 @@ export default class ImageLoader
addImage(imagePath)
{
let image = new Image();
image.src = imagePath;
image.addEventListener(
'load', () => {
this.update();
}
);
this.images.push(image);
this.images.push(imagePath);
}
}
load()
{
for (const imagePath of this.images) {
let image = new Image();
image.src = imagePath;
image.addEventListener(
'load', () => {
this.update();
}
);
}
}
}

View File

@ -1,11 +1,12 @@
import {EventBus} from "./events/EventBus.js";
export default class Key
{
constructor(name)
{
this.name = name;
this.pressed = false;
window.addEventListener(
EventBus.addEventListener(
'keydown',
(event) => {
if (event.code === this.name) {
@ -13,8 +14,7 @@ export default class Key
}
}
);
window.addEventListener(
EventBus.addEventListener(
'keyup',
(event) => {
if (event.code === this.name) {

View File

@ -74,14 +74,27 @@ export default class Level
this.gravity = gravity;
}
static createFromFile(filename)
static createFromFile(filename, callback = () => {})
{
let loader = new FileLoader(filename);
let levelData = JSON.parse(loader.getContent());
let terrain = Terrain.createFromJson(levelData);
let level = new Level(terrain);
level.setGravity(levelData.gravity);
loader.onLoad = (data) => {
const json = JSON.parse(data);
const level = new Level(Terrain.createFromJson(json));
level.setGravity(json.gravity);
callback(level);
}
loader.loadContent();
}
static createFromJson(json)
{
const data = JSON.parse(json);
const terrain = Terrain.createFromJson(data);
const level = new Level(terrain);
level.setGravity(data.gravity);
return level;
}
}
}

View File

@ -23,4 +23,4 @@ export default class MrCroc extends Movable
this.playAnimation('WALK_LEFT', timestamp);
super.moveLeft(delta);
}
}
}

View File

@ -44,15 +44,15 @@ export default class UrlParam
getInt(name)
{
let value = parseInt(this.get(name));
const value = parseInt(this.get(name));
return isNaN(value) ? undefined : value;
return isNaN(value) ? 0 : value;
}
getFloat(name)
{
let value = parseFloat(this.get(name));
const value = parseFloat(this.get(name));
return isNaN(value) ? undefined : value;
return isNaN(value) ? 0.0 : value;
}
}
}

25
js/events/EventBus.js Normal file
View File

@ -0,0 +1,25 @@
export class EventBus
{
static listeners = []
static addEventListener(eventName, callback)
{
EventBus.listeners.push({eventName, callback});
window.addEventListener(eventName, callback, false);
}
static dispatchEvent(event)
{
window.dispatchEvent(event);
}
static clear()
{
for (const listener of EventBus.listeners) {
window.removeEventListener(listener.eventName, listener.callback, false);
}
EventBus.listeners = [];
}
}

View File

@ -1,9 +0,0 @@
import InterfaceEvent from "./InterfaceEvent.js";
export default class FrameRateMeasuredEvent extends Event
{
constructor(frameRate) {
super(InterfaceEvent.FRAME_RATE_MEASURED);
this.frameRate = frameRate;
}
}

View File

@ -1,138 +1,81 @@
'use strict';
import Key from "./Key.js";
import MrCroc from "./MrCroc.js";
import RetroArchitecture from "./retro/RetroArchitecture.js";
import Camera from "./Camera.js";
import Gisela from "./Gisela.js";
import Setting from "./Setting.js";
import FrameRateMeasurer from "./FrameRateMeasurer.js";
import GraphicSet from "./GraphicSet.js";
import ImageLoader from "./ImageLoader.js";
import Level from "./Level.js";
import InterfaceEvent from "./events/InterfaceEvent.js";
import UrlParam from "./UrlParam.js";
import UserInterface from "./ui/UserInterface.js";
import TextMessageGisela from "./ui/TextMessageGisela.js";
import TextMessageMrCroc from "./ui/TextMessageMrCroc.js";
import {LoadLevelDialog} from "./ui/LoadLevelDialog.js";
import {EventBus} from "./events/EventBus.js";
import {Game} from "./Game.js";
function MainLoop(timestamp)
function mainLoop(timestamp)
{
if (lastRendered === undefined && lastTimestamp === undefined) {
lastRendered = timestamp;
lastTimestamp = timestamp;
if (game.isPaused) {
return;
}
let delta = (timestamp - lastTimestamp) / (10 / GAME_SPEED);
let ceilingHeight = Math.max(
architecture.getCeilingHeight(mrCroc.getPositionHeadLeft()),
architecture.getCeilingHeight(mrCroc.getPositionHeadRight()),
);
let groundHeight = Math.min(
architecture.getGroundHeight(mrCroc.getPositionFootLeft()),
architecture.getGroundHeight(mrCroc.getPositionFootRight())
);
/* Handle falling */
mrCroc.position.y += mrCroc.fallSpeed;
mrCroc.fallSpeed += GRAVITY * delta;
/* Handle ground collision */
if (mrCroc.position.y > groundHeight && mrCroc.fallSpeed > 0) {
mrCroc.position.y = groundHeight;
mrCroc.fallSpeed = 0;
if (game.lastRendered === undefined && game.lastTimestamp === undefined) {
game.lastRendered = timestamp;
game.lastTimestamp = timestamp;
}
/* Handle ceiling collision */
if (mrCroc.position.y - mrCroc.getHeight() <= ceilingHeight) {
mrCroc.fallSpeed = 0;
mrCroc.position.y = ceilingHeight + mrCroc.getHeight() + 1;
let delta = (timestamp - game.lastTimestamp) / (10 / Game.GAME_SPEED);
game.handlePhysics(delta, timestamp);
game.updateCamera();
game.render(timestamp);
game.lastTimestamp = timestamp;
if (game.canBeFinished()) {
game.finish();
}
/* Handle jumping */
if (!mrCroc.isJumping && mrCroc.fallSpeed === 0 && mrCroc.position.y === groundHeight && KeyJump.isPressed()) {
mrCroc.jump();
} else if (!KeyJump.isPressed()) {
mrCroc.isJumping = false;
}
/* Movement left and right */
if (!hasPlayerLeftArchitecture && KeyLeft.isPressed()) {
let lastWallLeft = Math.min(
architecture.getWallLeft(mrCroc.getPositionHeadLeft()),
architecture.getWallLeft(mrCroc.getPositionFootLeft())
);
mrCroc.moveLeft(timestamp, delta);
if (mrCroc.position.x <= lastWallLeft + mrCroc.getWidth() * 0.5) {
mrCroc.position.x = lastWallLeft + mrCroc.getWidth() * 0.5 + 1;
if (game.KeyLoad.isPressed()) {
const dialog = new LoadLevelDialog();
dialog.onClose = () => {
dialog.close();
game.isPaused = false;
window.requestAnimationFrame(mainLoop);
}
dialog.onLoad = (data) => {
EventBus.clear();
loadLevel(Level.createFromJson(data));
}
} else if (!hasPlayerLeftArchitecture && KeyRight.isPressed()) {
let lastWallRight = Math.max(
architecture.getWallRight(mrCroc.getPositionHeadRight()),
architecture.getWallRight(mrCroc.getPositionFootRight())
);
mrCroc.moveRight(timestamp, delta);
game.isPaused = true;
}
if (mrCroc.position.x >= lastWallRight - mrCroc.getWidth() * 0.5) {
mrCroc.position.x = lastWallRight - mrCroc.getWidth() * 0.5 - 1;
window.requestAnimationFrame(mainLoop);
}
function loadLevel(level)
{
const loader = new ImageLoader();
loader.addImage(Setting.GRAPHICS_LOCATION + 'mr-croc-walk-right.png');
loader.addImage(Setting.GRAPHICS_LOCATION + 'mr-croc-walk-left.png');
loader.addImage(Setting.GRAPHICS_LOCATION + 'gisela-right.png');
loader.addImage(Setting.GRAPHICS_LOCATION + 'gisela-left.png');
loader.addImage(Setting.GRAPHICS_LOCATION + 'gisela-left.png');
for (const graphicSet of GraphicSet) {
loader.addImage(Setting.TILESET_LOCATION + graphicSet.tileset);
if (graphicSet.backgroundImage !== null) {
loader.addImage(Setting.GRAPHICS_LOCATION + graphicSet.backgroundImage);
}
}
if (!hasPlayerLeftArchitecture && !architecture.isInsideArchitecture(mrCroc.position)) {
hasPlayerLeftArchitecture = true;
loader.load();
setTimeout(
function () {
architecture.setMovableToStartPosition(mrCroc);
hasPlayerLeftArchitecture = false;
}, 2000
);
}
camera.focusPosition(
mrCroc.position.x - mrCroc.getWidth() * 0.5,
mrCroc.position.y - mrCroc.getHeight() * 0.5,
20
);
camera.lockCameraIntoBorders();
/* Drawing */
if (timestamp - lastRendered >= frameDuration) {
if (gisela.currentAnimation !== 'LOOK_LEFT' && mrCroc.position.x < gisela.position.x) {
gisela.currentAnimation = 'LOOK_LEFT';
} else if (gisela.currentAnimation !== 'LOOK_RIGHT' && mrCroc.position.x >= gisela.position.x) {
gisela.currentAnimation = 'LOOK_RIGHT';
EventBus.addEventListener(
'imagesloaded',
() => {
game = new Game(level);
game.init(mainLoop);
}
context.clearRect(0, 0, window.innerWidth, window.innerHeight);
architecture.draw(context, camera);
mrCroc.draw(context, camera);
gisela.draw(context, camera);
userInterface.draw(context);
lastRendered = timestamp;
}
lastTimestamp = timestamp;
if (!gameFinished && mrCroc.isJumping === false && architecture.isMovableInsideTargetPosition(mrCroc)) {
gameFinished = true;
KeyLeft.pressed = false;
KeyRight.pressed = false;
KeyJump.pressed = false;
lastTimestamp = undefined;
lastRendered = undefined;
textBoxGameFinished.updateLines(window.innerWidth - 40, context);
textBoxGameFinished.animate(75);
userInterface.addTextBox(textBoxGameFinished);
}
window.requestAnimationFrame(MainLoop);
);
}
const LEVEL = [
@ -140,100 +83,18 @@ const LEVEL = [
'moon.json',
'moonbase.json',
'terrain8.json',
'darius.json',
'imperial-prison.json',
];
let urlGetter = new UrlParam();
let game;
let levelIndex = urlGetter.getInt('level');
const urlGetter = new UrlParam();
const levelIndex = urlGetter.getInt('level');
if (levelIndex === undefined || levelIndex < 0 || levelIndex >= LEVEL.length) {
levelIndex = 0;
}
let level = Level.createFromFile(Setting.LEVELS_LOCATION + LEVEL[levelIndex]);
const GAME_SPEED = 1;
const GRAVITY = level.gravity;
let fps;
let frameDuration;
let lastRendered = undefined;
let lastTimestamp = undefined;
let context;
let mrCroc, gisela, architecture;
let camera = new Camera();
let gameFinished = false;
let hasPlayerLeftArchitecture = false;
let textBoxGameStart;
let textBoxGameFinished;
let userInterface = new UserInterface();
let KeyLeft = new Key('ArrowLeft');
let KeyRight = new Key('ArrowRight');
let KeyJump = new Key('Space');
let loader = new ImageLoader();
loader.addImage(Setting.GRAPHICS_LOCATION + 'mr-croc-walk-right.png');
loader.addImage(Setting.GRAPHICS_LOCATION + 'mr-croc-walk-left.png');
loader.addImage(Setting.TILESET_LOCATION + GraphicSet[level.getTilesetId()].tileset);
loader.addImage(Setting.GRAPHICS_LOCATION + 'gisela-right.png');
loader.addImage(Setting.GRAPHICS_LOCATION + 'gisela-left.png');
new FrameRateMeasurer();
window.addEventListener(
'imagesloaded',
() => {
let canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.backgroundAttachment = 'fixed';
canvas.style.backgroundSize = 'cover';
canvas.style.backgroundPosition = 'center center';
if (GraphicSet[level.getTilesetId()].backgroundImage !== null) {
canvas.style.backgroundImage = 'url("' + Setting.GRAPHICS_LOCATION + GraphicSet[level.getTilesetId()].backgroundImage +'")';
}
canvas.style.backgroundColor = level.getBackgroundColor();
window.addEventListener(
'resize',
function () {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
);
context = canvas.getContext('2d');
textBoxGameFinished = new TextMessageGisela(
'Gisela: "Thanks for showing up, Mr. Croc, but I\'m not in danger."',
context
);
textBoxGameStart = new TextMessageMrCroc('Mr. Croc: "Where is Gisela? I have to find her!"', context);
textBoxGameStart.animate(75, 1000);
textBoxGameStart.show(1000);
textBoxGameStart.hide(10000);
userInterface.addTextBox(textBoxGameStart);
architecture = RetroArchitecture.createFromData(level);
camera.borderRight = architecture.columns * architecture.tileWidth;
camera.borderBottom = architecture.rows * architecture.tileHeight;
mrCroc = new MrCroc();
architecture.setMovableToStartPosition(mrCroc);
gisela = new Gisela();
architecture.setMovableToTargetPosition(gisela);
window.addEventListener(
InterfaceEvent.FRAME_RATE_MEASURED,
(event) => {
fps = event.frameRate;
frameDuration = 1000 / fps;
window.requestAnimationFrame(MainLoop);
}
);
Level.createFromFile(
Setting.LEVELS_LOCATION + LEVEL[levelIndex % LEVEL.length],
(level) => {
loadLevel(level);
}
);
);

View File

@ -204,10 +204,10 @@ export default class RetroArchitecture
draw(context, camera = new Camera())
{
let viewX = parseInt(Math.floor(Math.max(0, camera.position.x) / this.tileWidth));
let viewWidth = parseInt(Math.min(Math.ceil(camera.width), this.columns));
let viewY = parseInt(Math.floor(Math.max(0, camera.position.y)) / this.tileHeight);
let viewHeight = parseInt(Math.min(Math.ceil(camera.height), this.rows));
const viewX = parseInt(Math.floor(Math.max(0, camera.position.x) / this.tileWidth));
const viewWidth = parseInt(Math.min(Math.ceil(camera.width), this.columns));
const viewY = parseInt(Math.floor(Math.max(0, camera.position.y)) / this.tileHeight);
const viewHeight = parseInt(Math.min(Math.ceil(camera.height), this.rows));
for (let y = viewY; y < viewHeight; y++) {
for (let x = viewX; x < viewWidth; x++) {
@ -224,17 +224,22 @@ export default class RetroArchitecture
{
context.drawImage(
this.tilesetSprite.canvas,
field.tile.index * this.tileWidth,
(field.tile.index % this.tiles) * this.tileWidth,
0,
this.tileWidth,
this.tileHeight,
x * this.tileWidth - camera.position.x,
y * this.tileHeight - camera.position.y,
x * this.tileWidth - camera.position.x - 1,
y * this.tileHeight - camera.position.y - 1,
this.tileWidth + 1,
this.tileHeight + 1
);
}
getStartPosition()
{
return new GeometryPoint(this.startX * this.tileWidth, this.startY * this.tileHeight);
}
static createFromData(level)
{
let graphicSet = GraphicSet[level.getTilesetId()];
@ -276,4 +281,4 @@ export default class RetroArchitecture
return architecture;
}
}
}

View File

@ -82,7 +82,7 @@ export default class RetroSprite
canvasTemp.width = this.image.width * this.scale;
canvasTemp.height = this.image.height * this.scale;
let contextTemp = canvasTemp.getContext('2d');
let contextTemp = canvasTemp.getContext('2d', {willReadFrequently: true});
contextTemp.drawImage(this.image, 0, 0);
this.canvas.width = this.image.width * this.scale;
@ -99,4 +99,4 @@ export default class RetroSprite
}
}
}
}
}

44
js/ui/LoadLevelDialog.js Normal file
View File

@ -0,0 +1,44 @@
import Dialog from "../../tilorswift/js/dialog/Dialog.js";
export class LoadLevelDialog extends Dialog
{
constructor() {
super();
this.setMessage('Level laden');
this.fileInput = this.createFileInput(['json']);
this.fileInput.addEventListener(
'change',
() => {
if (this.fileInput.files.length === 0) {
return;
}
const reader = new FileReader();
reader.addEventListener(
'load',
(event) => {
this.onClose();
this.onLoad(event.target.result);
}
)
reader.readAsBinaryString(this.fileInput.files[0]);
}
)
this.onClose = () => {};
this.onLoad = () => {};
this.buttonCancel = this.createButton('Abbrechen');
this.buttonCancel.addEventListener(
'click',
() => {
this.onClose();
}
);
}
openFileBrowser()
{
this.fileInput.click();
}
}

View File

@ -4,11 +4,12 @@ import UserInterfaceElement from "./UserInterfaceElement.js";
export default class TextBox extends UserInterfaceElement
{
constructor(text, width, context)
constructor(text, width, context, paused = false)
{
super();
this.text = text;
this.width = width;
this.paused = paused;
this.colorText = 'red';
this.colorShadow = 'black';
this.colorBorder = 'black';
@ -115,4 +116,4 @@ export default class TextBox extends UserInterfaceElement
return line;
}
}
}

View File

@ -2,9 +2,10 @@ import TextAlignment from "./TextAlignment.js";
export default class TextLine
{
constructor(text)
constructor(text, paused)
{
this.text = text;
this.paused = paused;
this.estimatedTextWidth = null;
this.colorText = 'red';
this.colorShadow = 'black';
@ -24,6 +25,10 @@ export default class TextLine
let process = setInterval(
() => {
if (this.paused) {
return;
}
this.chars++;
if (this.chars === this.text.length) {
@ -83,4 +88,4 @@ export default class TextLine
context.fillText(this.text.substr(0, this.chars), x + 2, y + this.size + 2);
}
}
}
}

View File

@ -3,8 +3,8 @@ import GeometryPoint from "../geometry/GeometryPoint.js";
export default class TextMessage extends TextBox
{
constructor(text, context) {
super(text, window.innerWidth - 40, context);
constructor(text, context, paused = false) {
super(text, window.innerWidth - 40, context, paused);
this.update();
this.context = context;
}
@ -21,4 +21,4 @@ export default class TextMessage extends TextBox
this.setPosition(this.defaultPosition.x, this.defaultPosition.y);
this.updateLines(this.defaultWidth, this.context);
}
}
}

15112
levels/darius.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
{"tileset":2,"rows":"30","columns":"30","startX":2,"startY":28,"targetX":2,"targetY":15,"gravity":2,"matrix":[[3,0,5,0,5,18,-1,30,5,0,5,0,21,-1,-1,33,5,0,0,5,0,0,5,0,0,3,21,-1,33,3],[2,0,24,0,24,72,-1,66,24,24,0,24,19,-1,-1,31,0,0,0,0,0,0,0,24,24,25,73,-1,31,1],[3,18,-1,92,-1,-1,-1,-1,-1,-1,92,-1,87,-1,-1,33,24,28,28,24,28,28,72,-1,-1,-1,-1,-1,33,3],[2,18,-1,-1,-1,8,-1,10,-1,-1,-1,-1,85,-1,-1,92,-1,-1,-1,-1,-1,-1,-1,-1,36,13,43,-1,31,1],[3,18,-1,39,57,75,-1,69,57,63,-1,51,21,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,30,3,21,-1,33,3],[2,18,-1,85,-1,-1,-1,-1,-1,-1,-1,-1,85,-1,-1,38,56,56,62,-1,-1,50,56,56,0,1,19,-1,31,1],[3,18,-1,87,-1,-1,50,56,56,56,56,56,21,-1,-1,84,-1,-1,-1,-1,-1,-1,-1,-1,30,3,21,-1,33,3],[2,18,-1,31,62,-1,-1,-1,-1,-1,-1,-1,85,-1,-1,86,-1,-1,50,62,-1,-1,-1,-1,30,1,19,-1,31,1],[3,18,-1,87,-1,-1,38,56,62,-1,50,56,21,-1,-1,86,-1,-1,-1,-1,-1,-1,-1,-1,30,3,21,-1,33,3],[2,18,-1,85,-1,37,19,-1,-1,-1,-1,-1,85,-1,-1,30,63,-1,-1,-1,-1,-1,-1,51,0,1,19,-1,1,1],[3,18,-1,69,56,26,20,-1,50,56,62,-1,93,-1,-1,86,-1,-1,-1,-1,-1,-1,-1,-1,30,3,21,-1,3,3],[2,18,-1,-1,-1,-1,89,-1,-1,-1,-1,-1,-1,-1,-1,84,-1,50,62,-1,-1,80,-1,-1,30,1,19,-1,1,1],[3,0,62,-1,50,14,26,56,14,56,15,56,44,-1,-1,86,-1,-1,-1,-1,-1,-1,-1,51,0,3,21,-1,3,3],[2,18,-1,-1,-1,86,-1,-1,89,-1,85,-1,89,-1,51,18,-1,-1,-1,-1,-1,-1,-1,-1,30,1,19,-1,31,1],[3,18,-1,-1,-1,86,-1,-1,95,-1,93,-1,95,-1,-1,92,-1,-1,7,-1,-1,-1,7,-1,30,3,21,-1,33,3],[2,18,-1,-1,-1,86,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,88,-1,-1,-1,88,-1,30,1,19,-1,31,1],[3,2,57,56,57,2,15,56,57,56,15,14,45,-1,-1,36,12,12,0,14,14,14,0,12,0,3,21,-1,33,3],[2,18,-1,-1,-1,30,18,-1,-1,-1,30,5,18,-1,80,0,5,5,0,5,5,5,0,5,0,1,19,-1,31,1],[3,18,-1,10,-1,32,20,-1,10,-1,30,5,18,-1,-1,66,24,24,0,26,26,26,24,24,24,27,75,-1,33,3],[2,0,14,0,14,0,0,14,0,14,0,2,0,80,-1,-1,-1,-1,92,-1,-1,-1,-1,-1,-1,-1,-1,-1,31,1],[3,0,5,0,5,0,0,5,0,5,0,5,18,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,36,15,45,-1,33,3],[2,0,2,0,2,0,0,2,0,2,0,2,0,14,44,-1,50,56,56,56,56,62,-1,-1,30,1,19,-1,31,1],[3,0,3,0,3,0,0,3,0,3,0,3,0,3,18,-1,33,3,3,3,21,-1,-1,-1,30,3,21,-1,33,3],[2,0,26,24,26,24,0,26,24,26,24,26,0,0,18,-1,30,0,0,0,18,-1,-1,80,0,1,19,-1,31,1],[3,18,-1,-1,-1,-1,86,-1,-1,-1,-1,-1,30,2,18,-1,30,2,0,2,18,-1,-1,-1,30,3,21,-1,33,3],[2,18,-1,-1,-1,-1,92,-1,-1,-1,-1,-1,30,2,18,-1,30,2,0,2,0,62,-1,-1,30,1,19,-1,31,1],[3,18,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,66,24,72,-1,66,24,24,24,72,-1,-1,-1,30,3,21,-1,33,3],[2,18,-1,-1,-1,-1,-1,-1,-1,7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,0,1,19,-1,31,1],[3,18,-1,-1,-1,-1,8,-1,-1,87,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,30,3,21,-1,33,3],[2,0,12,17,16,17,0,12,17,0,17,12,14,44,-1,-1,38,44,-1,-1,38,14,14,14,0,1,19,-1,31,1]]}

BIN
tilorswift/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -4,9 +4,10 @@
<meta charset="UTF-8">
<title>Tilorswift</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script type="module" src="js/module.js"></script>
<link rel="shortcut icon" type="image/png" href="favicon.png">
<script type="module" src="js/module.js" defer></script>
</head>
<body>
<div id="map"></div>
</body>
</html>
</html>

View File

@ -5,5 +5,6 @@ export default class Brush
constructor()
{
this.mode = BrushMode.TERRAIN;
this.isIntelligent = false;
}
}
}

View File

@ -5,7 +5,7 @@ export default class ButtonTile extends Field
{
constructor(tileset, index = 0)
{
super(tileset, index);
super(tileset, 0, 0, index);
}
initHtml() {
@ -22,4 +22,4 @@ export default class ButtonTile extends Field
}
)
}
}
}

View File

@ -3,10 +3,12 @@ import TilorswiftFieldEnteredEvent from "./events/TilorswiftFieldEnteredEvent.js
export default class Field
{
constructor(tileset, index = -1)
constructor(tileset, x = 0, y = 0, index = -1)
{
this.tileset = tileset;
this.index = index;
this.x = x;
this.y = y;
this.isEntrancePoint = false;
this.isTargetPoint = false;
this.initHtml();
@ -91,4 +93,4 @@ export default class Field
{
return this.htmlElement;
}
}
}

View File

@ -75,14 +75,13 @@ export default class Terrain
_insertRow(index = undefined)
{
let row = [];
let tr = document.createElement('tr');
const row = [];
const tr = document.createElement('tr');
for (let col = 0; col < this.tilesX; col++) {
let field = new Field(this.tileset);
let td = field.getElement();
const field = new Field(this.tileset, col, this.fields.length);
row.push(field);
tr.appendChild(td);
tr.appendChild(field.getElement());
}
if (index === undefined || index >= this.tilesY - 1) {
@ -183,10 +182,11 @@ export default class Terrain
static createFromJson(levelData)
{
let graphicSet = GraphicSet[levelData.tileset];
const graphicSet = GraphicSet[levelData.tileset];
let tileset = new Tileset(levelData.tileset);
let terrain = new Terrain(tileset, levelData.columns, levelData.rows, graphicSet.backgroundColor);
const tileset = new Tileset(levelData.tileset);
const terrain = new Terrain(tileset, levelData.columns, levelData.rows, graphicSet.backgroundColor);
terrain.backgroundImage = graphicSet.backgroundImage ?? undefined;
for (let y = 0; y < levelData.rows; y++) {
for (let x = 0; x < levelData.columns; x++) {
@ -202,4 +202,60 @@ export default class Terrain
return terrain;
}
}
getFieldNeighbours(field)
{
const neighbours = [];
for (let x = Math.max(0, field.x - 1); x < Math.min(this.tilesX, field.x + 2); x++) {
for (let y = Math.max(0, field.y - 1); y < Math.min(this.tilesY, field.y + 2); y++) {
if (field.x === x && field.y === y) {
continue;
}
neighbours.push(this.fields[y][x]);
}
}
return neighbours;
}
hasFieldNeighbour(field, offsetX, offsetY)
{
const x = field.x + offsetX;
const y = field.y + offsetY;
if (x < 0 || x > this.tilesX - 1) {
return true;
}
if (y < 0 || y > this.tilesY - 1) {
return true;
}
return this.fields[y][x].index > -1;
}
getFieldNeighbourCode(field)
{
let code = '';
if (!this.hasFieldNeighbour(field, -1, 0)) {
code += 'l';
}
if (!this.hasFieldNeighbour(field, 0, -1)) {
code += 't';
}
if (!this.hasFieldNeighbour(field, 1, 0)) {
code += 'r';
}
if (!this.hasFieldNeighbour(field, 0, 1)) {
code += 'b';
}
return code;
}
}

View File

@ -10,6 +10,7 @@ export default class Tileset
this.image.src = '../' + Setting.TILESET_LOCATION + GraphicSet[this.setId].tileset;
this.tiles = GraphicSet[this.setId].tiles;
this.scale = GraphicSet[this.setId].scale;
this.primaryTiles = GraphicSet[this.setId].primaryTiles;
}
getWidth()
@ -31,4 +32,16 @@ export default class Tileset
{
return this.image.height * this.scale;
}
hasExtendedTiles()
{
return GraphicSet[this.setId].tiles > GraphicSet[this.setId].primaryTiles ;
}
getTileIndexFactor(code)
{
const CODES = ['ltr', 't', 'r', 'b', 'l', 'lt', 'tr', 'ltb', 'tb', 'trb', 'lb', 'rb', 'ltrb', 'lr', 'lrb'];
return CODES.indexOf(code) + 1;
}
}

View File

@ -1,54 +1,156 @@
import {LoadLevelDialog} from "../../js/ui/LoadLevelDialog.js";
import GraphicSet from "../../js/GraphicSet.js";
import Setting from "../../js/Setting.js";
import Brush from "./Brush.js";
import Tileset from "./Tileset.js";
import WidgetBar from "./menu/WidgetBar.js";
import TilesetPickerWidget from "./menu/TilesetPickerWidget.js";
import EntrancePointWidget from "./menu/EntrancePointWidget.js";
import TargetPointWidget from "./menu/TargetPointWidget.js";
import Mouse from "./Mouse.js";
import MainMenu from "./menu/MainMenu.js";
import MenuGroup from "./menu/MenuGroup.js";
import MainMenuEntry from "./menu/MainMenuEntry.js";
import TilorswiftMenuNewTerrainClickedEvent from "./events/TilorswiftMenuNewTerrainClickedEvent.js";
import {TilorswiftMenuOpenCLickedEvent} from "./events/TilorswiftMenuOpenCLickedEvent.js";
import TilorswiftMenuSaveClickedEvent from "./events/TilorswiftMenuSaveClickedEvent.js";
import TilorswiftAddRowsClickedEvent from "./events/TilorswiftAddRowsClickedEvent.js";
import TilorswiftAddColumnsClickedEvent from "./events/TilorswiftAddColumnsClickedEvent.js";
import TilorswiftMenuGravityClickedEvent from "./events/TilorswiftMenuGravityClickedEvent.js";
import TilorswiftEvent from "./events/TilorswiftEvent.js";
import BrushMode from "./BrushMode.js";
import DialogNewTerrain from "./dialog/DialogNewTerrain.js";
import DialogGravity from "./dialog/DialogGravity.js";
import DialogAddRows from "./dialog/DialogAddRows.js";
import DialogAddColumns from "./dialog/DialogAddColumns.js";
import Terrain from "./Terrain.js";
import TilorswiftSavedEvent from "./events/TilorswiftSavedEvent.js";
import Level from "../../js/Level.js";
import {IntelligentBrushSwitch} from "./menu/IntelligentBrushSwitch.js";
export default class Tilorswift
{
static getLevelAsJson(level)
constructor(level) {
this.level = level;
this.map = document.getElementById('map');
this.brush = new Brush();
this.tileset = new Tileset(this.level.terrain.tileset.setId);
this.widgetBar = new WidgetBar('widget-bar');
this.tilesetPicker = new TilesetPickerWidget(this.tileset, this.brush);
this.widgetBar.addWidget(this.tilesetPicker);
this.intelligentBrushSwitch = new IntelligentBrushSwitch(this.tilesetPicker, this.brush);
this.widgetBar.addWidget(this.intelligentBrushSwitch);
this.entrancePicker = new EntrancePointWidget(this.widgetBar, this.brush);
this.widgetBar.addWidget(this.entrancePicker);
this.targetPicker = new TargetPointWidget(this.widgetBar, this.brush);
this.widgetBar.addWidget(this.targetPicker);
this.mouse = new Mouse();
this.mainbar = new MainMenu('mainbar');
}
init()
{
document.body.appendChild(this.widgetBar.getElement());
const menuFile = new MenuGroup('Datei');
menuFile.addMenuEntry(new MainMenuEntry('Neu...', TilorswiftMenuNewTerrainClickedEvent));
menuFile.addMenuEntry(new MainMenuEntry('Öffnen...', TilorswiftMenuOpenCLickedEvent));
menuFile.addMenuEntry(new MainMenuEntry('Speichern...', TilorswiftMenuSaveClickedEvent));
this.mainbar.addMenuGroup(menuFile);
const menuEdit = new MenuGroup('Bearbeiten');
menuEdit.addMenuEntry(new MainMenuEntry('Zeilen einfügen...', TilorswiftAddRowsClickedEvent));
menuEdit.addMenuEntry(new MainMenuEntry('Spalten einfügen...', TilorswiftAddColumnsClickedEvent));
this.mainbar.addMenuGroup(menuEdit);
const menuLevel = new MenuGroup('Level');
menuLevel.addMenuEntry(new MainMenuEntry('Gravitation...', TilorswiftMenuGravityClickedEvent));
this.mainbar.addMenuGroup(menuLevel);
document.body.appendChild(this.mainbar.getElement());
this.addEventListeners();
}
getLevelAsJson()
{
let matrix = [];
for (let y = 0; y < level.terrain.fields.length; y++) {
for (let y = 0; y < this.level.terrain.fields.length; y++) {
let row = [];
for (let x = 0; x < level.terrain.fields[y].length; x++) {
row.push(level.terrain.fields[y][x].index);
for (let x = 0; x < this.level.terrain.fields[y].length; x++) {
row.push(this.level.terrain.fields[y][x].index);
}
matrix.push(row);
}
let data = {
tileset: level.getTilesetId(),
rows: level.getRows(),
columns: level.getColumns(),
startX: level.getStartX(),
startY: level.getStartY(),
targetX: level.getTargetX(),
targetY: level.getTargetY(),
gravity: level.gravity,
tileset: this.level.getTilesetId(),
rows: this.level.getRows(),
columns: this.level.getColumns(),
startX: this.level.getStartX(),
startY: this.level.getStartY(),
targetX: this.level.getTargetX(),
targetY: this.level.getTargetY(),
gravity: this.level.gravity,
matrix: matrix,
};
return JSON.stringify(data);
}
static saveLevelToFile(level)
openLevelFromFile()
{
if (!level.hasEntrancePoint()) {
const dialog = new LoadLevelDialog();
dialog.onLoad = (json) => {
this.tileset = new Tileset(JSON.parse(json).tileset);
this.level = Level.createFromJson(json);
this.loadLevel();
dialog.close();
}
dialog.openFileBrowser();
}
loadLevel()
{
this.tileset = new Tileset(this.level.terrain.tileset.setId);
document.body.style.backgroundColor = this.level.getBackgroundColor();
if (GraphicSet[this.level.terrain.tileset.setId].backgroundImage !== null) {
document.body.style.backgroundImage = 'url("../' + Setting.GRAPHICS_LOCATION + GraphicSet[this.level.terrain.tileset.setId].backgroundImage + '")';
} else {
document.body.style.backgroundImage = 'none';
}
this.map.innerHTML = '';
this.map.appendChild(this.level.terrain.getElement());
this.tilesetPicker.reloadTileset(this.tileset);
this.initializeIntelligentBrushWidget();
}
saveLevelToFile()
{
if (!this.level.hasEntrancePoint()) {
alert('Es muss ein Startpunkt definiert sein!');
return false;
}
if (!level.hasTargetPoint()) {
if (!this.level.hasTargetPoint()) {
alert('Es muss ein Zielpunkt definiert sein!');
return false;
}
let filename = prompt('Dateiname', 'terrain.json');
const filename = prompt('Dateiname', 'terrain.json');
if (filename === null) {
return false;
}
let json = Tilorswift.getLevelAsJson(level);
let download = document.createElement('a');
const json = this.getLevelAsJson(level);
const download = document.createElement('a');
download.setAttribute('download', filename);
download.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(json));
@ -57,4 +159,208 @@ export default class Tilorswift
return true;
}
}
initializeIntelligentBrushWidget()
{
if (this.tileset.hasExtendedTiles()) {
this.intelligentBrushSwitch.enable();
this.intelligentBrushSwitch.switchOn();
} else {
this.intelligentBrushSwitch.switchOff();
this.intelligentBrushSwitch.disable();
}
}
createNewTerrain(tilesetIndex, tilesX, tilesY)
{
this.tileset = new Tileset(tilesetIndex);
this.level.terrain = new Terrain(this.tileset, tilesX, tilesY, GraphicSet[tilesetIndex].backgroundColor);
this.level.setGravity(GraphicSet[tilesetIndex].gravity);
document.body.style.backgroundColor = this.level.getBackgroundColor();
if (GraphicSet[tilesetIndex].backgroundImage !== null) {
document.body.style.backgroundImage = 'url("../' + Setting.GRAPHICS_LOCATION + GraphicSet[tilesetIndex].backgroundImage + '")';
} else {
document.body.style.backgroundImage = 'none';
}
this.map.innerHTML = '';
this.map.appendChild(this.level.terrain.getElement());
this.tilesetPicker.reloadTileset(this.tileset);
this.initializeIntelligentBrushWidget();
}
addTerrain(field)
{
if (this.brush.isIntelligent) {
const index = this.level.terrain.brushTileIndex + this.tileset.primaryTiles * this.tileset.getTileIndexFactor(
this.level.terrain.getFieldNeighbourCode(field)
);
field.setIndex(index);
this.updateNeighbours(field);
} else {
field.setIndex(this.level.terrain.brushTileIndex);
}
}
updateNeighbours(field)
{
for (const neighbour of this.level.terrain.getFieldNeighbours(field)) {
if (neighbour.index === -1) {
continue;
}
const neighbourIndex = (neighbour.index % this.tileset.primaryTiles) + this.tileset.primaryTiles * this.tileset.getTileIndexFactor(
this.level.terrain.getFieldNeighbourCode(neighbour)
);
neighbour.setIndex(neighbourIndex);
}
}
removeTerrain(field)
{
field.setIndex(-1);
if (this.brush.isIntelligent) {
for (const neighbour of this.level.terrain.getFieldNeighbours(field)) {
this.updateNeighbours(field);
}
}
}
addEventListeners()
{
window.addEventListener(
TilorswiftEvent.FIELD_CLICKED,
(event) => {
if (this.brush.mode === BrushMode.TERRAIN && !event.getField().isEntrancePoint) {
switch (event.button) {
case 0:
this.addTerrain(event.getField());
break;
case 2:
this.removeTerrain(event.getField());
break;
}
} else if (this.brush.mode === BrushMode.ENTRANCE) {
if (event.getField().index === -1) {
const coordinates = this.level.terrain.getFieldCoordinates(event.getField());
this.level.terrain.setEntrancePoint(coordinates.x, coordinates.y);
this.brush.mode = BrushMode.TERRAIN;
this.widgetBar.enableWidgets();
}
} else if (this.brush.mode === BrushMode.EXIT) {
if (event.getField().index === -1) {
const coordinates = this.level.terrain.getFieldCoordinates(event.getField());
this.level.terrain.setTargetPoint(coordinates.x, coordinates.y);
this.brush.mode = BrushMode.TERRAIN;
this.widgetBar.enableWidgets();
}
}
}
);
window.addEventListener(
'contextmenu',
(event) => {
event.preventDefault();
}
);
window.addEventListener(
TilorswiftEvent.FIELD_ENTERED,
(event) => {
if (this.mouse.isPressedLeft) {
this.addTerrain(event.getField());
} else if (this.mouse.isPressedRight) {
event.getField().setIndex(-1);
}
}
);
window.addEventListener(
TilorswiftEvent.NEW_TERRAIN_CLICKED,
() => {
new DialogNewTerrain();
}
);
window.addEventListener(
TilorswiftEvent.MENU_GRAVITY_CLICKED,
() => {
new DialogGravity(this.level.gravity);
}
);
window.addEventListener(
TilorswiftEvent.ADD_ROWS_CLICKED,
() => {
new DialogAddRows();
}
);
window.addEventListener(
TilorswiftEvent.ADD_COLUMNS_CLICKED,
() => {
new DialogAddColumns();
}
);
window.addEventListener(
TilorswiftEvent.GRAVITY_UPDATED,
(event) => {
this.level.gravity = event.gravity;
}
);
window.addEventListener(
TilorswiftEvent.ADD_ROWS,
(event) => {
this.level.terrain.addRows(event.beforeRow, event.rowCount);
}
);
window.addEventListener(
TilorswiftEvent.ADD_COLUMNS,
(event) => {
this.level.terrain.addColumns(event.beforeColumn, event.columnCount);
}
);
window.addEventListener(
TilorswiftEvent.NEW_TERRAIN,
(event) => {
this.createNewTerrain(event.tilesetIndex, event.tilesX, event.tilesY);
}
);
/* Prevents Firefox's annoying default drag and drop behavior for images */
document.addEventListener(
'dragstart',
(event) => {
event.preventDefault();
}
);
window.addEventListener(
TilorswiftEvent.MENU_OPEN_CLICKED,
() => {
this.openLevelFromFile();
}
);
window.addEventListener(
TilorswiftEvent.MENU_SAVE_CLICKED,
() => {
if (this.saveLevelToFile()) {
window.dispatchEvent(new TilorswiftSavedEvent());
}
}
);
}
}

View File

@ -100,7 +100,12 @@ export default class Dialog
let htmlThumbnail = document.createElement('div');
htmlThumbnail.classList.add('tileset-thumbnail');
htmlThumbnail.style.backgroundImage = 'url("../' + Setting.TILESET_LOCATION + graphicSet.tileset + '")';
const image = new Image();
image.src = '../' + Setting.TILESET_LOCATION + graphicSet.tileset;
htmlThumbnail.style.backgroundImage = 'url(' + image.src + ')';
htmlThumbnail.style.backgroundPositionX = -(image.width / graphicSet.tiles) * graphicSet.tilePreview + 'px';
htmlTilesetElement.appendChild(htmlThumbnail);
let htmlTitleElement = document.createElement('div');
@ -112,7 +117,9 @@ export default class Dialog
'click',
() => {
htmlListElement.style.display = 'none';
htmlAvatarElement.style.backgroundImage = 'url("../' + Setting.TILESET_LOCATION + GraphicSet[index].tileset + '")';
htmlAvatarElement.style.backgroundImage = 'url(' + image.src + ')';
htmlAvatarElement.style.backgroundPositionX = -96 * graphicSet.tilePreview + 'px';
window.dispatchEvent(new TilorswiftTilesetSelectedEvent(index));
}
);
@ -126,8 +133,31 @@ export default class Dialog
return htmlElement;
}
createFileInput(types = [])
{
let input = document.createElement('input');
input.type = 'file';
if (types.length > 0) {
for (const t in types) {
types[t] = '.' + types[t]
}
input.accept = types.join(', ');
}
this.inputAreaElement.appendChild(input);
return input;
}
setMessage(message)
{
this.messageElement.innerText = message;
}
close()
{
this.htmlElement.remove();
}
}

View File

@ -3,6 +3,7 @@ const TilorswiftEvent = {
FIELD_ENTERED: 'fieldEntered',
BUTTON_TILE_CLICKED: 'buttonTileClicked',
MENU_SAVE_CLICKED: 'menuSaveClicked',
MENU_OPEN_CLICKED: 'menuOpenClicked',
DIALOG_BUTTON_OK_CLICKED: 'dialogButtonOkClicked',
MENU_EDIT_CLICKED: 'menuEditClicked',
SAVED: 'saved',
@ -18,4 +19,4 @@ const TilorswiftEvent = {
GRAVITY_UPDATED: 'gravityUpdated',
};
export default TilorswiftEvent;
export default TilorswiftEvent;

View File

@ -0,0 +1,8 @@
import TilorswiftEvent from "./TilorswiftEvent.js";
export class TilorswiftMenuOpenCLickedEvent extends Event
{
constructor() {
super(TilorswiftEvent.MENU_OPEN_CLICKED);
}
}

View File

@ -0,0 +1,58 @@
import Widget from "./Widget.js";
import {Switch} from "./Switch.js";
export class IntelligentBrushSwitch extends Widget
{
constructor(tilesetPicker, brush) {
super('Intelligenter Pinsel');
this.tilesetPicker = tilesetPicker;
this.brush = brush;
this.switch = new Switch();
this.switch.onToggle = (status) => {
if (this.isActive) {
this.setIntelligentBrush(status);
}
}
this.htmlElement.appendChild(this.switch.htmlElement);
if (tilesetPicker.tileset.hasExtendedTiles()) {
this.setIntelligentBrush(true);
}
}
switchOn()
{
if (!this.switch.status) {
this.switch.toggle()
this.setIntelligentBrush(true);
}
}
switchOff()
{
if (this.switch.status) {
this.switch.toggle()
this.setIntelligentBrush(false);
}
}
setIntelligentBrush(status)
{
this.brush.isIntelligent = status;
this.tilesetPicker.updateExtendedTileVisibility();
}
enable() {
super.enable();
this.switch.enable();
}
disable() {
super.disable();
this.switch.disable();
}
}

View File

@ -0,0 +1,50 @@
export class Switch
{
constructor(status = false) {
this.htmlElement = document.createElement('div');
this.htmlElement.classList.add('switch');
this.slider = document.createElement('div');
this.slider.classList.add('switch-slider');
this.htmlElement.appendChild(this.slider);
this.status = status;
this.isEnabled = true;
this.updateSlider();
this.onToggle = () => {}
this.htmlElement.addEventListener(
'click',
() => {
if (!this.isEnabled) {
return;
}
this.toggle();
this.onToggle(this.status);
}
)
}
toggle()
{
this.status = !this.status;
this.updateSlider();
}
updateSlider()
{
this.slider.classList.add(this.status ? 'switch-slider-on' : 'switch-slider-off');
this.slider.classList.remove(this.status ? 'switch-slider-off' : 'switch-slider-on');
}
enable()
{
this.isEnabled = true;
}
disable()
{
this.isEnabled = false;
}
}

View File

@ -24,13 +24,13 @@ export default class TilesetPickerWidget extends Widget
this.brush.mode = BrushMode.TERRAIN;
}
}
)
);
}
loadTileset()
{
for (let t = 0; t < this.tileset.tiles; t++) {
let button = new ButtonTile(this.tileset, t);
const button = new ButtonTile(this.tileset, t);
this.htmlElementSelector.appendChild(button.getElement());
}
@ -48,7 +48,7 @@ export default class TilesetPickerWidget extends Widget
createElementPicker()
{
let htmlElement = document.createElement('div');
const htmlElement = document.createElement('div');
htmlElement.id = 'tileset-picker';
htmlElement.style.width = this.tileset.getTileWidth() + 'px';
htmlElement.style.height = this.tileset.getTileHeight() + 'px';
@ -59,9 +59,9 @@ export default class TilesetPickerWidget extends Widget
createElementSelector()
{
let htmlElementSelector = document.createElement('div');
const htmlElementSelector = document.createElement('div');
htmlElementSelector.id = 'tileset-selector-widget';
htmlElementSelector.style.width = Math.ceil(Math.sqrt(this.tileset.tiles)) * this.tileset.getTileWidth() + 'px';
htmlElementSelector.style.width = Math.ceil(Math.sqrt(this.tileset.tiles)) * this.tileset.getTileWidth() + 'px';
htmlElementSelector.style.left = String(this.tileset.getTileWidth() + 1) + 'px';
return htmlElementSelector;
@ -69,12 +69,24 @@ export default class TilesetPickerWidget extends Widget
setTile(index)
{
let position = -this.tileset.getTileWidth() * index + 'px ' + this.tileset.getTileHeight() + 'px';
this.htmlElement.style.backgroundPosition = position;
this.htmlElement.style.backgroundPosition = -this.tileset.getTileWidth() * index + 'px ' + this.tileset.getTileHeight() + 'px';
}
getElement()
{
return this.htmlElement;
}
}
updateExtendedTileVisibility()
{
const firstExtendedTileIndex = this.tileset.tiles - (this.tileset.tiles - this.tileset.primaryTiles);
for (const index of this.htmlElementSelector.childNodes.keys()) {
if (index >= firstExtendedTileIndex) {
this.htmlElementSelector.childNodes.item(index).style.display = this.brush.isIntelligent
? 'none'
: 'inline-flex';
}
}
}
}

View File

@ -1,208 +1,23 @@
import Terrain from "./Terrain.js";
import TilorswiftEvent from "./events/TilorswiftEvent.js";
import Mouse from "./Mouse.js";
import Tileset from "./Tileset.js";
import Tilorswift from "./Tilorswift.js";
import TilorswiftSavedEvent from "./events/TilorswiftSavedEvent.js";
import MainMenu from "./menu/MainMenu.js";
import MenuGroup from "./menu/MenuGroup.js";
import MainMenuEntry from "./menu/MainMenuEntry.js";
import TilorswiftMenuSaveClickedEvent from "./events/TilorswiftMenuSaveClickedEvent.js";
import TilesetPickerWidget from "./menu/TilesetPickerWidget.js";
import WidgetBar from "./menu/WidgetBar.js";
import EntrancePointWidget from "./menu/EntrancePointWidget.js";
import Brush from "./Brush.js";
import BrushMode from "./BrushMode.js";
import DialogAddRows from "./dialog/DialogAddRows.js";
import DialogAddColumns from "./dialog/DialogAddColumns.js";
import TilorswiftAddRowsClickedEvent from "./events/TilorswiftAddRowsClickedEvent.js";
import TilorswiftAddColumnsClickedEvent from "./events/TilorswiftAddColumnsClickedEvent.js";
import TilorswiftMenuNewTerrainClickedEvent from "./events/TilorswiftMenuNewTerrainClickedEvent.js";
import DialogNewTerrain from "./dialog/DialogNewTerrain.js";
import TargetPointWidget from "./menu/TargetPointWidget.js";
import Level from "../../js/Level.js";
import ImageLoader from "../../js/ImageLoader.js";
import GraphicSet from "../../js/GraphicSet.js";
import Setting from "../../js/Setting.js";
import Level from "../../js/Level.js";
import TilorswiftMenuGravityClickedEvent from "./events/TilorswiftMenuGravityClickedEvent.js";
import DialogGravity from "./dialog/DialogGravity.js";
let level = Level.createFromFile('../levels/moonbase.json');
const imageLoader = new ImageLoader();
if (GraphicSet[level.terrain.tileset.setId].backgroundImage !== null) {
document.body.style.backgroundImage = 'url("../' + Setting.GRAPHICS_LOCATION + GraphicSet[level.getTilesetId()].backgroundImage + '")';
for (const graphicSet of GraphicSet) {
imageLoader.addImage('../' + Setting.TILESET_LOCATION + graphicSet.tileset);
}
let image = new Image();
image.src = '../' + Setting.TILESET_LOCATION + GraphicSet[level.terrain.tileset.setId].tileset;
image.onload = function () {
document.body.style.backgroundColor = GraphicSet[level.terrain.tileset.setId].backgroundColor;
let map = document.getElementById('map');
map.appendChild(level.terrain.getElement());
let brush = new Brush();
let tileset = new Tileset(level.terrain.tileset.setId);
let widgetBar = new WidgetBar('widget-bar');
let tilesetPicker = new TilesetPickerWidget(tileset, brush);
widgetBar.addWidget(tilesetPicker);
let entrancePicker = new EntrancePointWidget(widgetBar, brush);
widgetBar.addWidget(entrancePicker);
let targetPicker = new TargetPointWidget(widgetBar, brush);
widgetBar.addWidget(targetPicker);
document.body.appendChild(widgetBar.getElement());
let mouse = new Mouse();
let mainbar = new MainMenu('mainbar');
let menuFile = new MenuGroup('Datei');
menuFile.addMenuEntry(new MainMenuEntry('Neu...', TilorswiftMenuNewTerrainClickedEvent));
menuFile.addMenuEntry(new MainMenuEntry('Speichern...', TilorswiftMenuSaveClickedEvent));
mainbar.addMenuGroup(menuFile);
let menuEdit = new MenuGroup('Bearbeiten');
menuEdit.addMenuEntry(new MainMenuEntry('Zeilen einfügen...', TilorswiftAddRowsClickedEvent));
menuEdit.addMenuEntry(new MainMenuEntry('Spalten einfügen...', TilorswiftAddColumnsClickedEvent));
mainbar.addMenuGroup(menuEdit);
let menuLevel = new MenuGroup('Level');
menuLevel.addMenuEntry(new MainMenuEntry('Gravitation...', TilorswiftMenuGravityClickedEvent));
mainbar.addMenuGroup(menuLevel);
document.body.appendChild(mainbar.getElement());
window.addEventListener(
TilorswiftEvent.FIELD_CLICKED,
(event) => {
if (brush.mode === BrushMode.TERRAIN && !event.getField().isEntrancePoint) {
switch (event.button) {
case 0:
event.getField().setIndex(level.terrain.brushTileIndex);
break;
case 2:
event.getField().setIndex(-1);
break;
}
} else if (brush.mode === BrushMode.ENTRANCE) {
if (event.getField().index === -1) {
let coordinates = level.terrain.getFieldCoordinates(event.getField());
level.terrain.setEntrancePoint(coordinates.x, coordinates.y);
brush.mode = BrushMode.TERRAIN;
widgetBar.enableWidgets();
}
} else if (brush.mode === BrushMode.EXIT) {
if (event.getField().index === -1) {
let coordinates = level.terrain.getFieldCoordinates(event.getField());
level.terrain.setTargetPoint(coordinates.x, coordinates.y);
brush.mode = BrushMode.TERRAIN;
widgetBar.enableWidgets();
}
}
imageLoader.onLoad = () => {
Level.createFromFile(
'../levels/moonbase.json',
(level) => {
const tilorswift = new Tilorswift(level);
tilorswift.loadLevel();
tilorswift.init();
}
);
window.addEventListener(
'contextmenu',
(event) => {
event.preventDefault();
}
);
window.addEventListener(
TilorswiftEvent.FIELD_ENTERED,
(event) => {
if (mouse.isPressedLeft) {
event.getField().setIndex(level.terrain.brushTileIndex);
} else if (mouse.isPressedRight) {
event.getField().setIndex(-1);
}
}
);
window.addEventListener(
TilorswiftEvent.NEW_TERRAIN_CLICKED,
() => {
new DialogNewTerrain();
}
);
window.addEventListener(
TilorswiftEvent.MENU_GRAVITY_CLICKED,
() => {
new DialogGravity(level.gravity);
}
);
window.addEventListener(
TilorswiftEvent.ADD_ROWS_CLICKED,
() => {
new DialogAddRows();
}
);
window.addEventListener(
TilorswiftEvent.ADD_COLUMNS_CLICKED,
() => {
new DialogAddColumns();
}
);
window.addEventListener(
TilorswiftEvent.GRAVITY_UPDATED,
(event) => {
level.gravity = event.gravity;
}
);
window.addEventListener(
TilorswiftEvent.ADD_ROWS,
function (event) {
level.terrain.addRows(event.beforeRow, event.rowCount);
}
);
window.addEventListener(
TilorswiftEvent.ADD_COLUMNS,
function (event) {
level.terrain.addColumns(event.beforeColumn, event.columnCount);
}
);
window.addEventListener(
TilorswiftEvent.NEW_TERRAIN,
(event) => {
let tileset = new Tileset(event.tilesetIndex);
level.terrain = new Terrain(tileset, event.tilesX, event.tilesY, GraphicSet[event.tilesetIndex].backgroundColor);
document.body.style.backgroundColor = level.getBackgroundColor();
if (GraphicSet[event.tilesetIndex].backgroundImage !== null) {
document.body.style.backgroundImage = 'url("../' + Setting.GRAPHICS_LOCATION + GraphicSet[event.tilesetIndex].backgroundImage + '")';
} else {
document.body.style.backgroundImage = 'none';
}
map.innerHTML = '';
map.appendChild(level.terrain.getElement());
tilesetPicker.reloadTileset(tileset);
}
);
/* Prevents Firefox's annoying default drag and drop behavior for images */
document.addEventListener(
'dragstart',
function (event) {
event.preventDefault();
}
);
window.addEventListener(
TilorswiftEvent.MENU_SAVE_CLICKED,
function () {
if (Tilorswift.saveLevelToFile(level)) {
window.dispatchEvent(new TilorswiftSavedEvent());
}
}
);
};
}
imageLoader.load();

View File

@ -3,6 +3,16 @@
to { width: 512px; }
}
@keyframes switch-toggle-on {
from {left: 0}
to {left: 50%}
}
@keyframes switch-toggle-off {
from {left: 50%}
to {left: 0}
}
body {
padding: 0;
margin: 0;
@ -112,6 +122,9 @@ body {
top: 0;
background-color: #cccccc;
box-shadow: 20px 20px 20px rgba(0, 0, 0, 0.5);
max-width: 1000%;
max-height: 500%;
overflow: auto;
}
#tileset-picker:hover > #tileset-selector-widget {
@ -236,6 +249,10 @@ body {
margin-bottom: 20px;
}
input[type="file"] {
margin-bottom: 20px;
}
.dialog-button {
padding: 5px 20px;
background-color: grey;
@ -277,7 +294,7 @@ body {
}
.tileset-thumbnail {
display: table-cell;
display: block;
width: 32px;
height: 32px;
background-size: auto 100%;
@ -292,8 +309,34 @@ body {
padding: 10px;
}
/*
tr:hover > td > .selection {
opacity: 0.5;
.switch {
position: relative;
border-radius: 16px;
border: 2px solid #333333;
width: 100%;
height: 32px;
cursor: pointer;
background-color: #777777;
}
.switch-slider {
position: absolute;
border-radius: 14px;
width: 50%;
height: 100%;
border: 1px solid white;
}
.switch-slider-on {
left: 50%;
background-color: #2222aa;
animation-name: switch-toggle-on;
animation-duration: 0.2s;
}
.switch-slider-off {
left: 0;
background-color: #888888;
animation-name: switch-toggle-off;
animation-duration: 0.2s;
}
*/