在 Space Invaders
的游戏设计中,
Enemy
的移动逻辑扮演了非常重要的角色,
他为游戏提供了难度,并随着玩家每次击杀 Enemy
增加难度,
是这款游戏最关键的游戏设计。
接下来,卡比要实作原作游戏中,Enemy
的移动逻辑。
首先,为了让游戏逻辑能够操作特定的游戏物件,
我们需要新增一个概念,tags
。
透过 tags
,
我们可以知道当前的游戏物件是什麽,
并对其提供相对应的操作。
-- src/types.ts
export interface GameObject {
tags?: string[];
destroy?: boolean;
update?(delta: number): void;
}
为了要将游戏逻辑集中在一处,
我们需要将 Enemy
的逻辑做些调整。
透过分析原作,卡比发现,
Enemy
会在每次移动时才会切换图片,
於是我们要设计一个 Proxy
,外面的逻辑可以透过操作 Proxy
来操作图片。
并且需要一个 id
,
这个在我们逻辑操作时方便我们知道当前物件的位置以及确定个数。
export type IEnemy = GameObject &
Transform &
Renderer &
Collision &
Shooter & { id: number; frame: number };
export default function Enemy({ type, id, position }: EnemyProps): IEnemy {
const images = EnemyImages[type];
let current = 0;
return {
id,
tags: ["enemy"],
position,
set frame(value) {
current = value % images.length;
this.renderer.src = images[current];
},
get frame() {
return current;
},
canShoot: false,
shoot() {
const { x, y } = this.position;
const [w, h] = [images[0].length, images.length];
return EnemyLaser({
position: { x: x + w / 2, y: y + h + 1 },
update(it) {
it.position.y += 1;
},
});
},
renderer: {
type: "graphics",
src: images[current],
},
collider: {
size: { x: images[0].length, y: images.length },
},
};
}
根据原作,Enemy
原先的位置决定了其移动的顺序,
为了复刻原作 按照顺序一个接一个的移动,
要透过 id
来对应 Enemy
原先的位置。
对照表示意如下:
[
[44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54],
[33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43],
[22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32],
[11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21],
[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
]
实作如下:
-- src/scenes/Game.ts
const GRID_SIZE = 16;
const ROW_WIDTH = 11;
const points: EnemyProps[][] = [
"squid",
"crab",
"crab",
"octopus",
"octopus",
].map((type, y, list) =>
Array.from({ length: ROW_WIDTH }, (_, x) => ({
id: (list.length - 1 - y) * ROW_WIDTH + x,
type: type as EnemyTypes,
position: { x: x * GRID_SIZE, y: y * GRID_SIZE },
}))
);
为了集中管理游戏逻辑,我们要开一个新的资料夹。
在 src
底下建立一个新的资料夹 logic
并建立一个档案 SequentialMovement.ts
。
SequentialMovement
是一个 higherOrderFunction
,
他会回传一个 update
函式,用於每次刷新时执行。
SequentialMovement
会负责提供 Enemy
在游戏中的移动行为 ,细节如下:
Enemy
并横向移动。实作如下:
-- src/logic/SequentialMovement.ts
import { isEnemy } from "../characters/Enemy";
import { GameObject } from "../types";
type Props = {
counts: number;
step: number;
};
export function SequentialMovement({ counts, step }: Props) {
const movement = { x: step, y: 0 };
let pedometer = 0;
let index = 0;
return (instances: GameObject[]) => {
const enemies = instances.filter(isEnemy);
let processed = enemies.length > 0;
while (processed) {
enemies
.filter((instance) => instance.id === index)
.forEach((instance) => {
instance.position.x += movement.x;
instance.position.y += movement.y;
instance.frame += 1;
processed = false;
});
index = (index + 1) % counts;
}
if (index === 0) {
if (pedometer === 0) movement.y = 0;
pedometer += 1;
}
if (pedometer <= 10) return;
movement.x *= -1;
movement.y = step;
pedometer = 0;
};
}
接着我们只要在 Game
套上逻辑即可。
-- src/scenes/Game.ts
export default function Game(screen: Rectangle): Scene<Container> {
let instances: GameObject[] = [LaserCannon(screen), ...spawn(Enemy, points)];
const update = SequentialMovement({
counts: instances.filter(isEnemy).length,
step: 2,
});
return {
update(delta) {
collisionDetect(instances.filter(canCollision).filter(canTransform));
update(instances);
instances.forEach((instance) => {
if (canControl(instance)) {
instance.handleInput(getKeyPressed());
}
if (canShoot(instance) && instance.canShoot) {
requestAnimationFrame(() => {
instances = [...instances, instance.shoot()];
});
instance.canShoot = false;
}
if (instance.destroy) {
requestAnimationFrame(() => {
instances = instances.filter((_instance) => _instance !== instance);
});
return;
}
instance.update?.(delta);
});
},
render(stage) {
clear();
instances
.filter(canRender)
.forEach((instance) => render(stage, instance));
},
};
}
>>: Day 5 - Using Argon2 for Password Verifying with ASP.NET Web Forms C# 使用 Argon2 验证密码
Express 利用 pm2 做管理(因为 docker 坑很深 加上来的话会写不完) Expres...
自动更新每日个股日成交资讯 结合前几篇所学,我们来做一个可以自动更新日成交资讯的程序吧! Reque...
LINE Developers:https://developers.line.biz/zh-ha...
这个的上一篇:https://ithelp.ithome.com.tw/articles/10283...
● 这章将以模拟帐户来示范如何取得期货(Futures)资讯 回顾上一章,我们学习如何用Contra...