背景
個人的にSVGに興味が出てきた+ゲームを作りたかったので作ってみた。
完成したものは下記
http://hasito.com/snake/
仕様
- 2Dゲーム
- 蛇が餌を食べると長くなる
- 壁/体にあたったら死ぬ
- 少しづつ動きが早くなる
- キーボードで操作
こんな感じ…
開発
枠を組む
SVGで下記のように書くと格子状にブロックを配置できる様子
<svg width="30" height="60">
<g transform={('translate(0,0)')}>
<rect x="0" y="0" width="15" height="15" title="" fill="#000000"></rect>
<rect x="0" y="15" width="15" height="15" title="" fill="#000000"></rect>
<rect x="0" y="30" width="15" height="15" title="" fill="#000000"></rect>
<rect x="0" y="45" width="15" height="15" title="" fill="#000000"></rect>
</g>
<g transform={('translate(15,0)')}>
<rect x="0" y="0" width="15" height="15" title="" fill="#000000"></rect>
<rect x="0" y="15" width="15" height="15" title="" fill="#000000"></rect>
<rect x="0" y="30" width="15" height="15" title="" fill="#000000"></rect>
<rect x="0" y="45" width="15" height="15" title="" fill="#000000"></rect>
</g>
</svg>
これをriot.jsで記載
<svg riot-width={ w*15 } riot-height={ h*15 } >
<g each={d,i in view} transform={('translate('+(i*15)+',0)')}>
<rect each={d2,i2 in d} x="0" riot-y={i2*15} data-point='{(JSON.stringify({x:i,y:i2}))}' width="15" height="15" title="" fill="#{('000000'+d2.toString(16)).slice(-6)}"></rect>
</g>
</svg>
注意点
-
view
には多次元配列が入っている予定 -
fill
は16進指定。6桁必要なので0で頭をうめています。 -
x
やらwidth
は頭にriot-
をつける必要あり
全体の流れ
全体の処理の流れとしては下記な感じを想定して作りました。
1. 初期化処理
2. メイン処理
3. 蛇の死
4. 初期化処理(2回目)
5. メイン処理(2回目)
…こんな感じ
初期化処理
/* タイマーセット関数(一定ごとに時を進める用)
*/
var tm_set=()=>{
if(self.tme)clearInterval(self.tme);
self.tme=setInterval(()=>{
self.tm-=1;
tm_set2(self.tm);
},1000);//とりあえず1秒単位で更新固定
};
/* タイマーセット関数(メイン処理用)
*/
var tm_set2=(v)=>{
if(self.tme2)clearInterval(self.tme2);
self.tme2=setInterval(()=>{
_main_();
},v);
};
/* 初期化処理
*/
var _init_=()=>{
// 変数の初期化
self.st=1;//ゲームステータス
self.w=40;//横の長さ
self.h=40;//縦の長さ
self.view=new Array(self.h);//表示用変数初期化
for(i=0;i<self.h;i++){
self.view[i] = new Array(self.w);
self.view[i].fill(0);
}
self.map=new Array(self.h);//マップ変数の初期化(今は餌しか置かれない…)
for(i=0;i<self.h;i++){
self.map[i] = new Array(self.w);
self.map[i].fill(0);
}
self.snake={//蛇の初期化
hed:{x:Math.floor(self.w/2),y:Math.floor(self.h/2)},//頭の位置
body:[],//体
point:0,//ポイント
move:{x:1,y:0}//移動属性(キーダウンイベントで変化)
}
self.tm=300;//時間更新間隔(初期値)
//キーダウンイベント
document.onkeydown = function (e){
var k = e.keyCode;
var s = self.snake;
console.log(k)
if(k==37)s.move={x: 0,y:-1};//左
if(k==38)s.move={x:-1,y: 0};//上
if(k==39)s.move={x: 0,y: 1};//右
if(k==40)s.move={x: 1,y: 0};//下
if(k==32)self.st=2;//game開始
};
//タイマー設定
tm_set();
console.log("_init_");
}
移動処理
/* run処理
*/
var _run_=()=>{
var s = self.snake;
// --move-- 移動処理
// body
s.body.unshift(JSON.parse(JSON.stringify(s.hed)));
s.body.pop();
// hed
s.hed.x += s.move.x;
s.hed.y += s.move.y;
// --dead-- 死亡判定
var b1 = (s.hed.x<self.w);
var b2 = (s.hed.y<self.h);
var b3 = (s.hed.x>=0);
var b4 = (s.hed.y>=0);
var b5 = (s.body.filter((v)=>{return (s.hed.x==v.x)&&(s.hed.y==v.y);}).length==0);
if(!(b1&&b2&&b3&&b4&&b5)){
return false;
}
// --eat-- お食事判定
if(self.map[s.hed.y][s.hed.x]=="food"){
if(s.body.length==0){
s.body.push(JSON.parse(JSON.stringify(s.hed)));
}else{
s.body.push(JSON.parse(JSON.stringify(s.body[s.body.length-1])));
}
s.point+=1/self.tm;
}
self.map[s.hed.y][s.hed.x]=0;
// --food-- 餌をRANDOMに置く処理
var foody=Math.floor(Math.random()*self.h);
var foodx=Math.floor(Math.random()*self.w);
var fb1=(s.body.filter((v)=>{return (foodx==v.x)==(foody==v.y);}).length==0);
var fb2=(!((s.hed.x==foodx)&&(s.hed.y==foody)));
if(fb1&&fb1){
self.map[foody][foodx]="food";
}
// --view-- 表示を更新する処理
for(i=0;i<self.h;i++){self.view[i].fill(0);}
self.view[s.hed.y][s.hed.x]=0x0000ff;
s.body.forEach((v)=>{
self.view[v.y][v.x]=0x5555ff;
});
self.map.forEach((y,yi)=>{
y.forEach((x,xi)=>{
if(x=="food"){
self.view[yi][xi]=0xff00ff;
}
});
})
return true;
}
メイン処理
ほぼ、切り替えしかしてないけど…
/* main
*/
var _main_=()=>{
if(self.st==2){//st:0>初期 1>停止 2>ゲーム中
if(!_run_()){
_init_();
}
self.update();
}
};
全体コード
index.html
<html>
<head>
<title>Hello Riot.</title>
<meta charset="UTF-8"/>
</head>
<body>
<sample></sample>
<script type="riot/tag" src="sample.tag"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/riot@3.9/riot+compiler.min.js"></script> -->
<script src="riot/riot+compiler.min.js"></script>
<script>riot.mount('sample')</script>
</body>
</html>
sample.tag
<sample>
<h1>蛇のやつ</h1>
<h3>スペースキーで開始してください</h3>
<svg riot-width={ w*15 } riot-height={ h*15 } >
<g each={d,i in view} transform={('translate('+(i*15)+',0)')}>
<rect each={d2,i2 in d} x="0" riot-y={i2*15} data-point='{(JSON.stringify({x:i,y:i2}))}' width="15" height="15" title="" fill="#{('000000'+d2.toString(16)).slice(-6)}"></rect>
</g>
</svg>
<h3>ポイント{snake.point}点</h3>
<h3>体の長さ{snake.body.length}ブロック</h3>
<h3>頭の位置{snake.hed.x}:{snake.hed.y}</h3>
<script>
var self=this;
self.st=0;
self.tme=false;
self.tme2=false;
/* タイマーセット関数(一定ごとに時を進める用)
*/
var tm_set=()=>{
if(self.tme)clearInterval(self.tme);
self.tme=setInterval(()=>{
self.tm-=1;
tm_set2(self.tm);
},1000);
};
/* タイマーセット関数(メイン処理用)
*/
var tm_set2=(v)=>{
if(self.tme2)clearInterval(self.tme2);
self.tme2=setInterval(()=>{
_main_();
},v);
};
/* main
*/
var _main_=()=>{
if(self.st==2){
if(!_run_()){
_init_();
}
self.update();
}
};
/* run処理
*/
var _run_=()=>{
var s = self.snake;
// --move--
// body
s.body.unshift(JSON.parse(JSON.stringify(s.hed)));
s.body.pop();
// hed
s.hed.x += s.move.x;
s.hed.y += s.move.y;
// --dead--
var b1 = (s.hed.x<self.w);
var b2 = (s.hed.y<self.h);
var b3 = (s.hed.x>=0);
var b4 = (s.hed.y>=0);
var b5 = (s.body.filter((v)=>{return (s.hed.x==v.x)&&(s.hed.y==v.y);}).length==0);
if(!(b1&&b2&&b3&&b4&&b5)){
return false;
}
// --eat--
if(self.map[s.hed.y][s.hed.x]=="food"){
if(s.body.length==0){
s.body.push(JSON.parse(JSON.stringify(s.hed)));
}else{
s.body.push(JSON.parse(JSON.stringify(s.body[s.body.length-1])));
}
s.point+=1/self.tm;
}
self.map[s.hed.y][s.hed.x]=0;
// --food--
var foody=Math.floor(Math.random()*self.h);
var foodx=Math.floor(Math.random()*self.w);
var fb1=(s.body.filter((v)=>{return (foodx==v.x)==(foody==v.y);}).length==0);
var fb2=(!((s.hed.x==foodx)&&(s.hed.y==foody)));
if(fb1&&fb1){
self.map[foody][foodx]="food";
}
// --view--
for(i=0;i<self.h;i++){self.view[i].fill(0);}
self.view[s.hed.y][s.hed.x]=0x0000ff;
s.body.forEach((v)=>{
self.view[v.y][v.x]=0x5555ff;
});
self.map.forEach((y,yi)=>{
y.forEach((x,xi)=>{
if(x=="food"){
self.view[yi][xi]=0xff00ff;
}
});
})
return true;
}
/* 初期化処理
*/
var _init_=()=>{
self.st=1;
self.w=40;
self.h=40;
self.view=new Array(self.h);
for(i=0;i<self.h;i++){
self.view[i] = new Array(self.w);
self.view[i].fill(0);
}
self.map=new Array(self.h);
for(i=0;i<self.h;i++){
self.map[i] = new Array(self.w);
self.map[i].fill(0);
}
self.snake={
hed:{x:Math.floor(self.w/2),y:Math.floor(self.h/2)},
body:[],
point:0,
move:{x:1,y:0}
}
self.tm=300;
document.onkeydown = function (e){
var k = e.keyCode;
var s = self.snake;
console.log(k)
if(k==37)s.move={x: 0,y:-1};//左
if(k==38)s.move={x:-1,y: 0};//上
if(k==39)s.move={x: 0,y: 1};//右
if(k==40)s.move={x: 1,y: 0};//下
if(k==32)self.st=2;//game開始
};
tm_set();
console.log("_init_");
}
/* マウント前イベント
*/
self.on("before-mount",(v)=>{
_init_();
});
</script>
</sample>
残課題
- 死んだら死んだとか出てほしいけど出ない
- ポイントが後半に食った餌ほど線形に高い雑仕様
- 死んだら得たポイント見られない
- 毎更新に餌がおかれるため、後半めっちゃ出る