generated at
GIfBirdをいじる
> オッコードが綺麗だtakker
>   コメントまでついてる!贅沢!
>   古のJSって感じですね・・(もはや何も覚えてない)inajob
>  これをrewriteして描画周りの処理の書き方を学ぶのよさそう
>   教科書の数式を自分の記号体系に翻訳して理解するパターン
>  ゲームというUIで動画を作る動画編集アプリと言えなくもないねinajob
というわけでここでcode readingしつつ書き換えてみる

2022-09-10
17:20:24 global変数の有効範囲と実際に使われている範囲をなるべく等しくした
あといらないコードを削った
16:43:20 /takker/GifBirdから井戸端に持ってきた
2022-05-22
19:09:03 TSに書き換え中
下手にclassを消さないほうがいいかな
2022-05-20
08:53:30 方針
とりあえずすべて一つのhtmlファイルに収めて、ローカルでも動くようにしようかな
そのあとScrapboxでも動くようにする
Gyazoにuploadする機能までつけるとおもしろそう

code
deno fmtしたあとdeno lspの警告を全部消した
不要な変数の削除
let const に置き換え
classに変換
これを自動で行えたのは助かった
JQueryとgif.jsに依存している
ゲーム内のオブジェクトの定義・\varDelta t間の座標移動・衝突判定
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変換関数
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(); });
オブジェクトの描画
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 birdImgs = [loadImg("img/bird1.gif"), loadImg("img/bird2.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( birdImgs[animCount % 2], 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; } };
script.ts
/** マウスが押されているかどうか */ let mousef = false; document.body.addEventListener("pointerdown", () => { mousef = true; }); document.body.addEventListener("pointerup", () => { mousef = false; });
ゲームループ管理
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で管理するようにしたい
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
マウス操作のタイミングがシビア
壁の穴に来るよう高度を制御するのが困難
すぐ天井や壁にぶつかってしまう