接下来我们要让 Laser
打中敌人时,将敌人消灭。
Laser
要有办法侦测到目前打中谁,
所以需要一个新的功能来检测目前哪几个物件是互相重叠的,
这个就是 撞击检测 (Collision Detection)
Collision Detection 根据形状的不同,实作的演算方式也不同。
这边卡比用相对简单的 Rect to Rect
检测即可。
大致的演算方式如下:
interface Rect {
x: number;
y: number;
w: number;
h: number;
}
function points({ x, y, w, h }: Rect) {
return [x, x + w, y, y + h];
}
function hitTest(o1: Rect, o2: Rect) {
const [a1, a2, a3, a4] = points(o1);
const [b1, b2, b3, b4] = points(o2);
return [
a2 >= b1,
a1 <= b2,
a4 >= b3,
a3 <= b4,
//
].every(Boolean);
}
export function collisionDetect(instances: GameObject[]) {
for (let i = 0; i < instances.length; i++) {
for (let j = i + 1; j < instances.length; j++) {
if (hitTest(o1, o2)) {
//...
}
}
}
}
接下来就是要将我们的撞击逻辑元件化,
我们需要新增一个元件 Collider
,
用於提供我们撞击检测中需要的资料,像是 width
和 height
。
export interface Collision {
collider: {
size: Vector;
};
}
export function canCollision<T extends GameObject>(
instance: T
): instance is T & Collision {
return "collider" in instance;
}
接着让 Laser
跟 Squid
有办法相撞。
function Laser({
x,
y,
}: Vector): GameObject & Transform & Renderer & Collision {
return {
renderer: {
type: "graphics",
src: [[1], [1], [1], [1]],
},
position: { x, y },
update() {
this.position.y -= 1;
},
collider: {
size: { x: 1, y: 4 },
},
};
}
export default function Squid(): GameObject & Transform & Renderer & Collision {
const images = [image1, image2];
let current = 0;
let timePass = 0;
return {
position: { x: 0, y: 0 },
update(delta) {
timePass += delta;
if (timePass > 1000) {
current += 1;
timePass = 0;
this.renderer.src = images[current % images.length];
}
},
renderer: {
type: "graphics",
src: images[current % images.length],
},
collider: {
size: { x: image1[0].length, y: image1.length },
},
};
}
接着,在 Game
加入 collisionDetect
。
export default function Game(screen: Rectangle): Scene<Container> {
let instances: GameObject[] = [LaserCannon(screen), Squid()];
return {
update(delta) {
collisionDetect(instances.filter(canCollision).filter(canTransform));
instances.forEach((instance) => {
if (canControl(instance)) {
instance.handleInput(getKeyPressed());
}
if (canShoot(instance) && instance.canShoot) {
requestAnimationFrame(() => {
instances = [...instances, instance.shoot()];
});
instance.canShoot = false;
}
instance.update?.(delta);
});
},
render(stage) {
instances
.filter(canRender)
.forEach((instance) => render(stage, instance));
},
};
}
将 Rect
移动到 types
。
export interface Rect extends Vector {
w: number;
h: number;
}
接着,将实作完刚才的 collisionDetect
函式。
export function collisionDetect(instances: (Collision & Transform)[]) {
for (let i = 0; i < instances.length; i++) {
for (let j = i + 1; j < instances.length; j++) {
const A = instances[i];
const B = instances[j];
const o1 = {
x: A.position.x,
y: A.position.y,
w: A.collider.size.x,
h: A.collider.size.y,
};
const o2 = {
x: B.position.x,
y: B.position.y,
w: B.collider.size.x,
h: B.collider.size.y,
};
if (hitTest(o1, o2)) {
console.log("hit");
}
}
}
}
<<: [Day10] Google Cloud Platform 简介
前言 常见的市场热络程度,也有用成交金额判断,当大家踊跃交易的时候,容易价格抬升。试想如果,大家预期...
前言 接续着上篇,这篇要说明的是如何自动导出hprof文件,针对自动导出又有分两种状况:已是运行中的...
DAY16 MongoDB Explain 与 Index 建议 MongoDB explain -...
今日文章目录 应用情境 事前准备 CSS 说明 参考资料 应用情境 针对重复性的资料流中,指定其中...
今天的范例也有结合昨天的程序码,要聊聊直接在介面上做增减,等不及的话就赶快往下滑吧~ ♠♣今天的文章...