照例先放上圖片方便對照。
昨天我們把圖片上半部的初始場景建立好了,包含 地圖、怪物、掉落物、玩家。然而這些物件之間並不是靜態的,想要讓他們在地圖上互動,我們就要先定義好相關互動事件的模型
(接續昨天的實作內容) 在 src/models/event.ts
新增以下程式碼定義事件模型
import { Player } from "./item";
export type Event = MoveEvent | AttackEvent | PickEvent;
export class MoveEvent {
_tag: "MoveEvent" = "MoveEvent";
player: Player; // 誰在移動 ?
direction: "left" | "right"; // 往哪裡移動 ?
constructor(player: Player, direction: "left" | "right") {
this.player = player;
this.direction = direction;
}
}
export class AttackEvent {
_tag: "AttackEvent" = "AttackEvent";
player: Player; // 誰施放技能 ?
skill: string; // 技能名稱
range: { dx: number; dy: number }; // 技能範圍,這邊就不做傷害計算了,一律秒殺
constructor(
player: Player,
skill: string,
range: { dx: number; dy: number }
) {
this.player = player;
this.range = range;
this.skill = skill;
}
}
export class PickEvent {
_tag: "PickEvent" = "PickEvent";
player: Player; // 誰在撿取物品
constructor(player: Player) {
this.player = player;
}
}
有事件模型後,我們就可以在 src/models/map.ts
建立一個事件分配器,把各個事件分別交給不同的事件處理器來處理
on(event: Event) {
const { _tag } = event;
if (_tag === "MoveEvent") this.onMove(event);
else if (_tag === "AttackEvent") this.onAttack(event);
else if (_tag === "PickEvent") this.onPick(event);
}
實作 onMove, onAttack, onPick 三種事件處理方式
private find(player: Player) {
return this.grid
.map((row, y) =>
row
.map((cell, x) =>
cell.some((item) => item.id === player.id)
? ([x, y] as const)
: undefined
)
.find((cell) => cell !== undefined)
)
.find((row) => row !== undefined);
}
private onMove({ player, direction }: MoveEvent) {
const position = this.find(player);
if (!position) return;
const [x, y] = position;
this.grid[y][x] = this.grid[y][x].filter((item) => item.id !== player.id);
if (direction === "left") {
this.grid[y][x - 1].push(player);
} else if (direction === "right") {
this.grid[y][x + 1].push(player);
}
}
private onAttack({ player, range, skill }: AttackEvent) {
const position = this.find(player);
if (!position) return;
const [x, y] = position;
const { dx, dy } = range;
const xStart = Math.min(x, x + dx);
const xEnd = Math.max(x, x + dx);
const yStart = Math.min(y, y + dy);
const yEnd = Math.max(y, y + dy);
for (let i = xStart; i < xEnd; i++) {
for (let j = yStart; j < yEnd; j++) {
this.grid[j][i] = this.grid[j][i]
.map((item) => {
if (item instanceof Monster) {
console.log(`${player.name}使用${skill}擊敗了${item.name}`);
return item.drop();
} else {
return item;
}
})
.filter((item) => item !== undefined);
}
}
}
private onPick({ player }: PickEvent) {
const position = this.find(player);
if (!position) return;
const [x, y] = position;
this.grid[y][x] = this.grid[y][x]
.map((item) => {
if (item instanceof Dropped) {
console.log(`${player.name}撿走了${item.name}`);
return undefined;
} else {
return item;
}
})
.filter((item) => item !== undefined);
}
最後 index.ts 看起來會像是這樣
const map = new Map("森林小徑1", { colums: 50, rows: 1 });
const 煞氣a刀賊 = new Player("煞氣a刀賊");
const 乂正義黑騎乂 = new Player("乂正義黑騎乂");
const 紅寶 = new Monster("紅寶");
const 藍寶 = new Monster("藍寶");
const 刀賊向左走 = new MoveEvent(煞氣a刀賊, "left");
const 黑騎向右走 = new MoveEvent(乂正義黑騎乂, "right");
const 刀賊迴旋斬 = new AttackEvent(煞氣a刀賊, "迴旋斬", { dx: -1, dy: 1 });
const 刀賊撿取 = new PickEvent(煞氣a刀賊);
map.add(煞氣a刀賊, { x: 49, y: 0 });
map.add(乂正義黑騎乂, { x: 0, y: 0 });
map.add(紅寶, { x: 25, y: 0 });
map.add(藍寶, { x: 37, y: 0 });
for (let i = 0; i < 11; i++) {
map.on(刀賊向左走);
map.on(黑騎向右走);
}
map.on(刀賊迴旋斬);
map.on(刀賊向左走);
map.on(刀賊撿取);
是不是感覺越來越有畫面感了呢