●ゲーム&ウオッチ
http://ja.wikipedia.org/wiki/ゲーム%26ウオッチ
ゲーム&ウオッチは名前からもわかるように時計にゲーム機能を加えたものです。基本的にシンプルなゲームしかないのでenchant.jsで作成するにはもってこいです。今回はゲーム&ウオッチの中から「ヘルメット」をベースにしてゲームを作成してみます。そっくり作ると怒られますので、上から落下してくるリンゴをさけて右に移動するだけ、というシンプルなものにしました。
●ゲーム&ウオッチ「ヘルメット」
http://www.nintendo.co.jp/ds/dsiware/game_and_watch/
なお、今回使用するenchant.jsに関しては以下の公式サイトを参照するか、以前の連載記事を参照してください。enchant.jsの新しいバージョンではCanvasベースになっていますが、ここではver 0.43を使用します。
●enchant.js
http://enchantjs.com/ja/
【図】fig1.png
上からリンゴが回転しながら落下してくる
これまでと異なる部分だけ説明します。まず、落下しながら回転するリンゴの処理です。これまではスプライト(キャラクタ)を回転させることはありませんでした。回転処理はCSSで行うこともできますが、幸いenchant.jsにはスプライトを回転させるrotate()メソッドが用意されています。このメソッドのパラメータに回転させたい角度指定するとスプライトが回転します。
例えばabc.rotate(5)とするとスプライトabcが5度時計回りに回転します。さらにabc.rotate(5)とすると合計10度時計回りに回転します。
実際に落下と回転処理を行っているのが以下の関数になります。リンゴは下まで落下したら再度上から同じX座標に出るようにしています。
-------------------------------------------------
function moveApple(){
for(var i=0; i<game.appleCount; i++){
apple[i].y = apple[i].y + apple[i].speed;
apple[i].rotate(5);
if (apple[i].y > game.height){
apple[i].y = -32; // 一番上に座標を再設定
apple[i].speed = Math.random()*7; // 落下速度を再設定
}
}
}
-------------------------------------------------
これまでと異なるのはリンゴとドロイド君の接触判定です。これまでは四角形同士の当たり判定を利用していました。今回は四角形の範囲ではなく、スプライト同士の距離で当たり判定を行います。つまりスプライトの半径24ピクセル以内だったら接触しているという判定を行うわけです。
enchant.jsでは、このように距離を利用して判定を行うためのwithin()メソッドが用意されています。within()メソッドは最初に接触判定を行うスプライトを指定し、2番目のパラメータで距離(半径、ピクセル単位)を指定します。接触していればtrue、そうでなければfalseになります。
【図】fig2.png
enchant.jsでの接触判定
実際の接触判定のプログラムは以下のようになっています。リンゴとドロイド君が一定の距離内で接触するとゲームオーバーになります。
-------------------------------------------------
function hitCheck(){
for(var i=0; i<game.appleCount; i++){
if(droid.within(apple[i], 24)){
game.stop();
alert("ゲームオーバー");
}
}
}
-------------------------------------------------
【実際のゲームのURL】
http://www.openspc2.org/reibun/enchant.js/v0.4.3/asc/0003/01_apple/
-------------------------------------------------
■サンプル1
-------------------------------------------------
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name = "viewport" content = "width=device-width, initial-scale=1,user-scalable=no">
<title>Sample game</title>
<style>
body { margin: 0; }
</style>
<script src="js/enchant.min.js"></script>
<script src="js/main.js"></script>
</head>
<body></body>
</html>
-------------------------------------------------
■main.js
-------------------------------------------------
enchant(); // ライブラリの初期化
window.onload = function(){
var game = new Game(480, 360); // 480×360画面(Canvas)を作成
game.fps = 30; // フレームレートの設定。30fpsに設定
// 画像データをあらかじめ読み込ませる
game.preload("images/droid.png", "images/apple.png");
game.rootScene.backgroundColor = "blue"; // ゲームの背景色を青色に設定
game.score = 0; // スコアを入れる変数を用意する
game.appleCount = 15; // リンゴの総数を入れておく変数。15個。
// スコアを表示するラベルを作成
var scoreLabel = new Label("SCORE : 0");
scoreLabel.font = "16px Tahoma";
scoreLabel.color = "white";
scoreLabel.x = 10; // X座標
scoreLabel.y = 5; // Y座標
game.rootScene.addChild(scoreLabel);
// リンゴを格納する配列
var apple = new Array();
// データの読み込みが完了したら処理
game.onload = function(){
// ドロイド君の設定
var droid = new Sprite(32, 32);
droid.image = game.assets["images/droid.png"];
droid.x = 0; // X座標
droid.y = game.height - 40; // Y座標
game.rootScene.addChild(droid);
// リンゴを描く
drawApple();
// フレームイベントが発生したら処理
game.rootScene.addEventListener(Event.ENTER_FRAME, function(){
moveDroid(); // ドロイド君を移動させる
moveApple(); // 敵を移動させる
hitCheck(); // リンゴとドロイド君の接触判定
// =============== 各種処理 ==================
// ------------ ■ドロイド君を移動させる -----------------
function moveDroid(){
if (game.input.right){
droid.x = droid.x + 4; // ドロイド君を右に移動
game.score = game.score + 1; // 右に移動するだけで1点加算
if (droid.x > game.width-8){ // 右端かどうか調べる
game.score = game.score + 100; // スコアを加算(100点)
droid.x = -24; // 左側に再度座標を設定
}
}
scoreLabel.text = "SCORE : "+game.score; // 画面に表示
}
// ------------ ■リンゴを落下させる -----------------
function moveApple(){
for(var i=0; i<game.appleCount; i++){
apple[i].y = apple[i].y + apple[i].speed;
apple[i].rotate(5);
if (apple[i].y > game.height){
apple[i].y = -32; // 一番上に座標を再設定
apple[i].speed = Math.random()*7; // 落下速度を再設定
}
}
}
// ------------ ■ドロイド君とリンゴの接触判定を行う -----------------
function hitCheck(){
for(var i=0; i<game.appleCount; i++){
if(droid.within(apple[i], 24)){
game.stop();
alert("ゲームオーバー");
}
}
}
});
}
// ゲーム処理開始
game.start();
// ------------ リンゴを描く -----------------
function drawApple(){
for(var i=0; i<game.appleCount; i++){
apple[i] = new Sprite(32, 32);
apple[i].image = game.assets["images/apple.png"];
apple[i].x = i * 32+10; // X座標
apple[i].y = Math.random()*140; // Y座標
apple[i].speed = Math.random()*7; // 落下速度
apple[i].rotate(Math.random()* 360); // 最初に回転させておく
game.rootScene.addChild(apple[i]);
}
}
}
-------------------------------------------------
【図】fig3.pdf
4つの背景画像を用意した。夜、朝、昼、夕方となっている。
次にステージ数を表示するラベルを用意します。これはスコアと同じで以下のように生成します。
-------------------------------------------------
// ステージを表示するラベルを作成
var stageLabel = new Label("STAGE 1");
stageLabel.font = "14px Tahoma";
stageLabel.color = "red";
stageLabel.x = 400; // X座標
stageLabel.y = 330; // Y座標
game.rootScene.addChild(stageLabel);
-------------------------------------------------
次にステージ背景を変化させる部分です。これはドロイド君を移動させるmoveDroid()関数内で行います。
背景画像のURLを求める際にステージ数と3との論理積をとっています。論理積はビット単位で処理を行うもので値1と値2の論理積をとった場合、以下の表のような結果になります。単純に1×1の時だけ1で他は0になるということです。
【表】論理積
値1 | 値2 | 結果 |
---|---|---|
0 | 0 | 0 |
1 | 0 | 0 |
0 | 1 | 0 |
1 | 1 | 1 |
ステージ数と3(ビットで表すと011)との論理積を取ると以下のように変化していきます。つまり、下2ビット以外は全て0になるため、値は必ず0〜3の範囲内になります。
【表】3との論理積をとった結果(カッコ内はビットで表した場合)
ステージ数 | 結果 |
---|---|
1(0001) | 1 |
2(0010) | 2 |
3(0011) | 3 |
4(0100) | 0 |
5(0101) | 1 |
6(0110) | 2 |
7(0111) | 3 |
8(1000) | 0 |
9(1001) | 1 |
10(1010) | 2 |
ステージ数から表示する背景画像の数値を求めたら、次に画像のURLを求めます。画像のURLは"bg"の文字に論理積で得られた値(0〜3)を連結し、さらに拡張子であるjpgを連結した文字列になります。その文字列をbody要素のstyle.backgroundImageプロパティに入れます。これでステージをクリアするたびに4枚の背景が繰り返し切り替わります。
-------------------------------------------------
// ステージ数に1を加算
game.stage = game.stage + 1;
stageLabel.text = "STAGE "+game.stage; // 画面に表示
// 新たなステージ背景に変更する
var n = game.stage & 3;
var bgURL = "images/bg"+n+".jpg";
document.body.style.backgroundImage = "url("+bgURL+")";
-------------------------------------------------
【図】fig4.png
【図】fig5.png
【図】fig6.png
【図】fig7.png
ステージをクリアするたびに背景画像が変わる
【実際のゲームのURL】
http://www.openspc2.org/reibun/enchant.js/v0.4.3/asc/0003/02_apple/
【表】プロパティ名と機能
blur | ぼかし |
brightness | 明るさ |
contrast | コントラスト |
drop-shadow | ドロップシャドウ |
grayscale | グレースケール |
hue-rotate | 色相 |
invert | 色反転 |
opacity | 不透明度 |
saturate | 彩度 |
sepia | セピア調 |
使い方ですが、Safari 6とGoogle Chromeでは以下のようにwebkitのベンダプレフィックスが必要です。-webkit-filterの後にフィルタ名と値を指定します。指定できる値はフィルタによって異なっています。
以下のようにするとbody要素がグレースケールになります。
-------------------------------------------------
body { margin: 0;
background-image: url("images/bg1.jpg");
-webkit-filter: grayscale(100%);
}
-------------------------------------------------
実際にゲームに組み込んでみると、期待とは違った結果になっています。body要素の背景画像は、なぜかグレースケールにならず色が付いたままです。
【図】fig8.png
ドロイド君とリンゴ、スコア(ラベル)はグレースケールになるが、背景画像はグレースケールにならない
【実際のゲームのURL】
http://www.openspc2.org/reibun/enchant.js/v0.4.3/asc/0003/03_apple/
仕方ないので背景用に新たなスプライトを作成し背景画像として利用することにします。スプライトとして読み込ませるので、他のスプライト画像と同様にプレロードするようにします。
-------------------------------------------------
// 画像データをあらかじめ読み込ませる
game.preload("images/droid.png", "images/apple.png", "images/bg0.jpg", "images/bg1.jpg", "images/bg2.jpg", "images/bg3.jpg");
-------------------------------------------------
次に以下のように背景用のスプライトを作成します。
-------------------------------------------------
// 背景の設定
var bg = new Sprite(480, 360);
bg.image = game.assets["images/bg1.jpg"];
bg.x = 0;
bg.y = 0;
game.rootScene.addChild(bg);
-------------------------------------------------
これで背景画像のスプライトが用意できました。ステージが進むにつれて背景画像を切り換える場合は以下のようになります。先ほどは直接body要素のスタイルに設定していましたが、今度はスプライトのimageプロパティに設定すればできあがりです。
-------------------------------------------------
var n = game.stage & 3;
var bgURL = "images/bg"+n+".jpg";
bg.image = game.assets[bgURL];
-------------------------------------------------
実際のプログラムはサンプル2になります。body要素に指定したCSS3フィルタ(グレースケール)が期待どおり反映されているのが分かります。
【図】fig9.png
画面がグレースケールになる。
【実際のゲームのURL】
http://www.openspc2.org/reibun/enchant.js/v0.4.3/asc/0003/04_apple/
-------------------------------------------------
■サンプル2
-------------------------------------------------
enchant(); // ライブラリの初期化
window.onload = function(){
var game = new Game(480, 360); // 480×360画面(Canvas)を作成
game.fps = 30; // フレームレートの設定。30fpsに設定
// 画像データをあらかじめ読み込ませる
game.preload("images/droid.png", "images/apple.png", "images/bg0.jpg", "images/bg1.jpg", "images/bg2.jpg", "images/bg3.jpg");
game.score = 0; // スコアを入れる変数を用意する
game.appleCount = 15; // リンゴの総数を入れておく変数。15個。
game.stage = 1; // ゲームのステージ番号を入れておく変数。
// スコアを表示するラベルを作成
var scoreLabel = new Label("SCORE : 0");
scoreLabel.font = "16px Tahoma";
scoreLabel.color = "white";
scoreLabel.x = 10; // X座標
scoreLabel.y = 5; // Y座標
// ステージを表示するラベルを作成
var stageLabel = new Label("STAGE 1");
stageLabel.font = "14px Tahoma";
stageLabel.color = "red";
stageLabel.x = 400; // X座標
stageLabel.y = 330; // Y座標
// リンゴを格納する配列
var apple = new Array();
// データの読み込みが完了したら処理
game.onload = function(){
// 背景の設定
var bg = new Sprite(480, 360);
bg.image = game.assets["images/bg1.jpg"];
bg.x = 0;
bg.y = 0;
game.rootScene.addChild(bg);
game.rootScene.addChild(scoreLabel);
game.rootScene.addChild(stageLabel);
// ドロイド君の設定
var droid = new Sprite(32, 32);
droid.image = game.assets["images/droid.png"];
droid.x = 0; // X座標
droid.y = game.height - 40; // Y座標
game.rootScene.addChild(droid);
// リンゴを描く
drawApple();
// フレームイベントが発生したら処理
game.rootScene.addEventListener(Event.ENTER_FRAME, function(){
moveDroid(); // ドロイド君を移動させる
moveApple(); // 敵を移動させる
hitCheck(); // リンゴとドロイド君の接触判定
// =============== 各種処理 ==================
// ------------ ■ドロイド君を移動させる -----------------
function moveDroid(){
if (game.input.right){
droid.x = droid.x + 4; // ドロイド君を右に移動
game.score = game.score + 1; // 右に移動するだけで1点加算
if (droid.x > game.width-8){ // 右端かどうか調べる
game.score = game.score + 100; // スコアを加算(100点)
droid.x = -24; // 左側に再度座標を設定
// ステージ数に1を加算
game.stage = game.stage + 1;
stageLabel.text = "STAGE "+game.stage; // 画面に表示
// 新たなステージ背景に変更する
var n = game.stage & 3;
var bgURL = "images/bg"+n+".jpg";
bg.image = game.assets[bgURL];
}
}
scoreLabel.text = "SCORE : "+game.score; // 画面に表示
}
// ------------ ■リンゴを落下させる -----------------
function moveApple(){
for(var i=0; i<game.appleCount; i++){
apple[i].y = apple[i].y + apple[i].speed;
apple[i].rotate(5);
if (apple[i].y > game.height){
apple[i].y = -32; // 一番上に座標を再設定
apple[i].speed = Math.random()*7; // 落下速度を再設定
}
}
}
// ------------ ■ドロイド君とリンゴの接触判定を行う -----------------
function hitCheck(){
for(var i=0; i<game.appleCount; i++){
if(droid.within(apple[i], 24)){
game.stop();
alert("ゲームオーバー");
}
}
}
});
}
// ゲーム処理開始
game.start();
// ------------ リンゴを描く -----------------
function drawApple(){
for(var i=0; i<game.appleCount; i++){
apple[i] = new Sprite(32, 32);
apple[i].image = game.assets["images/apple.png"];
apple[i].x = i * 32+10; // X座標
apple[i].y = Math.random()*140; // Y座標
apple[i].speed = Math.random()*7; // 落下速度
apple[i].rotate(Math.random()* 360); // 最初に回転させておく
game.rootScene.addChild(apple[i]); // シーンに追加
}
}
}
-------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------
game.stop();
document.body.style.webkitFilter = "grayscale(100%)";
alert("ゲームオーバー");
-------------------------------------------------
【図】fig10.png
【図】fig11.png
ドロイド君がリンゴに当たるとゲームオーバーとなり画面がグレースケールになる。
【図】fig12.png
Safari 6でもグレースケールフィルタが反映される。
【実際のゲームのURL】
http://www.openspc2.org/reibun/enchant.js/v0.4.3/asc/0003/05_apple/
せっかくCSS3フィルタが利用できるのでゲーム中でも利用してみましょう。ステージが切り替わった際に、様々なフィルタ効果が適用されるようにします。これは、配列にCSS3フィルタの文字列を用意しておき、ステージ数に応じてbody要素に設定します。実際にフィルタを設定する部分は以下のsetStageFilter()関数で行っています。
-------------------------------------------------
// ------------ ステージ別フィルタ -----------------
function setStageFilter(){
var filterList = ["", "", "", "", "", // ステージ0〜4まではフィルタなし
"opacity(20%)", // ステージ5は不透明度を指定し霧を演出する
"saturate(20000%)", // ステージ6はコントラストを強く
"hue-rotate(45deg)", // ステージ7は色をずらして夕焼けの色を変える
"brightness(-60%)", // ステージ8は全体的に暗くする
"blur(4px)", // ステージ9は画面をぼかす
"invert()", // ステージ10は画面を反転させる
"contrast(20%)", // ステージ11は画面のコントラストを下げる
"opacity(20%) blur(4px)" // ステージ12はぼかして半透明に
];
var n = game.stage % filterList.length;
document.body.style.webkitFilter = filterList[n]; // フィルタ効果を設定
}
-------------------------------------------------
実際のプログラムはサンプル3になります。ステージが進むとステージがフィルタ効果によって、さまざまに変化します。つまらないゲームでも演出によって、うまくごかませることもあります。
【図】fig13.png
【図】fig14.png
【図】fig15.png
【図】fig16.png
【図】fig17.png
ステージによって同じ背景画像でもCSS3フィルタを使うことで、さまざまな変化を見せることができる。
【実際のゲームのURL】
http://www.openspc2.org/reibun/enchant.js/v0.4.3/asc/0003/06_apple/
【実際のゲームのURL】(ゲームオーバー後に、ゆっくりとグレースケールにする場合のサンプル)
http://www.openspc2.org/reibun/enchant.js/v0.4.3/asc/0003/07_apple/
それでは、また次回。
-------------------------------------------------
■サンプル3
-------------------------------------------------
enchant(); // ライブラリの初期化
window.onload = function(){
var game = new Game(480, 360); // 480×360画面(Canvas)を作成
game.fps = 30; // フレームレートの設定。30fpsに設定
// 画像データをあらかじめ読み込ませる
game.preload("images/droid.png", "images/apple.png", "images/bg0.jpg", "images/bg1.jpg", "images/bg2.jpg", "images/bg3.jpg");
game.score = 0; // スコアを入れる変数を用意する
game.appleCount = 15; // リンゴの総数を入れておく変数。15個。
game.stage = 1; // ゲームのステージ番号を入れておく変数。
// スコアを表示するラベルを作成
var scoreLabel = new Label("SCORE : 0");
scoreLabel.font = "16px Tahoma";
scoreLabel.color = "white";
scoreLabel.x = 10; // X座標
scoreLabel.y = 5; // Y座標
// ステージを表示するラベルを作成
var stageLabel = new Label("STAGE 1");
stageLabel.font = "14px Tahoma";
stageLabel.color = "red";
stageLabel.x = 400; // X座標
stageLabel.y = 330; // Y座標
// リンゴを格納する配列
var apple = new Array();
// データの読み込みが完了したら処理
game.onload = function(){
// 背景の設定
var bg = new Sprite(480, 360);
bg.image = game.assets["images/bg1.jpg"];
bg.x = 0;
bg.y = 0;
game.rootScene.addChild(bg);
game.rootScene.addChild(scoreLabel);
game.rootScene.addChild(stageLabel);
// ドロイド君の設定
var droid = new Sprite(32, 32);
droid.image = game.assets["images/droid.png"];
droid.x = 0; // X座標
droid.y = game.height - 40; // Y座標
game.rootScene.addChild(droid);
// リンゴを描く
drawApple();
// フレームイベントが発生したら処理
game.rootScene.addEventListener(Event.ENTER_FRAME, function(){
moveDroid(); // ドロイド君を移動させる
moveApple(); // 敵を移動させる
//hitCheck(); // リンゴとドロイド君の接触判定
// =============== 各種処理 ==================
// ------------ ■ドロイド君を移動させる -----------------
function moveDroid(){
if (game.input.right){
droid.x = droid.x + 4; // ドロイド君を右に移動
game.score = game.score + 1; // 右に移動するだけで1点加算
if (droid.x > game.width-8){ // 右端かどうか調べる
game.score = game.score + 100; // スコアを加算(100点)
droid.x = -24; // 左側に再度座標を設定
// ステージ数に1を加算
game.stage = game.stage + 1;
stageLabel.text = "STAGE "+game.stage; // 画面に表示
// 新たなステージ背景に変更する
var n = game.stage & 3;
var bgURL = "images/bg"+n+".jpg";
bg.image = game.assets[bgURL];
// ステージフィルタを設定する
setStageFilter();
}
}
scoreLabel.text = "SCORE : "+game.score; // 画面に表示
}
// ------------ ■リンゴを落下させる -----------------
function moveApple(){
for(var i=0; i<game.appleCount; i++){
apple[i].y = apple[i].y + apple[i].speed;
apple[i].rotate(5);
if (apple[i].y > game.height){
apple[i].y = -32; // 一番上に座標を再設定
apple[i].speed = Math.random()*7; // 落下速度を再設定
}
}
}
// ------------ ■ドロイド君とリンゴの接触判定を行う -----------------
function hitCheck(){
for(var i=0; i<game.appleCount; i++){
if(droid.within(apple[i], 24)){
game.stop();
document.body.style.webkitFilter = "grayscale(100%)";
alert("ゲームオーバー");
}
}
}
});
}
// ゲーム処理開始
game.start();
// ------------ リンゴを描く -----------------
function drawApple(){
for(var i=0; i<game.appleCount; i++){
apple[i] = new Sprite(32, 32);
apple[i].image = game.assets["images/apple.png"];
apple[i].x = i * 32+10; // X座標
apple[i].y = Math.random()*140; // Y座標
apple[i].speed = Math.random()*7; // 落下速度
apple[i].rotate(Math.random()* 360); // 最初に回転させておく
game.rootScene.addChild(apple[i]); // シーンに追加
}
}
// ------------ ステージ別フィルタ -----------------
function setStageFilter(){
var filterList = ["", "", "", "", "", // ステージ0〜4まではフィルタなし
"opacity(20%)", // ステージ5は不透明度を指定し霧を演出する
"saturate(20000%)", // ステージ6はコントラストを強く
"hue-rotate(45deg)", // ステージ7は色をずらして夕焼けの色を変える
"brightness(-60%)", // ステージ8は全体的に暗くする
"blur(4px)", // ステージ9は画面をぼかす
"invert()", // ステージ10は画面を反転させる
"contrast(20%)", // ステージ11は画面のコントラストを下げる
"opacity(20%) blur(4px)" // ステージ12はぼかして半透明に
];
var n = game.stage % filterList.length;
document.body.style.webkitFilter = filterList[n]; // フィルタ効果を設定
}
}
-------------------------------------------------