First playable version
This commit is contained in:
parent
399e3097a3
commit
bdca1ce602
|
|
@ -62,7 +62,7 @@ export default class Character extends Item {
|
|||
character.health += this.healingAmount;
|
||||
}
|
||||
|
||||
public useItem(item: Item | null | undefined): boolean {
|
||||
public removeItem(item: Item | null | undefined): boolean {
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -90,7 +90,7 @@ export default class Character extends Item {
|
|||
return this.meleeWeapon != null && !this.tile.enemy?.isBoss;
|
||||
|
||||
case SpinnerAction.SHOOT:
|
||||
return this.useItem(this.gun) && !this.tile.enemy?.isBoss;
|
||||
return this.removeItem(this.gun) && !this.tile.enemy?.isBoss;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export default abstract class Entity {
|
|||
|
||||
public handleClick(x: number, y: number) {
|
||||
if (this.isPointInBounds(x, y)) {
|
||||
this.onClick();
|
||||
this.onClick(x - this.left, y - this.right);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -46,6 +46,6 @@ export default abstract class Entity {
|
|||
}
|
||||
|
||||
protected abstract draw(ctx: CanvasRenderingContext2D): void;
|
||||
protected onClick() { }
|
||||
protected onClick(_x: number, _y: number) { }
|
||||
public update(_dt: number) { };
|
||||
}
|
||||
|
|
@ -65,6 +65,7 @@ export default async function main() {
|
|||
const ctx = canvas.getContext('2d');
|
||||
|
||||
spinner.addListener((a) => map.handleSpin(a));
|
||||
inventory.addListener((c, i) => map.handleItemUse(c, i));
|
||||
|
||||
canvas.addEventListener('click', onClick);
|
||||
canvas.addEventListener('mousemove', onMouseMove);
|
||||
|
|
|
|||
|
|
@ -1,28 +1,92 @@
|
|||
import { range } from "@common/utils";
|
||||
import type Character from "./character";
|
||||
import { Characters } from "./character";
|
||||
import Entity from "./entity";
|
||||
import Tile from "./tile";
|
||||
import type Item from "./item";
|
||||
|
||||
export type UseListener = (character: Character, item: Item) => void;
|
||||
|
||||
export default class Inventory extends Entity {
|
||||
private tiles: Tile[][];
|
||||
private listeners = new Set<UseListener>();
|
||||
|
||||
constructor(public readonly characters: Character[], position: [number, number], size: [number, number]) {
|
||||
super(position, size);
|
||||
|
||||
const numCharacters = Object.keys(Characters).length;
|
||||
const tileSize = this.width / numCharacters;
|
||||
const numTiles = Math.floor(this.height / tileSize) - 2;
|
||||
|
||||
this.tiles = range(numCharacters).map((x) =>
|
||||
range(numTiles).map((y) =>
|
||||
new Tile([x * tileSize, (2 + y) * tileSize], tileSize)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private drawCharacter(ctx: CanvasRenderingContext2D, character: Character) {
|
||||
private drawCharacter(ctx: CanvasRenderingContext2D, idx: number) {
|
||||
const character = this.characters[idx];
|
||||
ctx.drawImage(character.type, 0.1, 0.1, 0.8, 0.8);
|
||||
ctx.fillText(`♥ ${character.health}`, 0.5, 1.5);
|
||||
ctx.fillText(`💖 ${character.health}`, 0.5, 1.5);
|
||||
|
||||
let y = 2;
|
||||
for (const item of character.inventory) {
|
||||
ctx.drawImage(item.type, 0.1, 0.1 + y, 0.8, 0.8);
|
||||
y += 1;
|
||||
}
|
||||
}
|
||||
|
||||
public override handleClick(x: number, y: number): void {
|
||||
for (const { tile, item, character } of this.activeTiles) {
|
||||
if (tile.isPointInBounds(x - this.left, y - this.top)) {
|
||||
this.listeners.forEach(l => l(character, item));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override handleMouseMove(x: number, y: number): void {
|
||||
this.activeTiles.forEach(({ tile }) => tile.handleMouseMove(x - this.left, y - this.top));
|
||||
}
|
||||
|
||||
private get activeTiles() {
|
||||
return this.tiles.slice(0, this.characters.length)
|
||||
.flatMap((column, characterIndex) =>
|
||||
column.slice(0, this.characters[characterIndex].inventory.length)
|
||||
.map((tile, tileIndex) => ({
|
||||
tile,
|
||||
character: this.characters[characterIndex],
|
||||
item: this.characters[characterIndex].inventory[tileIndex],
|
||||
}))
|
||||
.filter(({ item }) => item.isConsumable)
|
||||
);
|
||||
}
|
||||
|
||||
public addListener(listener: UseListener) {
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
public removeListener(listener: UseListener) {
|
||||
this.listeners.delete(listener);
|
||||
}
|
||||
|
||||
protected override draw(ctx: CanvasRenderingContext2D): void {
|
||||
const step = 1 / Object.keys(Characters).length;
|
||||
const columnWidth = this.width * step;
|
||||
|
||||
ctx.save();
|
||||
ctx.scale(step, columnWidth / this.height);
|
||||
ctx.font = `0.5px Arial`;
|
||||
ctx.font = `0.3px Arial`;
|
||||
ctx.fillStyle = 'black';
|
||||
|
||||
for (const char of this.characters) {
|
||||
this.drawCharacter(ctx, char);
|
||||
for (const i of range(this.characters.length)) {
|
||||
this.drawCharacter(ctx, i);
|
||||
ctx.translate(1, 0);
|
||||
}
|
||||
ctx.restore();
|
||||
|
||||
ctx.scale(1 / this.width, 1 / this.height);
|
||||
this.activeTiles.forEach(({ tile }) => tile.render(ctx));
|
||||
}
|
||||
}
|
||||
|
|
@ -107,6 +107,14 @@ export default class Item {
|
|||
return this.type === ItemType.ENEMY_BOSS;
|
||||
}
|
||||
|
||||
get isConsumable() {
|
||||
return [
|
||||
ItemType.ITEM_HEAL,
|
||||
ItemType.ITEM_PLANKS,
|
||||
ItemType.WEAPON_GRENADE,
|
||||
].includes(this.type);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return Object.entries(ItemType).find(t => t[1] === this.type)?.[0];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ export default class Tile extends Entity {
|
|||
ctx.fillStyle = `rgba(255, 255, 255, 0.2)`;
|
||||
ctx.fillRect(0, 0, 1, 1);
|
||||
|
||||
ctx.lineWidth = 1 / this.width;
|
||||
ctx.strokeStyle = 'red';
|
||||
// ctx.lineWidth = 1 / this.width;
|
||||
// ctx.strokeStyle = 'red';
|
||||
|
||||
// for (const tile of this.connections) {
|
||||
// const center = [
|
||||
|
|
|
|||
|
|
@ -148,9 +148,13 @@ export default class TileMap extends Entity {
|
|||
items.push(char);
|
||||
}
|
||||
|
||||
const normalTiles = this.tiles.filter(t => t.type === TileType.NORMAL);
|
||||
const endTiles = this.tiles.filter(t => t.type === TileType.END);
|
||||
const endTilesNeighbors = new Set(endTiles.flatMap(t => t.connections));
|
||||
const normalTiles = this.tiles.filter(t =>
|
||||
t.type === TileType.NORMAL
|
||||
&& !endTilesNeighbors.has(t)
|
||||
&& !this.startTile.connections.includes(t)
|
||||
);
|
||||
|
||||
const fillableTiles = [
|
||||
...endTilesNeighbors,
|
||||
|
|
@ -171,6 +175,9 @@ export default class TileMap extends Entity {
|
|||
}
|
||||
|
||||
public override handleMouseMove(x: number, y: number): void {
|
||||
if (this.state !== GameState.NORMAL) {
|
||||
return;
|
||||
}
|
||||
this.availableTiles.forEach(tile => tile.handleMouseMove(x - this.left, y - this.top));
|
||||
}
|
||||
|
||||
|
|
@ -191,7 +198,7 @@ export default class TileMap extends Entity {
|
|||
tile.removeItem(item);
|
||||
this.characters.push(item);
|
||||
this.nextCharacter();
|
||||
} else if (item.isBoss && this.character.useItem(this.character.rocketLauncher)) {
|
||||
} else if (item.isBoss && this.character.removeItem(this.character.rocketLauncher)) {
|
||||
tile.killEnemy();
|
||||
this.nextCharacter();
|
||||
} else if (item.isEnemy) {
|
||||
|
|
@ -225,36 +232,54 @@ export default class TileMap extends Entity {
|
|||
this.character.tile.items.push(...this.character.inventory);
|
||||
this.characters.splice(this.currentCharacterIdx, 1);
|
||||
this.currentCharacterIdx = this.currentCharacterIdx % this.characters.length;
|
||||
this.setNormalState();
|
||||
}
|
||||
break;
|
||||
case SpinnerAction.MELEE:
|
||||
case SpinnerAction.SHOOT:
|
||||
this.character.tile.killEnemy();
|
||||
this.nextCharacter();
|
||||
this.killEnemy();
|
||||
break;
|
||||
case SpinnerAction.RUN:
|
||||
this.setNormalState();
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (action !== SpinnerAction.BITE) {
|
||||
this.state = GameState.NORMAL;
|
||||
this.findAvailableTiles();
|
||||
public handleItemUse(character: Character, item: Item) {
|
||||
const success = character.removeItem(item);
|
||||
if (success) {
|
||||
if (item.type === ItemType.ITEM_HEAL) {
|
||||
character.heal(character);
|
||||
} else if (item.type === ItemType.WEAPON_GRENADE && this.state === GameState.FIGHT) {
|
||||
this.killEnemy();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private findAvailableTiles(moveDistance: number = 1) {
|
||||
const characterTiles = new Set(this.characters.map(c => c.tile));
|
||||
this.availableTiles = Pathfinding.findPossibleMoves(this.character.tile, moveDistance)
|
||||
this.availableTiles = Pathfinding.findPossibleMoves(this.character.tile, moveDistance + this.character.moveBonus)
|
||||
.filter(t => !characterTiles.has(t));
|
||||
}
|
||||
|
||||
private setNormalState() {
|
||||
this.state = GameState.NORMAL;
|
||||
this.findAvailableTiles();
|
||||
}
|
||||
|
||||
private nextCharacter() {
|
||||
this.currentCharacterIdx = (this.currentCharacterIdx + 1) % this.characters.length;
|
||||
}
|
||||
|
||||
private killEnemy() {
|
||||
this.character.tile.killEnemy();
|
||||
this.nextCharacter();
|
||||
this.setNormalState();
|
||||
}
|
||||
|
||||
protected draw(ctx: CanvasRenderingContext2D): void {
|
||||
ctx.scale(1 / this.width, 1 / this.height);
|
||||
|
||||
|
|
@ -276,7 +301,11 @@ export default class TileMap extends Entity {
|
|||
ctx.drawImage(c.type, c.tile.centerX - w / 2, c.tile.centerY - w / 2, w, w)
|
||||
);
|
||||
|
||||
this.tiles.forEach(t => t.render(ctx));
|
||||
this.tiles.forEach(t => {
|
||||
if (t.items.length > 0 || (this.state === GameState.NORMAL && this.availableTiles.includes(t))) {
|
||||
t.render(ctx);
|
||||
}
|
||||
});
|
||||
|
||||
ctx.lineWidth = 3;
|
||||
ctx.strokeStyle = 'yellow';
|
||||
|
|
|
|||
Loading…
Reference in New Issue