Skip to content

Instantly share code, notes, and snippets.

@junpeitsuji
Created November 25, 2013 11:07
Show Gist options
  • Save junpeitsuji/7639753 to your computer and use it in GitHub Desktop.
Save junpeitsuji/7639753 to your computer and use it in GitHub Desktop.
「Boids シミュレーション をブラウザで」 をゲーム化しました。コピペして遊んでください。
<!DOCTYPE html>
<meta charset="UTF-8">
<title>Boids Sims.</title>
<h1>Boids Sims.</h1>
<ul>
<li>ブラウザ上の画面をクリックして、<b>青い丸(えさ)</b>の位置を変えます。</li>
<li><b>赤い丸(ボイド)</b>が<b>えさ</b>に群がります。</li>
<li><b>ボイド</b>が<b>緑色の丸(敵)</b>にぶつかると消えてしまいます。</li>
<li>すべて消えてしまうと<b>ゲームオーバー</b>。ゲームが続いた時間が長ければ長いほど高いスコアになります。</li>
<li>えさでうまく誘導してボイドの群れを生き延びさせましょう!</li>
</ul>
<canvas id="canvas" width="640" height="480"></canvas>
<p>YOUR SCORE IS <span id="score"></span></p>
<style>
/**
* html body に設定される自然な
* margin padding を除去する
*/
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
li {
color: #444;
}
h1 {
color: #444;
}
p {
color: #888;
font-size: xx-large;
}
p #score {
color: #F00;
}
canvas {
background-color: #CCC;
}
</style>
<script src="http://code.jquery.com/jquery-1.8.3.min.js"></script>
<script type="text/javascript">
$(function() {
// コンテキストを取得する関数
function getCtx() {
var canvas = document.getElementById('canvas');
return canvas.getContext('2d');
};
// グローバル変数の初期化
var width = canvas.width;
var height = canvas.height;
var canvasRect = canvas.getBoundingClientRect()
// 敵キャラ
var enemies = new Array(2);
enemies[0] = {
x: 100,
y: 100,
radious: 40
};
enemies[1] = {
x: 500,
y: 400,
radious: 60
};
var target = {
x: width/2,
y: height*4/5
};
// ゲーム状態
const GAME_CONTEXT_FLAG_GAMERUNNING = 0;
const GAME_CONTEXT_FLAG_GAMEOVER = 1;
var gameContextFlag = GAME_CONTEXT_FLAG_GAMERUNNING;
// ゲームスコア
var score = 0;
// 制限速度
var MAXSPEED = 5;
// boid の個体数
var numOfBoids = 100;
// boid の個体を管理する配列
var boids = new Array(numOfBoids);
/*
var Boid = (function(){
function Boid(id, x, y, vx, vy) {
this.id = id;
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
}
Boid.prototype.rule1 = function(){
var cx = 0;
var cy = 0;
for( var j = 0; j < numOfBoids; j++ ){
if( i != j ){
cx += boids[ j ].x;
cy += boids[ j ].y;
}
}
cx /= numOfBoids - 1;
cy /= numOfBoids - 1;
this.vx += ( cx - this.x ) / 1000;
this.vy += ( cy - this.y ) / 1000;
}
return Boid;
})();
*/
// 初期処理
for(var i=0; i<numOfBoids; i++){
boids[i] = {
x: width/2,
y: height/2,
vx: 0,
vy: 0,
alive: true
};
}
var Boids = {
rule1: function(i){ // すべてのBoidは群の中心に向かおうとする
var cx = 0;
var cy = 0;
var aliveCount = 0; // 生きている boid の数を数える
for( var j = 0; j < numOfBoids; j++ ){
if( i != j ){
if( boids[j].alive ){
cx += boids[ j ].x;
cy += boids[ j ].y;
aliveCount++;
}
}
}
// 残りの boids の数が 2 以上のととき
if( aliveCount > 1 ){
cx /= aliveCount - 1;
cy /= aliveCount - 1;
boids[ i ].vx += ( cx - boids[ i ].x ) / 1000;
boids[ i ].vy += ( cy - boids[ i ].y ) / 1000;
}
},
rule2: function(i){ // 他の個体と距離をとろうとする
var vx = 0;
var vy = 0;
for( var j = i + 1; j < numOfBoids; j++ ){
if( boids[j].alive ){
var dx = boids[ j ].x - boids[ i ].x;
var dy = boids[ j ].y - boids[ i ].y;
var dist = Math.sqrt( dx * dx + dy * dy );
if( dist < 15 ){
dist += 0.001;
vx -= dx / dist;
vy -= dy / dist;
}
}
}
boids[ i ].vx += vx;
boids[ i ].vy += vy;
},
rule3: function(i){ // 他の個体と向きと速度を合わせようとする
var pvx = 0;
var pvy = 0;
var aliveCount = 0; // 生きている boids の数
for( var j = 0; j < numOfBoids; j++ ){
if( i != j ){
if( boids[j].alive ){
pvx += boids[ j ].vx;
pvy += boids[ j ].vy;
aliveCount++;
}
}
}
// 残りの boids の数が 2 以上のととき
if( aliveCount > 1 ) {
pvx /= aliveCount - 1;
pvy /= aliveCount - 1;
boids[ i ].vx += ( pvx - boids[ i ].vx ) / 10;
boids[ i ].vy += ( pvy - boids[ i ].vy ) / 10;
}
},
rule4: function(i){ // 移動領域を限定する
// 壁の近くでは方向反転
if( boids[i].x < 10 && boids[ i ].vx < 0 ){
boids[i].vx += 10 / ( Math.abs( boids[i].x ) + 1 );
}
else if( boids[i].x > width - 10 && boids[i].vx > 0 ){
boids[i].vx -= 10 / ( Math.abs( width - boids[i].x ) + 1 );
}
if( boids[i].y < 10 && boids[i].vy < 0 ){
boids[i].vy += 10 / ( Math.abs( boids[i].y ) + 1 );
}
else if( boids[i].y > height - 10 && boids[i].vy > 0 ){
boids[i].vy -= 10 / ( Math.abs( height - boids[i].y ) + 1 );
}
},
rule5: function(i){
var dx = target.x - boids[ i ].x;
var dy = target.y - boids[ i ].y;
var dist = Math.sqrt( dx * dx + dy * dy );
boids[ i ].vx += ( target.x - boids[ i ].x ) / 500;
if( boids[ i ].vx * ( target.x - boids[ i ].x ) < 0 ){
boids[ i ].vx += ( target.x - boids[ i ].x ) / 500;
}
boids[ i ].vy += ( target.y - boids[ i ].y ) / 500;
if( boids[ i ].vy * ( target.y - boids[ i ].y ) < 0 ){
boids[ i ].vy += ( target.y - boids[ i ].y ) / 500;
}
}
}
// メインの実行
main();
// クリック処理
$('canvas').click( function(event){
target.x = event.pageX - canvasRect.left;
target.y = event.pageY - canvasRect.top;
});
// ここからメイン
function main(){
var ctx = getCtx();
ctx.clearRect(0, 0, width, height);
draw();
update();
// 当たり判定
collisionDetection();
$('#score').text(score);
if( gameContextFlag == GAME_CONTEXT_FLAG_GAMERUNNING ){
score += 100;
setTimeout( main, 20 );
}
else if( gameContextFlag == GAME_CONTEXT_FLAG_GAMEOVER ) {
// Game Over
var text = "Game Over";
ctx.font = "72px 'MS Pゴシック'";
ctx.fillStyle = "#b00";
ctx.fillText(text, width/2 - 180, height/2);
ctx.fill();
ctx.font = "24px 'MS Pゴシック'";
ctx.fillStyle = "#b00";
ctx.fillText("press f5 to restart.", width/2 - 100, height/2+80);
ctx.fill();
ctx.fillStyle = 'rgba(192, 80, 77, 0.7)';
}
}
// とりあえずの変数
//var collisionTimer = 0;
// 当たり判定処理
function collisionDetection() {
/*
boids[collisionTimer] = false;
collisionTimer++;
*/
// 当たり判定
boids.forEach( function(boid){
enemies.forEach( function(enemy){
var dist = (boid.x-enemy.x)*(boid.x-enemy.x) + (boid.y-enemy.y)*(boid.y-enemy.y);
if( dist < enemy.radious*enemy.radious ){
boid.alive = false;
}
});
});
// 全個体が死んだらゲームオーバー
var aliveCount = 0;
boids.forEach( function(boid){
if( boid.alive ){
aliveCount++;
}
});
if( aliveCount <= 0 ) {
gameContextFlag = GAME_CONTEXT_FLAG_GAMEOVER;
}
}
function draw(){
var ctx = getCtx();
// boid の描画
boids.forEach( function(boid){
if( boid.alive ){
fillOval(ctx, boid.x, boid.y, 6, 'rgba(192, 80, 77, 0.7)');
}
});
// 敵キャラの描画
enemies.forEach( function(enemy){
fillOval(ctx, enemy.x, enemy.y, enemy.radious, 'rgba(30, 192, 77, 0.7)');
});
// target の描画
fillOval(ctx, target.x, target.y, 20, 'rgba(0, 80, 192, 0.7)');
}
function update(){
for(var i=0; i<numOfBoids; i++){
if( boids[i].alive ){
Boids.rule1(i);
Boids.rule2(i);
Boids.rule3(i);
Boids.rule4(i);
Boids.rule5(i);
// 速さを制限する
var l = Math.sqrt( boids[ i ].vx * boids[ i ].vx + boids[ i ].vy * boids[ i ].vy );
if( l > MAXSPEED ){
boids[ i ].vx *= MAXSPEED / l;
boids[ i ].vy *= MAXSPEED / l;
}
// 位置の更新
boids[ i ].x += boids[ i ].vx;
boids[ i ].y += boids[ i ].vy;
}
}
enemies.forEach( function(enemy){
var dx = Math.random()*40 - 20;
var dy = Math.random()*40 - 20;
if( enemy.x+dx > 0 && enemy.x+dx < width
&& enemy.y+dy > 0 && enemy.y+dy < height ){
enemy.x += dx;
enemy.y += dy;
}
} );
}
// 円を描く
function fillOval(ctx, x, y, radious, fillStyle){
ctx.fillStyle = fillStyle;
ctx.beginPath();
ctx.arc(x, y, radious, 0, Math.PI*2, false);
ctx.fill();
}
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment