wip!
This commit is contained in:
parent
1305ffbb16
commit
1013c53975
21 changed files with 988 additions and 43 deletions
41
static/js/tairu/cardinal-directions.js
Normal file
41
static/js/tairu/cardinal-directions.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { defineFrame, Frame } from './framework.js';
|
||||
import { TileEditor, canConnect, shouldConnect } from './tairu.js';
|
||||
|
||||
class CardinalDirectionsEditor extends TileEditor {
|
||||
constructor() {
|
||||
super();
|
||||
this.colorScheme.tiles[1] = "#f96565";
|
||||
}
|
||||
|
||||
drawConnectionText(text, enabled, tileX, tileY, hAlign, vAlign) {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.fillStyle = enabled ? "#6c023e" : "#d84161";
|
||||
this.ctx.font = `800 14px ${Frame.monoFontFace}`;
|
||||
const padding = 2;
|
||||
let topLeftX = tileX * this.tileSize + padding;
|
||||
let topLeftY = tileY * this.tileSize + padding;
|
||||
let rectSize = this.tileSize - padding * 2;
|
||||
let { leftX, baselineY } = this.getTextPositionInBox(text, topLeftX, topLeftY, rectSize, rectSize, hAlign, vAlign);
|
||||
this.ctx.fillText(text, leftX, baselineY);
|
||||
}
|
||||
|
||||
drawTiles() {
|
||||
super.drawTiles();
|
||||
for (let y = 0; y < this.tilemap.height; ++y) {
|
||||
for (let x = 0; x < this.tilemap.width; ++x) {
|
||||
let tile = this.tilemap.at(x, y);
|
||||
if (canConnect(tile)) {
|
||||
let connectedWithEast = shouldConnect(tile, this.tilemap.at(x + 1, y));
|
||||
let connectedWithSouth = shouldConnect(tile, this.tilemap.at(x, y + 1));
|
||||
let connectedWithNorth = shouldConnect(tile, this.tilemap.at(x, y - 1));
|
||||
let connectedWithWest = shouldConnect(tile, this.tilemap.at(x - 1, y));
|
||||
this.drawConnectionText("E", connectedWithEast, x, y, "right", "center");
|
||||
this.drawConnectionText("S", connectedWithSouth, x, y, "center", "bottom");
|
||||
this.drawConnectionText("N", connectedWithNorth, x, y, "center", "top");
|
||||
this.drawConnectionText("W", connectedWithWest, x, y, "left", "center");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
defineFrame("tairu-editor-cardinal-directions", CardinalDirectionsEditor);
|
|
@ -1,13 +1,87 @@
|
|||
// A frameworking class assigning some CSS classes to the canvas to make it integrate nicer with CSS.
|
||||
class Frame extends HTMLCanvasElement {
|
||||
export class Frame extends HTMLCanvasElement {
|
||||
static fontFace = "RecVar";
|
||||
static monoFontFace = "RecVarMono";
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
async connectedCallback() {
|
||||
this.style.cssText = `
|
||||
|
||||
margin-top: 8px;
|
||||
margin-bottom: 4px;
|
||||
border-radius: 4px;
|
||||
max-width: 100%;
|
||||
`;
|
||||
|
||||
this.ctx = this.getContext("2d");
|
||||
|
||||
requestAnimationFrame(this.#drawLoop.bind(this));
|
||||
}
|
||||
|
||||
#drawLoop() {
|
||||
this.ctx.font = "14px RecVar";
|
||||
this.draw();
|
||||
requestAnimationFrame(this.#drawLoop.bind(this));
|
||||
}
|
||||
|
||||
// Override this!
|
||||
draw() { }
|
||||
draw() {
|
||||
throw new ReferenceError("draw() must be overridden");
|
||||
}
|
||||
|
||||
getTextPositionInBox(text, x, y, width, height, hAlign, vAlign) {
|
||||
let measurements = this.ctx.measureText(text);
|
||||
|
||||
let leftX;
|
||||
switch (hAlign) {
|
||||
case "left":
|
||||
leftX = x;
|
||||
break;
|
||||
case "center":
|
||||
leftX = x + width / 2 - measurements.width / 2;
|
||||
break;
|
||||
case "right":
|
||||
leftX = x + width - measurements.width;
|
||||
break;
|
||||
}
|
||||
|
||||
let textHeight = measurements.fontBoundingBoxAscent;
|
||||
let baselineY;
|
||||
switch (vAlign) {
|
||||
case "top":
|
||||
baselineY = y + textHeight;
|
||||
break;
|
||||
case "center":
|
||||
baselineY = y + height / 2 + textHeight / 2;
|
||||
break;
|
||||
case "bottom":
|
||||
baselineY = y + height;
|
||||
break;
|
||||
}
|
||||
|
||||
return { leftX, baselineY };
|
||||
}
|
||||
|
||||
get scaleInViewportX() {
|
||||
return this.clientWidth / this.width;
|
||||
}
|
||||
|
||||
get scaleInViewportY() {
|
||||
return this.clientHeight / this.height;
|
||||
}
|
||||
|
||||
getMousePositionFromEvent(event) {
|
||||
return {
|
||||
x: event.offsetX / this.scaleInViewportX,
|
||||
y: event.offsetY / this.scaleInViewportY,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function defineFrame(elementName, claß) { // because `class` is a keyword.
|
||||
customElements.define(elementName, claß, { extends: "canvas" });
|
||||
}
|
||||
|
||||
defineFrame("tairu--frame", Frame);
|
||||
|
|
|
@ -1,8 +1,192 @@
|
|||
class TileEditor extends HTMLCanvasElement {
|
||||
import { Frame, defineFrame } from "./framework.js";
|
||||
import tilemapRegistry from "./tilemap-registry.js";
|
||||
|
||||
export function canConnect(tile) {
|
||||
return tile == 1;
|
||||
}
|
||||
|
||||
export function shouldConnect(a, b) {
|
||||
return a == b;
|
||||
}
|
||||
|
||||
export class TileEditor extends Frame {
|
||||
constructor() {
|
||||
super();
|
||||
this.tileCursor = { x: 0, y: 0 };
|
||||
|
||||
this.colorScheme = {
|
||||
background: "#F7F7F7",
|
||||
grid: "#00000011",
|
||||
tileCursor: "#222222",
|
||||
tiles: [
|
||||
"transparent",
|
||||
"#eb134a",
|
||||
],
|
||||
};
|
||||
|
||||
this.tileColorPalette = [
|
||||
"transparent",
|
||||
"#eb134a",
|
||||
];
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
this.tileSize = parseInt(this.getAttribute("data-tile-size"));
|
||||
|
||||
let tilemapId = this.getAttribute("data-tilemap-id");
|
||||
console.log(tilemapRegistry);
|
||||
if (tilemapId != null) {
|
||||
this.tilemap = tilemapRegistry[this.getAttribute("data-tilemap-id")];
|
||||
} else {
|
||||
throw new ReferenceError(`tilemap '${tilemapId}' does not exist`);
|
||||
}
|
||||
|
||||
// 0st element is explicitly null because it represents the empty tile.
|
||||
this.tilesets = [null];
|
||||
|
||||
let attachedImages = this.getElementsByTagName("img");
|
||||
for (let image of attachedImages) {
|
||||
if (image.hasAttribute("data-tairu-tileset")) {
|
||||
let tilesetIndex = parseInt(image.getAttribute("data-tairu-tileset"));
|
||||
this.tilesets[tilesetIndex] = image;
|
||||
}
|
||||
}
|
||||
|
||||
this.width = this.tilemap.width * this.tileSize;
|
||||
this.height = this.tilemap.height * this.tileSize;
|
||||
|
||||
this.hasFocus = false;
|
||||
this.paintingTile = null;
|
||||
|
||||
this.addEventListener("mousemove", event => this.mouseMoved(event));
|
||||
this.addEventListener("mousedown", event => this.mousePressed(event));
|
||||
this.addEventListener("mouseup", event => this.mouseReleased(event));
|
||||
|
||||
this.addEventListener("mouseenter", _ => this.hasFocus = true);
|
||||
this.addEventListener("mouseleave", _ => this.hasFocus = false);
|
||||
|
||||
this.addEventListener("contextmenu", event => event.preventDefault());
|
||||
|
||||
// TODO: This should also work on mobile.
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.ctx.fillStyle = this.colorScheme.background;
|
||||
this.ctx.fillRect(0, 0, this.width, this.height);
|
||||
|
||||
this.drawTiles();
|
||||
this.drawGrid();
|
||||
if (this.hasFocus) {
|
||||
this.drawTileCursor();
|
||||
}
|
||||
}
|
||||
|
||||
drawGrid() {
|
||||
this.ctx.beginPath();
|
||||
for (let x = 0; x < this.tilemap.width; ++x) {
|
||||
this.ctx.moveTo(x * this.tileSize, 0);
|
||||
this.ctx.lineTo(x * this.tileSize, this.height);
|
||||
}
|
||||
for (let y = 0; y < this.tilemap.width; ++y) {
|
||||
this.ctx.moveTo(0, y * this.tileSize);
|
||||
this.ctx.lineTo(this.width, y * this.tileSize);
|
||||
}
|
||||
this.ctx.strokeStyle = this.colorScheme.grid;
|
||||
this.ctx.lineWidth = 1;
|
||||
this.ctx.stroke();
|
||||
}
|
||||
|
||||
drawTileCursor() {
|
||||
this.ctx.strokeStyle = this.colorScheme.tileCursor;
|
||||
this.ctx.lineWidth = 5;
|
||||
this.ctx.strokeRect(this.tileCursor.x * this.tileSize, this.tileCursor.y * this.tileSize, this.tileSize, this.tileSize);
|
||||
}
|
||||
|
||||
get hasTilesets() {
|
||||
// Remember that tile 0 represents emptiness.
|
||||
return this.tilesets.length > 1;
|
||||
}
|
||||
|
||||
drawTiles() {
|
||||
if (this.hasTilesets) {
|
||||
this.drawTexturedTiles();
|
||||
} else {
|
||||
this.drawColoredTiles();
|
||||
}
|
||||
}
|
||||
|
||||
drawColoredTiles() {
|
||||
for (let y = 0; y < this.tilemap.height; ++y) {
|
||||
for (let x = 0; x < this.tilemap.width; ++x) {
|
||||
let tile = this.tilemap.at(x, y);
|
||||
if (tile != 0) {
|
||||
this.ctx.fillStyle = this.colorScheme.tiles[tile];
|
||||
this.ctx.fillRect(x * this.tileSize, y * this.tileSize, this.tileSize, this.tileSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawTexturedTiles() {
|
||||
this.ctx.imageSmoothingEnabled = false;
|
||||
|
||||
for (let y = 0; y < this.tilemap.height; ++y) {
|
||||
for (let x = 0; x < this.tilemap.width; ++x) {
|
||||
let tile = this.tilemap.at(x, y);
|
||||
if (tile != 0) {
|
||||
let tileset = this.tilesets[tile];
|
||||
|
||||
let connectedWithEast = shouldConnect(tile, this.tilemap.at(x + 1, y)) ? 0b0001 : 0;
|
||||
let connectedWithSouth = shouldConnect(tile, this.tilemap.at(x, y + 1)) ? 0b0010 : 0;
|
||||
let connectedWithWest = shouldConnect(tile, this.tilemap.at(x - 1, y)) ? 0b0100 : 0;
|
||||
let connectedWithNorth = shouldConnect(tile, this.tilemap.at(x, y - 1)) ? 0b1000 : 0;
|
||||
let tileIndex = connectedWithNorth
|
||||
| connectedWithWest
|
||||
| connectedWithSouth
|
||||
| connectedWithEast;
|
||||
|
||||
let tilesetTileSize = tileset.height;
|
||||
let tilesetX = tileIndex * tilesetTileSize;
|
||||
let tilesetY = 0;
|
||||
this.ctx.drawImage(
|
||||
this.tilesets[tile],
|
||||
tilesetX, tilesetY, tilesetTileSize, tilesetTileSize,
|
||||
x * this.tileSize, y * this.tileSize, this.tileSize, this.tileSize,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mouseMoved(event) {
|
||||
let mouse = this.getMousePositionFromEvent(event);
|
||||
this.tileCursor.x = Math.floor(mouse.x / this.tileSize);
|
||||
this.tileCursor.y = Math.floor(mouse.y / this.tileSize);
|
||||
this.paintTileUnderCursor();
|
||||
}
|
||||
|
||||
mousePressed(event) {
|
||||
event.preventDefault();
|
||||
if (event.button == 0) {
|
||||
this.paintingTile = 1;
|
||||
} else if (event.button == 2) {
|
||||
this.paintingTile = 0;
|
||||
}
|
||||
this.paintTileUnderCursor();
|
||||
}
|
||||
|
||||
mouseReleased() {
|
||||
this.paintingTile = null;
|
||||
}
|
||||
|
||||
paintTileUnderCursor() {
|
||||
if (this.paintingTile != null) {
|
||||
this.tilemap.setAt(this.tileCursor.x, this.tileCursor.y, this.paintingTile);
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define("tairu-tile-editor", TileEditor)
|
||||
defineFrame("tairu-editor", TileEditor);
|
||||
|
||||
console.log("tairu editor loaded");
|
||||
|
|
33
static/js/tairu/tilemap-registry.js
Normal file
33
static/js/tairu/tilemap-registry.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { Tilemap } from './tilemap.js';
|
||||
|
||||
const alphabet = " x";
|
||||
|
||||
function parseTilemap(lineArray) {
|
||||
let tilemap = new Tilemap(lineArray[0].length, lineArray.length);
|
||||
for (let y in lineArray) {
|
||||
let line = lineArray[y];
|
||||
for (let x = 0; x < line.length; ++x) {
|
||||
let char = line.charAt(x);
|
||||
tilemap.setAt(x, y, alphabet.indexOf(char));
|
||||
}
|
||||
}
|
||||
return tilemap;
|
||||
}
|
||||
|
||||
export default {
|
||||
bitwiseAutotiling: parseTilemap([
|
||||
" ",
|
||||
" xxx ",
|
||||
" xxx ",
|
||||
" xxx ",
|
||||
" ",
|
||||
]),
|
||||
bitwiseAutotilingChapter2: parseTilemap([
|
||||
" ",
|
||||
" x ",
|
||||
" x ",
|
||||
" xxx ",
|
||||
" ",
|
||||
]),
|
||||
};
|
||||
|
30
static/js/tairu/tilemap.js
Normal file
30
static/js/tairu/tilemap.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
export class Tilemap {
|
||||
constructor(width, height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.tiles = new Uint8Array(width * height);
|
||||
this.default = 0;
|
||||
}
|
||||
|
||||
tileIndex(x, y) {
|
||||
return x + y * this.width;
|
||||
}
|
||||
|
||||
inBounds(x, y) {
|
||||
return x >= 0 && y >= 0 && x < this.width && y < this.height;
|
||||
}
|
||||
|
||||
at(x, y) {
|
||||
if (this.inBounds(x, y)) {
|
||||
return this.tiles[this.tileIndex(x, y)];
|
||||
} else {
|
||||
return this.default;
|
||||
}
|
||||
}
|
||||
|
||||
setAt(x, y, tile) {
|
||||
if (this.inBounds(x, y)) {
|
||||
this.tiles[this.tileIndex(x, y)] = tile;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue