GifBird
オッコードが綺麗だtakker.icon
コメントまでついてる!贅沢!
古のJSって感じですね・・(もはや何も覚えてない)/villagepump/inajob.icon
これをrewriteして描画周りの処理の書き方を学ぶのよさそう
ゲームというUIで動画を作る動画編集アプリと言えなくもないね/villagepump/inajob.icon というわけでここでcode readingしつつ書き換えてみる
2022-05-22
19:09:03 TSに書き換え中
下手にclassを消さないほうがいいかな
2022-05-20
08:53:30 方針
とりあえずすべて一つのhtmlファイルに収めて、ローカルでも動くようにしようかな
そのあとScrapboxでも動くようにする
Gyazoにuploadする機能までつけるとおもしろそう
code
不要な変数の削除
letとconstに置き換え
classに変換
これを自動で行えたのは助かった
code:script.ts
const loadImg = (name: string): Image => {
const img = new Image();
img.src = name;
return img;
};
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();
});
// グローバルにある特別なやつ
const canv: HTMLCanvasElement = document.getElementById("canv")!;
const ctx = canv.getContext("2d");
let time = 0;
let mousef = false;
let overTime = 0;
let animCount = 0;
//let daikonImg = loadImg('img/daikon.gif');
//let etsukoImg = loadImg('img/etsuko.gif');
interface Point {
x: number;
y: number;
}
interface Character {
position: Point;
velocity: Point;
acceleration: Point;
width: number;
height: number;
type: "me" | "";
}
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;
};
const draw = (chr: Character): 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;
}
};
class MainScene {
constructor() {
//
this.bullets = [];
this.my = new Chr(30, 50, 13 * 1.5, 13 * 1.5);
this.my.ay = 0.08;
this.my.type = "my";
this.bullets.push(
new Chr(0, 0, 120, 10),
new Chr(0, 160 - 10, 120, 10),
);
}
hitCheck() {
return this.bullets.some((bullet) => isHit(bullet, this.my));
}
tick() {
let result = true;
if (mousef) this.my.vy -= 0.16;
if (time % 40 == 0) animCount++;
if (time % 5 == 0 && mousef) animCount++;
// gen
if (time % 120 == 0) {
const SIZE = 50;
const r = Math.random() * (160 - 20 - SIZE) + 20;
const c = new Chr(120, 0, 5 * 1.5, r)
c.vx = -1;
this.bullets.push(c);
const d = new Chr(120, r + SIZE, 5 * 1.5, 160 - (r + SIZE))
d.vx = -1;
this.bullets.push(d);
}
// draw
if (overTime > 0) {
overTime--;
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 120, 160);
if (overTime == 0) result = false;
}
if (overTime == 0) this.my.tick();
this.my.draw();
if (overTime == 0) {
for (const v of this.bullets) {
v.tick();
if (this.hitCheck()) {
overTime = 20;
}
}
}
this.bullets.forEach((v, i, arr) => {
v.draw();
if (v.del) {
arr.splice(i, 1);
}
});
ctx.fillStyle = "white";
ctx.font = "12px 'sans-serif'";
ctx.fillText("score: " + (time / 120 | 0), 10, 20);
if (time < 100) {
ctx.fillStyle = "white";
ctx.font = "30px 'sans-serif'";
ctx.fillText("GifBird", 10, 100);
}
if (time % 5 == 0 || overTime != 0) {
addFrame(ctx);
}
return result;
}
}
const blobToDataURL = async (blob): Promise<string> => {
const reader = new FileReader();
const promise = new Promise<string>((resolve, reject) => {
reader.addEventListener("load", (e) => {
reader.removeEventListener("erorr", reject);
resolve(e.target.result as string);
}, { once: true });
reader.addEventListener("error", reject);
});
reader.readAsDataURL(blob);
return await promise;
};
$(function () {
//$('.over .title').hide();
$("#loading").hide();
$(".over .gameover").hide();
$(".js-retry").bind("pointerdown", () => {
$("#screenshot").empty();
start();
});
$(".js-title").bind("pointerdown", () => {
$(".over .gameover").hide();
$(".over .title").show();
});
$(document.body).bind("pointerdown", () => {
mousef = true;
});
$(document.body).bind("pointerup", () => {
mousef = false;
});
$(document.body).bind("touchstart touchmove mousemove", (e) => {
if (e.type == "touchmove") {
e.preventDefault();
}
});
function start() {
initGif();
$(".over .title").hide();
$(".over .gameover").hide();
time = 0;
overTime = 0;
const scene = new MainScene();
await new Promise((resolve) => setTimeout(resolve, 10));
// 初期化
ctx.fillStyle = "#444";
ctx.fillRect(0, 0, 240, 320);
if (overTime == 0) time++;
while (scene.tick()) {
await new Promise((resolve) => setTimeout(resolve, 10));
}
ctx.fillStyle = "#444";
ctx.fillRect(0, 0, 240, 320);
const score = (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 = $("<img>");
img.attr("src", URL.createObjectURL(blob));
$("#screenshot").append(img);
const link = $("<a>").text("download");
link.attr("href", URL.createObjectURL(blob));
link.attr("download", "gifbird.gif");
$("#screenshot").append(link);
//window.open(URL.createObjectURL(blob));
const url = await blobToDataURL(blob);
$("#bin").val(url.replace(/^data:image\/gif;base64,/, ""));
}
});
CSSとHTML
元ページを参照
このゲーム難しいtakker.icon
マウス操作のタイミングがシビア
壁の穴に来るよう高度を制御するのが困難
すぐ天井や壁にぶつかってしまう