GIfBirdをいじる
オッコードが綺麗だtakker.icon
コメントまでついてる!贅沢!
古のJSって感じですね・・(もはや何も覚えてない)inajob.icon
これをrewriteして描画周りの処理の書き方を学ぶのよさそう
ゲームというUIで動画を作る動画編集アプリと言えなくもないねinajob.icon というわけでここでcode readingしつつ書き換えてみる
2022-09-10
17:20:24 global変数の有効範囲と実際に使われている範囲をなるべく等しくした
あといらないコードを削った
2022-05-22
19:09:03 TSに書き換え中
下手にclassを消さないほうがいいかな
2022-05-20
08:53:30 方針
とりあえずすべて一つのhtmlファイルに収めて、ローカルでも動くようにしようかな
そのあとScrapboxでも動くようにする
Gyazoにuploadする機能までつけるとおもしろそう
code
不要な変数の削除
letとconstに置き換え
classに変換
これを自動で行えたのは助かった
ゲーム内のオブジェクトの定義・$ \varDelta t間の座標移動・衝突判定
code:script.ts
interface Point {
x: number;
y: number;
}
interface Character {
position: Point;
velocity: Point;
acceleration: Point;
width: number;
height: number;
type: "me" | "";
}
const makeCharacter = (position: Point, width: number, height: number ): Character => ({
position,
velocity: { x: 0, y: 0 },
acceleration: { x: 0, y: 0 },
width, height, type: "",
});
const tick = (chr: Character): Character => {
const newChr = {
position: {
x: chr.position.x + chr.velocity.x,
y: chr.position.y + chr.velocity.y,
},
velocity: {
x: chr.velocity.x + chr.acceleration.x,
y: chr.velocity.y + chr.acceleration.y,
},
acceleration: {
x: chr.acceleration.x,
y: chr.acceleration.y,
},
width: chr.width,
height: chr.height,
};
newChr.position.x = Math.max(Math.min(-3, newChr.position.x), 3);
newChr.position.y = Math.max(Math.min(-3, newChr.position.y), 3);
return newChr;
};
const isOutOfArea = (chr: Character): boolean =>
chr.position.x + chr.width < 0 || chr.position.x > 240 ||
chr.position.y + chr.height < 0 || chr.position.y > 320;
const isHit = (chr1: Character, chr2: Character): boolean => {
const lx = Math.min(chr1.position.x + chr1.width, chr2.position.x + chr2.width); // 最も左にある右側
const rx = Math.max(chr1.position.x, chr2.position.x); // 最も右にある左側
const ty = Math.min(chr1.position.y + chr1.height, chr2.position.y + chr2.height); // 最も上にある下側
const by = Math.max(chr1.position.y, chr2.position.y); // 最も下にある上側
return rx < lx && by < ty;
};
GIF変換関数
code:script.ts
const gif = new GIF({
workers: 2,
quality: 10,
width: 120,
height: 160,
workerScript: "js/gif.worker.js",
});
const addFrame = (ctx: CanvasRenderingContext2D): void =>
gif.addFrame(ctx, { copy: true, delay: 10 });
const render = async (): Promise<Blob> => new Promise((resolve) => {
gif.once("finished", (blob) => resolve(blob));
gif.render();
});
オブジェクトの描画
code:script.ts
const loadImg = (name: string): Image => {
const img = new Image();
img.src = name;
return img;
};
//const daikonImg = loadImg('img/daikon.gif');
//const etsukoImg = loadImg('img/etsuko.gif');
const draw = (chr: Character, ctx: CanvasRenderingContext2D, animCount: number): void => {
switch (this.type) {
case "my":
ctx.fillStyle = "yellow";
ctx.fillRect(this.position.x, this.position.y, this.width, this.height);
ctx.drawImage(
this.position.x - 2,
this.position.y - 2,
this.width + 4,
this.height + 4,
);
break;
default:
ctx.fillStyle = "green";
ctx.fillRect(this.position.x, this.position.y, this.width, this.height);
break;
}
};
code:script.ts
/** マウスが押されているかどうか */
let mousef = false;
document.body.addEventListener("pointerdown", () => { mousef = true; });
document.body.addEventListener("pointerup", () => { mousef = false; });
ゲームループ管理
code:script.ts
class MainScene {
public time = 0;
public overTime = 0;
private animCount = 0;
private bullets = [
makeCharacter({ x: 0, y: 0 }, 120, 10),
makeCharacter({ x: 0, y: 160 }, - 10, 120, 10),
];
constructor(private ctx: CanvasRenderingContext2D) {
this.my = makeCharacter(30, 50, 13 * 1.5, 13 * 1.5);
this.my.ay = 0.08;
this.my.type = "my";
}
tick() {
if (mousef) this.my.vy -= 0.16;
if (this.time % 40 == 0) this.animCount++;
if (this.time % 5 == 0 && mousef) this.animCount++;
// 障害物を作る
if (this.time % 120 == 0) {
const SIZE = 50;
const r = Math.random() * (160 - 20 - SIZE) + 20;
{
const wall = makeCharacter(120, 0, 5 * 1.5, r)
wall.vx = -1;
this.bullets.push(wall);
}
{
const wall = makeCharacter(120, r + SIZE, 5 * 1.5, 160 - (r + SIZE))
wall.vx = -1;
this.bullets.push(wall);
}
}
// draw
let result = true;
if (this.overTime > 0) {
this.overTime--;
this.ctx.fillStyle = "red";
this.ctx.fillRect(0, 0, 120, 160);
if (this.overTime == 0) result = false;
}
if (this.overTime == 0) tick(this.my);
draw(this.my, this.ctx, this.animCount);
if (this.overTime == 0) {
for (const v of this.bullets) {
tick(v);
if (!.bullets.some((bullet) => isHit(bullet, this.my))) continue;
this.overTime = 20;
}
}
for (const v of this.bullets) {
draw(v, this.ctx, this.animCount);
if (!v.del) continue;
this.bullets.splice(this.bullets.indexOf(v), 1);
}
this.ctx.fillStyle = "white";
this.ctx.font = "12px 'sans-serif'";
this.ctx.fillText(score: ${(this.time / 120 | 0)}, 10, 20);
if (time < 100) {
this.ctx.fillStyle = "white";
this.ctx.font = "30px 'sans-serif'";
this.ctx.fillText("GifBird", 10, 100);
}
if (this.time % 5 == 0 || this.overTime != 0) {
addFrame(this.ctx);
}
return result;
}
}
ゲーム開始処理とか
描画処理は一箇所に集め、stateで管理するようにしたい
code:script.ts
//$('.over .title').hide();
$("#loading").hide();
$(".over .gameover").hide();
const title = document.getElementsByClassName("js-title")!.0; title.addEventListener("pointerdown", () => {
$(".over .gameover").hide();
$(".over .title").show();
});
document.body.addEventListener("touchmove", (e) => { e.preventDefault(); });
const canvas = document.getElementById("canv")!;
const ctx = canvas.getContext("2d");
const retry = document.getElementsByClassName("js-retry")!.0; retry.addEventListener("pointerdown", () => {
$(".over .title").hide();
$(".over .gameover").hide();
const scene = new MainScene(ctx);
await new Promise((resolve) => setTimeout(resolve, 10));
// 初期化
ctx.fillStyle = "#444";
ctx.fillRect(0, 0, 240, 320);
if (scene.overTime == 0) scene.time++;
// TODO: requestAnimationFrame()に書き換える
while (scene.tick()) {
await new Promise<void>((resolve) => setTimeout(resolve, 10));
}
ctx.fillStyle = "#444";
ctx.fillRect(0, 0, 240, 320);
const score = (scene.time / 120 | 0);
$(".js-over-logo").text(score:${score);
$(".over .gameover").fadeIn();
$(".retry").hide();
$("#screenshot").text("Generating GIF Animation..");
const blob = await render();
$(".retry").show();
$("#screenshot").empty();
const img = document.createElement("img");
img.src = URL.createObjectURL(blob);
$("#screenshot").append(img);
const link = document.createElement("a");
link.textContent = "download";
link.href = img.src;
link.download = "gifbird.gif";
$("#screenshot").append(link);
});
CSSとHTML
元ページを参照
このゲーム難しいtakker.icon
マウス操作のタイミングがシビア
壁の穴に来るよう高度を制御するのが困難
すぐ天井や壁にぶつかってしまう