diff --git a/graphics/background-graveyard.jpg b/graphics/background-graveyard.jpg new file mode 100644 index 0000000..c7153fc Binary files /dev/null and b/graphics/background-graveyard.jpg differ diff --git a/graphics/tileset/graveyard-background.png b/graphics/tileset/graveyard-background.png new file mode 100644 index 0000000..e618c5b Binary files /dev/null and b/graphics/tileset/graveyard-background.png differ diff --git a/graphics/tileset/graveyard.png b/graphics/tileset/graveyard.png new file mode 100644 index 0000000..15ff49a Binary files /dev/null and b/graphics/tileset/graveyard.png differ diff --git a/js/Game.js b/js/Game.js index a5c03f6..49fea07 100644 --- a/js/Game.js +++ b/js/Game.js @@ -69,8 +69,8 @@ export class Game 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.mrCroc.draw(this.context, this.camera); for (const effect of this.level.fullscreenEffects) { effect.update(timestamp); @@ -238,9 +238,9 @@ export class Game this.isPaused = false; - for (const effect of this.level.fullscreenEffects) { - effect.init(); - } + for (const effect of this.level.fullscreenEffects) { + effect.init(); + } window.requestAnimationFrame(loopFunction); } diff --git a/js/GraphicSet.js b/js/GraphicSet.js index 5d04975..d4539d0 100644 --- a/js/GraphicSet.js +++ b/js/GraphicSet.js @@ -1,4 +1,4 @@ -let GraphicSet = [ +const GraphicSet = [ { name: 'Nature', tileset: 'landscape01.jpg', @@ -9,6 +9,7 @@ let GraphicSet = [ tilePreview: 5, primaryTiles: 8, gravity: 9.806, + tilesetBackground: null, }, { name: 'Moon', @@ -20,6 +21,7 @@ let GraphicSet = [ tilePreview: 1, primaryTiles: 2, gravity: 2.4515, + tilesetBackground: null, }, { name: 'Death Star', @@ -31,6 +33,7 @@ let GraphicSet = [ tilePreview: 3, primaryTiles: 6, gravity: 9.806, + tilesetBackground: null, }, { name: 'Nature 2.0', @@ -42,6 +45,7 @@ let GraphicSet = [ tilePreview: 46, primaryTiles: 3, gravity: 9.806, + tilesetBackground: null, }, { name: 'Io', @@ -53,6 +57,7 @@ let GraphicSet = [ tilePreview: 2, primaryTiles: 6, gravity: 1.796, + tilesetBackground: null, }, { name: 'Southpole', @@ -64,7 +69,24 @@ let GraphicSet = [ tilePreview: 2, primaryTiles: 7, gravity: 9.806, + tilesetBackground: null, }, + { + name: 'Graveyard', + tileset: 'graveyard.png', + tiles: 304, + scale: 1, + backgroundColor: '#000000', + backgroundImage: 'background-graveyard.jpg', + tilePreview: 2, + primaryTiles: 19, + gravity: 9.806, + tilesetBackground: { + path: 'graveyard-background.png', + tiles: 2, + scale: 1, + } + } ]; export default GraphicSet; diff --git a/js/Level.js b/js/Level.js index 81aabf3..ebec95d 100644 --- a/js/Level.js +++ b/js/Level.js @@ -6,10 +6,15 @@ export default class Level { static FACTOR_GRAVITY = 4.903; + /** + * @param {Terrain} terrain + */ constructor(terrain) { + /** @type {Terrain} */ this.terrain = terrain; - this.fullscreenEffects = []; + + this.fullscreenEffects = []; this.gravity = 2.0; } @@ -78,7 +83,7 @@ export default class Level this.gravity = gravity; } - static createFromFile(filename, callback = () => {}) + static createFromFile(filename, onLoad = () => {}) { let loader = new FileLoader(filename); loader.onLoad = (data) => { @@ -86,6 +91,12 @@ export default class Level const level = new Level(Terrain.createFromJson(json)); level.setGravity(json.gravity / Level.FACTOR_GRAVITY); + for (const oldEffect of level.fullscreenEffects) { + oldEffect.destroy(); + } + + level.fullscreenEffects = []; + if (json.hasOwnProperty('effects')) { const effectFactory = new FullscreenEffectFactory(); @@ -96,7 +107,7 @@ export default class Level } } - callback(level); + onLoad(level); } loader.loadContent(); } @@ -109,6 +120,12 @@ export default class Level const level = new Level(terrain); level.setGravity(data.gravity / Level.FACTOR_GRAVITY); + for (const oldEffect of level.fullscreenEffects) { + oldEffect.destroy(); + } + + level.fullscreenEffects = []; + if (data.hasOwnProperty('effects')) { const effectFactory = new FullscreenEffectFactory(); diff --git a/js/effects/FullscreenEffect.js b/js/effects/FullscreenEffect.js index 35b3a26..275d8e7 100644 --- a/js/effects/FullscreenEffect.js +++ b/js/effects/FullscreenEffect.js @@ -11,6 +11,10 @@ export class FullscreenEffect { } + destroy() + { + } + update(timestamp) { } diff --git a/js/effects/RainEffect.js b/js/effects/RainEffect.js index c344e96..8d9a4ee 100644 --- a/js/effects/RainEffect.js +++ b/js/effects/RainEffect.js @@ -48,4 +48,12 @@ export class RainEffect extends FullscreenEffect } } } + + destroy() + { + super.destroy(); + + this.sound.pause(); + this.sound.remove(); + } } diff --git a/js/effects/SnowEffect.js b/js/effects/SnowEffect.js index f1b64d9..c043d30 100644 --- a/js/effects/SnowEffect.js +++ b/js/effects/SnowEffect.js @@ -48,4 +48,12 @@ export class SnowEffect extends FullscreenEffect } } } + + destroy() + { + super.destroy(); + + this.audio.pause(); + this.audio.remove(); + } } diff --git a/js/effects/ThunderstormEffect.js b/js/effects/ThunderstormEffect.js index 3873830..d7b4b05 100644 --- a/js/effects/ThunderstormEffect.js +++ b/js/effects/ThunderstormEffect.js @@ -18,6 +18,7 @@ export class ThunderstormEffect extends FullscreenEffect new Audio('js/effects/thunder04.mp3'), ]; this.currentSound = 0; + this.isActive = true; } shuffleSounds(iterations = 10) @@ -60,6 +61,10 @@ export class ThunderstormEffect extends FullscreenEffect setTimeout( () => { + if (!this.isActive) { + return; + } + if (this.currentSound === 0) { this.shuffleSounds(); } @@ -85,4 +90,14 @@ export class ThunderstormEffect extends FullscreenEffect context.fillStyle = 'rgba(255, 255, 255, ' + this.currentAlpha + ')'; context.fillRect(0, 0, context.canvas.width, context.canvas.height); } + + destroy() + { + this.isActive = false; + + for (const sound of this.sounds) { + sound.pause(); + sound.remove(); + } + } } diff --git a/js/module.js b/js/module.js index 8400b05..13e2406 100644 --- a/js/module.js +++ b/js/module.js @@ -75,6 +75,10 @@ function loadLevel(level) if (graphicSet.backgroundImage !== null) { loader.addImage(Setting.GRAPHICS_LOCATION + graphicSet.backgroundImage); } + + if (graphicSet.tilesetBackground !== null) { + loader.addImage(Setting.TILESET_LOCATION + graphicSet.tilesetBackground.path); + } } loader.load(); diff --git a/js/retro/RetroArchitecture.js b/js/retro/RetroArchitecture.js index 7425da2..f5a47ad 100644 --- a/js/retro/RetroArchitecture.js +++ b/js/retro/RetroArchitecture.js @@ -5,24 +5,69 @@ import GeometryPoint from "../geometry/GeometryPoint.js"; import GeometryRect from "../geometry/GeometryRect.js"; import GraphicSet from "../GraphicSet.js"; import Setting from "../Setting.js"; +import Camera from "../Camera.js"; +import Level from "../Level.js"; export default class RetroArchitecture { - constructor(tilesetSprite, tiles, columns, rows) + /** + * @param {{sprite: RetroSprite, tiles: number}} retroTileset + * @param {number} columns + * @param {number} rows + * @param {{sprite: RetroSprite, tiles: number}} retroTilesetBackground + */ + constructor(retroTileset, columns, rows, retroTilesetBackground = null) { - this.tilesetSprite = tilesetSprite; - this.tiles = tiles; + /** @type {RetroSprite} */ + this.tilesetSprite = retroTileset.sprite; + + /** @type {RetroSprite|null} */ + this.tilesetSpriteBackground = retroTilesetBackground !== null + ? retroTilesetBackground.sprite + : null; + + /** @type {number} */ + this.tiles = retroTileset.tiles; + + /** @type {number} */ + this.tilesBackground = retroTilesetBackground !== null + ? retroTilesetBackground.tiles + : 0; + + /** @type {string|null} */ this.backgroundColor = null; + + /** @type {string|null} */ this.backgroundImage = null; + + /** @type {number} */ this.rows = rows; + + /** @type {number} */ this.columns = columns; + + /** @type {Array>} */ this.matrix = []; + + /** @type {number} */ this.tileWidth = this.tilesetSprite.getWidth() / this.tiles; + + /** @type {number} */ this.tileHeight = this.tilesetSprite.getHeight(); + + /** @type {number} */ this.startX = 0; + + /** @type {number} */ this.startY = 0; + + /** @type {number} */ this.targetX = 0; + + /** @type {number} */ this.targetY = 0; + + /** @type {GeometryPoint} */ this.targetPosition = new GeometryPoint(this.targetX, this.targetY); this.init(); @@ -41,16 +86,26 @@ export default class RetroArchitecture } } + /** + * @param {string|null} color + */ setBackgroundColor(color) { this.backgroundColor = color; } + /** + * @param {string|null} image + */ setBackgroundImage(image) { this.backgroundImage = image; } + /** + * @param {GeometryRect} rect + * @returns {GeometryRectCollection} + */ getCollisionRects(rect) { let posX = Math.floor(rect.position.x / this.tileWidth) - 2; @@ -62,11 +117,16 @@ export default class RetroArchitecture for (let y = Math.max(0, posY); y < rangeY; y++) { for (let x = Math.max(0, posX); x < rangeX; x++) { - if (y < this.matrix.length && x < this.matrix[y].length && this.matrix[y][x] !== null) { + if ( + y < this.matrix.length && + x < this.matrix[y].length && + this.matrix[y][x] !== null && this.matrix[y][x].tile.index > -1 + ) { let intersection = this.matrix[y][x].rect.getRectIntersection(rect); + if (intersection !== null) { collection.addRect(intersection); - } + } } } } @@ -83,7 +143,7 @@ export default class RetroArchitecture for (let y = Math.max(0, posY); y < rangeY; y++) { for (let x = Math.max(0, posX); x < rangeX; x++) { - if (this.matrix[y][x] !== null) { + if (this.matrix[y][x] !== null && this.matrix[y][x].tile.index > -1) { if (this.matrix[y][x].rect.getRectIntersection(rect) !== null) { return true; } @@ -111,7 +171,10 @@ export default class RetroArchitecture let tilePosition = this.getTileForPosition(position, 0); while (tilePosition !== null && tilePosition.y > 0) { - if (this.matrix[tilePosition.y][tilePosition.x] !== null) { + if ( + this.matrix[tilePosition.y][tilePosition.x] !== null && + this.matrix[tilePosition.y][tilePosition.x].tile.index > -1 + ) { return tilePosition.y * this.tileHeight + this.tileHeight; } @@ -126,7 +189,10 @@ export default class RetroArchitecture let tilePosition = this.getTileForPosition(position); while (tilePosition !== null && tilePosition.y < this.rows) { - if (this.matrix[tilePosition.y][tilePosition.x] !== null) { + if ( + this.matrix[tilePosition.y][tilePosition.x] !== null && + this.matrix[tilePosition.y][tilePosition.x].tile.index > -1 + ) { return tilePosition.y * this.tileHeight; } @@ -141,7 +207,10 @@ export default class RetroArchitecture let tilePosition = this.getTileForPosition(new GeometryPoint(position.x, position.y), 1, 0); while (tilePosition !== null && tilePosition.x < this.columns) { - if (this.matrix[tilePosition.y][tilePosition.x] !== null) { + if ( + this.matrix[tilePosition.y][tilePosition.x] !== null && + this.matrix[tilePosition.y][tilePosition.x].tile.index > -1 + ) { return tilePosition.x * this.tileWidth; } @@ -156,7 +225,10 @@ export default class RetroArchitecture let tilePosition = this.getTileForPosition(new GeometryPoint(position.x, position.y), -1,0); while (tilePosition !== null && tilePosition.x > 0) { - if (this.matrix[tilePosition.y][tilePosition.x] !== null) { + if ( + this.matrix[tilePosition.y][tilePosition.x] !== null && + this.matrix[tilePosition.y][tilePosition.x].tile.index > -1 + ) { return tilePosition.x * this.tileWidth + this.tileWidth; } @@ -183,8 +255,8 @@ export default class RetroArchitecture setMovableToTargetPosition(movable) { - movable.position.x = this.tileWidth * this.targetX + this.tileWidth * 0.5; - movable.position.y = this.tileHeight * this.targetY + this.tileHeight; + movable.position.x = this.tileWidth * this.targetX + this.tileWidth * 0.5; + movable.position.y = this.tileHeight * this.targetY + this.tileHeight; } isMovableInsideTargetPosition(movable) @@ -213,8 +285,14 @@ export default class RetroArchitecture for (let x = viewX; x < viewWidth; x++) { let field = this.matrix[y][x]; - if (field !== null) { + if (field === null || field.tile.index === -1) { + continue; + } + + if (field.tile.index > -1) { this.drawField(context, x, y, camera, field); + } else { + this.drawBackgroundField(context, x, y, camera, field); } } } @@ -235,25 +313,47 @@ export default class RetroArchitecture ); } + drawBackgroundField(context, x, y, camera, field) + { + context.drawImage( + this.tilesetSpriteBackground.canvas, + -(field.tile.index % 2) * this.tileWidth, + 0, + this.tileWidth, + this.tileHeight, + Math.round(x * this.tileWidth - camera.position.x), + Math.round(y * this.tileHeight - camera.position.y), + this.tileWidth, + this.tileHeight, + ); + } + getStartPosition() { return new GeometryPoint(this.startX * this.tileWidth, this.startY * this.tileHeight); } + /** + * @param {Level} level + */ static createFromData(level) { let graphicSet = GraphicSet[level.getTilesetId()]; - let tileset = new RetroSprite( + const tilesetSprite = new RetroSprite( Setting.TILESET_LOCATION + graphicSet.tileset, graphicSet.scale ); - let architecture = new RetroArchitecture( - tileset, - graphicSet.tiles, + const tilesetBackground = level.terrain.tileset.background !== null + ? new RetroSprite(level.terrain.tileset.background.image.src) + : null; + + const architecture = new RetroArchitecture( + {sprite: tilesetSprite, tiles: graphicSet.tiles}, level.getColumns(), - level.getRows() + level.getRows(), + tilesetBackground === null ? null : {sprite: tilesetBackground, tiles: level.terrain.tileset.background.tiles} ); architecture.setBackgroundColor(graphicSet.backgroundColor); @@ -267,7 +367,7 @@ export default class RetroArchitecture for (let y = 0; y < level.getRows(); y++) { for (let x = 0; x < level.getColumns(); x++) { - if (level.getTilesetMatrix()[y][x].index > -1) { + if (level.getTilesetMatrix()[y][x].index !== -1) { architecture.matrix[y][x] = new RetroArchitectureTile( level.getTilesetMatrix()[y][x], x * architecture.tileWidth, diff --git a/tilorswift/js/BrushMode.js b/tilorswift/js/BrushMode.js index 527f590..4cbe237 100644 --- a/tilorswift/js/BrushMode.js +++ b/tilorswift/js/BrushMode.js @@ -2,6 +2,7 @@ const BrushMode = { TERRAIN: 1, ENTRANCE: 2, EXIT: 3, + BACKGROUND: 4, }; export default BrushMode; \ No newline at end of file diff --git a/tilorswift/js/ButtonBackgroundTile.js b/tilorswift/js/ButtonBackgroundTile.js new file mode 100644 index 0000000..13df4c7 --- /dev/null +++ b/tilorswift/js/ButtonBackgroundTile.js @@ -0,0 +1,30 @@ +import ButtonTile from "./ButtonTile.js"; +import TilorswiftButtonBackgroundTileClickedEvent from "./events/TilorswiftButtonBackgroundTileClickedEvent.js"; +import Tileset from "./Tileset.js"; + +export default class ButtonBackgroundTile extends ButtonTile +{ + /** + * @param {Tileset} tileset + * @param {number} index + */ + constructor(tileset, index = 0) + { + super(tileset, index); + } + + initHtml() { + this.htmlElement = document.createElement('div'); + this.className = 'field'; + this.setupElement(); + } + + initEventListeners() { + this.htmlElement.addEventListener( + 'mousedown', + () => { + window.dispatchEvent(new TilorswiftButtonBackgroundTileClickedEvent(this)); + } + ) + } +} diff --git a/tilorswift/js/ButtonTile.js b/tilorswift/js/ButtonTile.js index 8d68da7..ec952cc 100644 --- a/tilorswift/js/ButtonTile.js +++ b/tilorswift/js/ButtonTile.js @@ -1,8 +1,13 @@ import Field from "./Field.js"; import TilorswiftButtonTileClickedEvent from "./events/TilorswiftButtonTileClickedEvent.js"; +import Tileset from "./Tileset.js"; export default class ButtonTile extends Field { + /** + * @param {Tileset} tileset + * @param {number} index + */ constructor(tileset, index = 0) { super(tileset, 0, 0, index); diff --git a/tilorswift/js/Field.js b/tilorswift/js/Field.js index f12cc3d..2ea8d4a 100644 --- a/tilorswift/js/Field.js +++ b/tilorswift/js/Field.js @@ -3,6 +3,12 @@ import TilorswiftFieldEnteredEvent from "./events/TilorswiftFieldEnteredEvent.js export default class Field { + /** + * @param {Tileset} tileset + * @param {number} x + * @param {number} y + * @param {number} index + */ constructor(tileset, x = 0, y = 0, index = -1) { this.tileset = tileset; @@ -11,6 +17,9 @@ export default class Field this.y = y; this.isEntrancePoint = false; this.isTargetPoint = false; + this.entrancePoint = null; + this.targetPoint = null; + this.initHtml(); this.initEventListeners(); } @@ -50,21 +59,42 @@ export default class Field this.htmlElement.style.width = String(this.tileset.getTileWidth()) + 'px'; this.htmlElement.style.height = String(this.tileset.getTileHeight()) + 'px'; this.htmlElement.style.backgroundSize = 'auto ' + this.tileset.getTileHeight() + 'px'; - this.htmlElement.style.backgroundImage = 'url("' + this.tileset.image.src + '")'; - this.htmlElement.style.backgroundPositionX = -this.index * this.tileset.getTileWidth() + 'px'; + + this._setupBackgroundImage(); + this._setupBackgroundPosition(); + } + + _setupBackgroundImage() + { + const url = this.index >= -1 + ? this.tileset.image.src + : this.tileset.background.image.src; + + this.htmlElement.style.backgroundImage = 'url("' + url + '")'; + } + + _setupBackgroundPosition() + { + const position = this.index >= -1 + ? -this.index * this.tileset.getTileWidth() + : (this.index % this.tileset.background.tiles) * this.tileset.getTileWidth(); + + this.htmlElement.style.backgroundPositionX = String(position) + 'px'; } addSelector() { let hoverElement = document.createElement('div'); hoverElement.classList.add('selection'); + this.htmlElement.appendChild(hoverElement); } setIndex(index) { this.index = index; - this.htmlElement.style.backgroundPositionX = -this.index * this.tileset.getTileWidth() + 'px'; + this._setupBackgroundPosition(); + this._setupBackgroundImage(); } setEntrancePoint(bool) @@ -72,9 +102,15 @@ export default class Field this.isEntrancePoint = bool; if (this.isEntrancePoint) { - this.htmlElement.classList.add('entrance'); + this.entrancePoint = document.createElement('div'); + this.entrancePoint.classList.add('entrance'); + + this.htmlElement.appendChild(this.entrancePoint); } else { - this.htmlElement.classList.remove('entrance'); + if (this.entrancePoint instanceof HTMLElement) { + this.entrancePoint.remove(); + this.entrancePoint = null; + } } } @@ -83,9 +119,15 @@ export default class Field this.isTargetPoint = bool; if (this.isTargetPoint) { - this.htmlElement.classList.add('target'); + this.targetPoint = document.createElement('div'); + this.targetPoint.classList.add('target'); + + this.htmlElement.appendChild(this.targetPoint); } else { - this.htmlElement.classList.remove('target'); + if (this.targetPoint instanceof HTMLElement) { + this.targetPoint.remove(); + this.targetPoint = null; + } } } diff --git a/tilorswift/js/Terrain.js b/tilorswift/js/Terrain.js index 91b07d6..24c4e9b 100644 --- a/tilorswift/js/Terrain.js +++ b/tilorswift/js/Terrain.js @@ -5,9 +5,17 @@ import GraphicSet from "../../js/GraphicSet.js"; export default class Terrain { + /** + * @param {Tileset} tileset + * @param {number} tilesX + * @param {number} tilesY + * @param {string} backgroundColor + */ constructor(tileset, tilesX, tilesY, backgroundColor = 'black') { + /** @type {Tileset} */ this.tileset = tileset; + this.fields = []; this.tilesX = tilesX; this.tilesY = tilesY; @@ -19,6 +27,7 @@ export default class Terrain this.backgroundImage = undefined; this.htmlElement = document.createElement('table'); this.brushTileIndex = 0; + this.brushBackdroundTileIndex = 0; this.init(); } @@ -38,7 +47,14 @@ export default class Terrain (event) => { this.brushTileIndex = event.button.index; } - ) + ); + + window.addEventListener( + TilorswiftEvent.BACKGROUND_BUTTON_TILE_CLICKED, + (event) => { + this.brushTileIndex = event.button.index; + } + ); } getElement() @@ -106,8 +122,6 @@ export default class Terrain addColumns(index, quantity = 1) { - console.log(this.fields); - for (let c = 0; c < quantity; c++) { this._insertColumn(index); this.tilesX++; @@ -122,8 +136,6 @@ export default class Terrain } this.htmlElement.style.width = this.tileset.getTileWidth() * this.tilesX + 'px'; - - console.log(this.fields); } _insertColumn(index = undefined) @@ -149,7 +161,7 @@ export default class Terrain setEntrancePoint(tileX, tileY) { - if (this.fields[tileY][tileX].index === -1) { + if (this.fields[tileY][tileX].index < 0) { if (this.entranceTileX !== undefined && this.entranceTileY !== undefined) { this.fields[this.entranceTileY][this.entranceTileX].setEntrancePoint(false); } @@ -163,7 +175,7 @@ export default class Terrain setTargetPoint(tileX, tileY) { - if (this.fields[tileY][tileX].index === -1) { + if (this.fields[tileY][tileX].index < 0) { if (this.targetTileX !== undefined && this.targetTileY !== undefined) { this.fields[this.targetTileY][this.targetTileX].setTargetPoint(false); } @@ -203,6 +215,7 @@ export default class Terrain const graphicSet = GraphicSet[levelData.tileset]; const tileset = new Tileset(levelData.tileset); + const terrain = new Terrain(tileset, levelData.columns, levelData.rows, graphicSet.backgroundColor); terrain.backgroundImage = graphicSet.backgroundImage ?? undefined; diff --git a/tilorswift/js/Tileset.js b/tilorswift/js/Tileset.js index 5c4cb32..da4a9ef 100644 --- a/tilorswift/js/Tileset.js +++ b/tilorswift/js/Tileset.js @@ -3,41 +3,74 @@ import Setting from "../../js/Setting.js"; export default class Tileset { + /** + * @param {number} setId + */ constructor(setId) { + /** @type {number} */ this.setId = setId; + + /** @type {Image} */ this.image = new Image(); this.image.src = '../' + Setting.TILESET_LOCATION + GraphicSet[this.setId].tileset; + + /** @type {number} */ this.tiles = GraphicSet[this.setId].tiles; + + /** @type {number} */ this.scale = GraphicSet[this.setId].scale; + + /** @type {number} */ this.primaryTiles = GraphicSet[this.setId].primaryTiles; + + /** @type {{image: Image, tiles: number, scale: number}|null} */ + this.background = null; + + if (GraphicSet[this.setId].tilesetBackground !== null) { + const backgroundImage = new Image(); + backgroundImage.src = '../' + Setting.TILESET_LOCATION + + GraphicSet[this.setId].tilesetBackground.path; + + this.background = { + image: backgroundImage, + tiles: GraphicSet[this.setId].tilesetBackground.tiles, + scale: GraphicSet[this.setId].tilesetBackground.scale, + }; + } } + /** @returns {number} */ getWidth() { return this.image.width * this.scale; } + /** @returns {number} */ getHeight() { return this.image.height * this.scale; } + /** @returns {number} */ getTileWidth() { return this.image.width / this.tiles * this.scale; } + /** @returns {number} */ getTileHeight() { return this.image.height * this.scale; } + /** @returns {boolean} */ hasExtendedTiles() { return GraphicSet[this.setId].tiles > GraphicSet[this.setId].primaryTiles ; } + /** @returns {number} */ getTileIndexFactor(code) { const CODES = ['ltr', 't', 'r', 'b', 'l', 'lt', 'tr', 'ltb', 'tb', 'trb', 'lb', 'rb', 'ltrb', 'lr', 'lrb']; diff --git a/tilorswift/js/Tilorswift.js b/tilorswift/js/Tilorswift.js index b52553f..2e95b12 100644 --- a/tilorswift/js/Tilorswift.js +++ b/tilorswift/js/Tilorswift.js @@ -5,6 +5,7 @@ import Brush from "./Brush.js"; import Tileset from "./Tileset.js"; import WidgetBar from "./menu/WidgetBar.js"; import TilesetPickerWidget from "./menu/TilesetPickerWidget.js"; +import BackgroundPickerWidget from "./menu/BackgroundPickerWidget.js"; import EntrancePointWidget from "./menu/EntrancePointWidget.js"; import TargetPointWidget from "./menu/TargetPointWidget.js"; import Mouse from "./Mouse.js"; @@ -38,8 +39,8 @@ export default class Tilorswift { static EFFECT_NAMES = { [SnowEffect.NAME]: 'Schnee', - [RainEffect.NAME]: 'Regen', - [ThunderstormEffect.NAME]: 'Gewitter', + [RainEffect.NAME]: 'Regen', + [ThunderstormEffect.NAME]: 'Gewitter', } constructor(level) { @@ -52,6 +53,8 @@ export default class Tilorswift this.widgetBar.addWidget(this.tilesetPicker); this.intelligentBrushSwitch = new IntelligentBrushSwitch(this.tilesetPicker, this.brush); this.widgetBar.addWidget(this.intelligentBrushSwitch); + this.backgroundPicker = new BackgroundPickerWidget(this.tileset, this.brush); + this.widgetBar.addWidget(this.backgroundPicker); this.entrancePicker = new EntrancePointWidget(this.widgetBar, this.brush); this.widgetBar.addWidget(this.entrancePicker); this.targetPicker = new TargetPointWidget(this.widgetBar, this.brush); @@ -77,7 +80,7 @@ export default class Tilorswift const menuLevel = new MenuGroup('Level'); menuLevel.addMenuEntry(new MainMenuEntry('Gravitation...', TilorswiftMenuGravityClickedEvent)); - menuLevel.addMenuEntry(new MainMenuEntry('Effekte...', TilorswiftMenuEffectsClickedEvent)); + menuLevel.addMenuEntry(new MainMenuEntry('Effekte...', TilorswiftMenuEffectsClickedEvent)); this.mainbar.addMenuGroup(menuLevel); document.body.appendChild(this.mainbar.getElement()); @@ -109,7 +112,7 @@ export default class Tilorswift targetY: this.level.getTargetY(), gravity: this.level.gravity, matrix: matrix, - effects: this.level.fullscreenEffects.map((effect) => {return effect.getName()}), + effects: this.level.fullscreenEffects.map((effect) => {return effect.getName()}), }; return JSON.stringify(data); @@ -119,7 +122,6 @@ export default class Tilorswift { const dialog = new LoadLevelDialog(); dialog.onLoad = (json) => { - console.log(json); this.tileset = new Tileset(JSON.parse(json).tileset); this.level = Level.createFromJson(json); this.loadLevel(); @@ -142,6 +144,7 @@ export default class Tilorswift this.map.innerHTML = ''; this.map.appendChild(this.level.terrain.getElement()); this.tilesetPicker.reloadTileset(this.tileset); + this.backgroundPicker.reloadTileset(this.tileset); this.initializeIntelligentBrushWidget(); } @@ -222,10 +225,15 @@ export default class Tilorswift } } + addBackground(field) + { + field.setIndex(this.level.terrain.brushTileIndex); + } + updateNeighbours(field) { for (const neighbour of this.level.terrain.getFieldNeighbours(field)) { - if (neighbour.index === -1) { + if (neighbour.index <= -1) { continue; } @@ -262,15 +270,24 @@ export default class Tilorswift this.removeTerrain(event.getField()); break; } + } else if (this.brush.mode === BrushMode.BACKGROUND && !event.getField().isEntrancePoint) { + switch (event.button) { + case 0: + this.addBackground(event.getField()); + break; + case 2: + this.removeTerrain(event.getField()); + break; + } } else if (this.brush.mode === BrushMode.ENTRANCE) { - if (event.getField().index === -1) { + if (event.getField().index < 0) { 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) { + if (event.getField().index < 0) { const coordinates = this.level.terrain.getFieldCoordinates(event.getField()); this.level.terrain.setTargetPoint(coordinates.x, coordinates.y); this.brush.mode = BrushMode.TERRAIN; @@ -291,7 +308,14 @@ export default class Tilorswift TilorswiftEvent.FIELD_ENTERED, (event) => { if (this.mouse.isPressedLeft) { - this.addTerrain(event.getField()); + switch (this.brush.mode) { + case BrushMode.TERRAIN: + this.addTerrain(event.getField()); + break; + case BrushMode.BACKGROUND: + this.addBackground(event.getField()); + break; + } } else if (this.mouse.isPressedRight) { event.getField().setIndex(-1); } diff --git a/tilorswift/js/events/TilorswiftBackgroundButtonClickedEvent.js b/tilorswift/js/events/TilorswiftBackgroundButtonClickedEvent.js new file mode 100644 index 0000000..b46732f --- /dev/null +++ b/tilorswift/js/events/TilorswiftBackgroundButtonClickedEvent.js @@ -0,0 +1,9 @@ +import TilorswiftEvent from "./TilorswiftEvent.js"; + +export default class TilorswiftBackgroundButtonTileClickedEvent extends Event +{ + constructor(button) { + super(TilorswiftEvent.BACKGROUND_BUTTON_TILE_CLICKED); + this.button = button; + } +} \ No newline at end of file diff --git a/tilorswift/js/events/TilorswiftButtonBackgroundTileClickedEvent.js b/tilorswift/js/events/TilorswiftButtonBackgroundTileClickedEvent.js new file mode 100644 index 0000000..e97353f --- /dev/null +++ b/tilorswift/js/events/TilorswiftButtonBackgroundTileClickedEvent.js @@ -0,0 +1,9 @@ +import TilorswiftEvent from "./TilorswiftEvent.js"; + +export default class TilorswiftButtonBackgroundTileClickedEvent extends Event +{ + constructor(button) { + super(TilorswiftEvent.BACKGROUND_BUTTON_TILE_CLICKED); + this.button = button; + } +} \ No newline at end of file diff --git a/tilorswift/js/events/TilorswiftEvent.js b/tilorswift/js/events/TilorswiftEvent.js index 523d4f6..4857f22 100644 --- a/tilorswift/js/events/TilorswiftEvent.js +++ b/tilorswift/js/events/TilorswiftEvent.js @@ -2,6 +2,7 @@ const TilorswiftEvent = { FIELD_CLICKED: 'fieldClicked', FIELD_ENTERED: 'fieldEntered', BUTTON_TILE_CLICKED: 'buttonTileClicked', + BACKGROUND_BUTTON_TILE_CLICKED: 'backgroundButtonTileClicked', MENU_SAVE_CLICKED: 'menuSaveClicked', MENU_OPEN_CLICKED: 'menuOpenClicked', DIALOG_BUTTON_OK_CLICKED: 'dialogButtonOkClicked', diff --git a/tilorswift/js/menu/BackgroundPickerWidget.js b/tilorswift/js/menu/BackgroundPickerWidget.js new file mode 100644 index 0000000..4017116 --- /dev/null +++ b/tilorswift/js/menu/BackgroundPickerWidget.js @@ -0,0 +1,102 @@ +import Widget from "./Widget.js"; +import BrushMode from "../BrushMode.js"; +import TilorswiftEvent from "../events/TilorswiftEvent.js"; +import ButtonBackgroundTile from "../ButtonBackgroundTile.js"; +import Tileset from "../Tileset.js"; + +export default class BackgroundPickerWidget extends Widget +{ + constructor(tileset, brush) + { + super('Hintergrund'); + this.tileset = tileset; + this.brush = brush; + this.htmlElement = this.createElementPicker(); + this.htmlElementSelector = this.createElementSelector(); + this.htmlElement.appendChild(this.htmlElementSelector); + + this.loadTileset(); + + window.addEventListener( + TilorswiftEvent.BACKGROUND_BUTTON_TILE_CLICKED, + (event) => { + if (this.isActive) { + this.setTile(event.button.index); + this.brush.mode = BrushMode.BACKGROUND; + } + } + ); + } + + loadTileset() + { + if (this.tileset.background === null) { + this.htmlElement.style.backgroundImage = 'none'; + return; + } + + for (let t = -2; t >= -this.tileset.background.tiles - 1; t--) { + console.log(t); + const button = new ButtonBackgroundTile(this.tileset, t); + this.htmlElementSelector.appendChild(button.getElement()); + } + + this.htmlElement.style.backgroundImage = 'url("' + this.tileset.background.image.src + '")'; + this.htmlElement.style.backgroundSize = 'auto ' + this.tileset.getTileWidth() + 'px'; + } + + /** + * @param {Tileset} tileset + */ + reloadTileset(tileset) + { + this.tileset = tileset; + this.htmlElementSelector.innerHTML = ''; + this.htmlElementSelector.style.width = Math.ceil(Math.sqrt(this.tileset.tiles)) * this.tileset.getTileWidth() + 'px'; + this.loadTileset(); + } + + createElementPicker() + { + const htmlElement = document.createElement('div'); + htmlElement.id = 'tileset-picker'; + htmlElement.style.width = this.tileset.getTileWidth() + 'px'; + htmlElement.style.height = this.tileset.getTileHeight() + 'px'; + htmlElement.style.backgroundSize = this.tileset.getTileWidth() + 'px ' + this.tileset.getTileHeight() + 'px'; + + return htmlElement; + } + + createElementSelector() + { + 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.left = String(this.tileset.getTileWidth() + 1) + 'px'; + + return htmlElementSelector; + } + + setTile(index) + { + this.htmlElement.style.backgroundPosition = String(-this.tileset.getTileWidth() * index) + 'px ' + String(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'; + } + } + } +} \ No newline at end of file diff --git a/tilorswift/js/module.js b/tilorswift/js/module.js index 9cbe487..577fcb9 100644 --- a/tilorswift/js/module.js +++ b/tilorswift/js/module.js @@ -8,6 +8,10 @@ const imageLoader = new ImageLoader(); for (const graphicSet of GraphicSet) { imageLoader.addImage('../' + Setting.TILESET_LOCATION + graphicSet.tileset); + + if (graphicSet.tilesetBackground !== null) { + imageLoader.addImage('../' + Setting.TILESET_LOCATION + graphicSet.tilesetBackground.path); + } } imageLoader.onLoad = () => { diff --git a/tilorswift/style.css b/tilorswift/style.css index 3197ede..12c71c2 100644 --- a/tilorswift/style.css +++ b/tilorswift/style.css @@ -179,6 +179,9 @@ body { display: inline-flex; } .entrance, .target { + width: 96px; + height: 96px; + position: absolute; background-image: url("graphics/entrance-point.svg") !important; background-size: contain !important; background-position: center bottom !important;