はじめに
こんにちは!@70days_jsです。
オブジェクト(プロトタイプベース)を使って飼育ゲームを作りました。
飼育するのは黒い玉です。(たまに希少種として色違いが生まれるよう設定)
58日目。(2019/12/15)
よろしくお願いします。
サイトURL
やったこと
何が起きているか箇条書きします。
- 玉は画面内のみ動ける
- 玉には寿命がある(寿命を終えると消える)
- 玉は1/10以下の確率で色違いが生まれてくる
- 玉は檻を外すボタンを押すと画面外へ移動できる(topをすぎるとbottomに戻る、左右も同じ)
html↓
<body>
<canvas id="canvas"></canvas>
<input type="button" value="檻を外す" id="button" />
<input type="button" value="増やす" id="create" />
</body>
css↓
* {
margin: 0;
padding: 0;
}
# canvas {
display: block;
background: white;
}
# button {
position: absolute;
top: 10px;
left: 10px;
}
# create {
position: absolute;
top: 10px;
left: 100px;
}
JavaScript(かなり長いです。162行)↓
//全体で使う変数宣言(カンマ区切りで変数宣言が可能!!)
let canvas = document.getElementById("canvas"),
button = document.getElementById("button"),
create = document.getElementById("create"),
canvasContext = canvas.getContext("2d"),
radius = 10,
hairetu = [], //obj格納用の配列
width = window.innerWidth,
height = window.innerHeight,
number = 2,
maxLife = 500,
flag = true;
//_____画面サイズ変更に対応するための処理___________
canvas.width = width;
canvas.height = height;
window.addEventListener("resize", function resize() {
Width = window.innerWidth;
height = window.innerHeight;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
//檻をつける or つけないに対応するための処理
button.addEventListener("click", function() {
if (flag) {
flag = false;
button.value = "檻をつける";
} else {
flag = true;
button.value = "檻を外す";
}
});
//Animalを増やす処理
create.addEventListener("click", createAnimal);
//_______________初期化__________________________
function Animal(canvasContext, positionX, positionY) {
this.canvasContext = canvasContext;
this.initialize(positionX, positionY);
}
Animal.prototype.initialize = function(positionX, positionY) {
this.positionX = positionX;
this.positionY = positionY;
this.starLife = Math.ceil(maxLife * Math.random());
this.life = this.starLife;
if (Math.ceil(Math.random() * 10) === 1) {
this.color = {
r: 0,
g: Math.floor(Math.random() * 255),
b: Math.floor(Math.random() * 255),
a: 1
};
this.velocity = {
x: Math.random() * 10,
y: Math.random() * 10
};
this.rare = 1;
} else {
this.color = {
r: 200,
g: 0,
b: 0,
a: 1
};
this.velocity = {
x: Math.random() * 8,
y: Math.random() * 8
};
this.rare = 0;
}
};
//_________________各種メソッド_____________________
function render() {
canvasContext.clearRect(0, 0, width, height);
canvasContext.globalCompositeOperation = "lighter";
hairetu.forEach(function(obj) {
obj.render();
});
requestAnimationFrame(render); //自動でアニメーションしてくれる関数
}
Animal.prototype.render = function() {
this.draw();
this.updatePosition();
this.updateParams();
if (flag) {
this.limitPosition();
} else {
this.connectPosition();
}
};
Animal.prototype.draw = function() {
ctx = this.canvasContext;
ctx.beginPath();
ctx.arc(this.positionX, this.positionY, radius, Math.PI * 2, false);
ctx.fillStyle = this.updateParams();
ctx.fill;
ctx.fill();
ctx.closePath();
};
Animal.prototype.updatePosition = function() {
this.positionX += this.velocity.x;
this.positionY += this.velocity.y;
var ratio = this.life / this.starLife;
this.color.a = 1 + ratio;
this.life -= 1;
};
Animal.prototype.updateParams = function() {
var col = this.color.r + ", " + this.color.g + ", " + this.color.b;
var g = this.canvasContext.createRadialGradient(
this.positionX,
this.positionY,
0,
this.positionX,
this.positionY,
radius
);
g.addColorStop(0, "rgba(" + col + ", " + this.color.a * 1 + ")");
g.addColorStop(0.5, "rgba(" + col + ", " + this.color.a * 0.8 + ")");
g.addColorStop(1.0, "rgba(" + col + ", " + this.color.a * 0 + ")");
return g;
};
Animal.prototype.limitPosition = function() {
if (this.positionX > width - radius || 0 >= this.positionX - radius)
this.velocity.x = -this.velocity.x;
if (this.positionY > height - radius || 0 >= this.positionY - radius)
this.velocity.y = -this.velocity.y;
};
Animal.prototype.connectPosition = function() {
if (this.positionX < 0) this.positionX = width;
if (this.positionX > width) this.positionX = 0;
if (this.positionY < 0) this.positionY = height;
if (this.positionY > height) this.positionY = 0;
};
//______________実行__________________________
function createAnimal() {
var startPositionX = Math.random() * width,
startPositionY = Math.random() * height;
if (0 > startPositionX - radius) startPositionX += radius;
if (startPositionX >= width - radius) startPositionX -= radius;
if (0 > startPositionY - radius) startPositionY += radius;
if (startPositionY >= height - radius) startPositionY -= radius;
obj = new Animal(canvasContext, startPositionX, startPositionY);
hairetu.push(obj);
if (obj.rare === 1) alert("希少種が生まれました!");
}
for (var i = 0; i < number; i++) {
createAnimal();
}
render();
大体のやることはコメントに書いています。
ポイントを説明してみると、寿命はオブジェクトを削除しているのではなく、alpha値を0にしただけです。(つまり見えないだけで動いてはいる)↓
Animal.prototype.updatePosition = function() {
...
var ratio = this.life / this.starLife;//←ratioの比率はマイナスに増えていく
this.color.a = 1 + ratio;//←マイナスを足しているのでalpha値が0に近づき見えなくなる
this.life -= 1;
};
希少種が1/10以下の確率で生まれてくる部分はこれです。↓
if (Math.ceil(Math.random() * 10) === 1) {
...
ceilは切り上げをしています。randomは0~1までの値を返すので、1/10以下の確率ということになります。
メモ
今日勉強したことのメモを書いておきます。
主題: コールバック関数について
- Q. コールバック関数の引数は誰が入れている?
- ex: hoge.addEventListener('click', fuga(e));
- ↑このeは誰が?
- A. 呼び出し元の関数(この例だとaddEventListenerが入れている)
- 対処法: 混乱の原因はライブラリで知らぬ間に処理が行われいることにある。のでライブラリを調べるしかない
補足:
- Q. ちなみに呼び出し元の関数の名前は?
- A. 高階関数
function hoge(){ function(){} };
↑高階関数(呼び出し側) ↑コールバック関数(呼び出される側)
感想
コールバックの引数は今までずっと疑問だったので、理解することができてよかったです。
最後まで読んでいただきありがとうございます。明日も投稿しますのでよろしくお願いします。
参考
JavaScript中級者への道【5. コールバック関数】
ブレイクスルーJavaScript フロントエンドエンジニアとして越えるべき5つの壁―オブジェクト指向からシングルページアプリケーションまで(← 本を借りました。リンクはamazonページです)
大変勉強になりました。ありがとうございます!