#Cocos2d.JSで簡単なゲームを作ってみました
JavaScriptベースのゲームエンジンであるCocos2d.JSを使用した簡単なゲームを作ってみましたので、その作成手順をステップバイステップで説明します。 なお、Windows10の環境において Microsoft Edge (Ver. 11.0.17763.379), Firefox (Ver. 65.0.2/64 bit) 及び Google Chrome (Ver. 73.0.3683.86/64 bit) で動作を確認しました。 しかしながら、タッチ・イベントは未実装ですのでAndroidやiOSでは動作しませんので悪しからず。
また、ここで使用しているグラフィックの一部に「どらぴか様」ならびに「ぴぽや様」作成の素材をサウンドエフェクトには「効果音ラボ様」作成の素材を使用させていただきました。 フリー素材を提供して下さった各位に感謝いたします。
##ファイル構成
ここで説明する内容に関するファイルは全て「GitHub Cocos2d_Sample_games」からダウンロード可能です。 また、そのファイル構成は次の通りです。
・ frameworks: Cocos2d.JSからダウンロードしたものでゲームエンジン本体を含むフォルダーです。
・ res: ゲームで使用するグラフィックやサウンド・エフェクトを収納するフォルダーです。
・ Cocos2dJS_Sample_01 ~ Cocos2dJS_Sample_07: 各ステップ用のフォルダーです。
- Cocos2d_Sample_xx.html: 各ステップでのGame開始用HTMLファイルです。(xxは、各ステップの番号です/以下同様)
- main_xx.js: 最初に読み込まれるJSファイルです。(Cocos2d.JSからダウンロードしたものを修正しました)
- project.json: 読込むゲームファイルを指定するJSONファイルです。
- src: ゲーム・プログラム本体や設定ファイルを収容するフォルダーです。
+ app_xx.js: ゲーム本体のJSファイルです。
+ loading.js: ゲーム開始までのローディング画面用JSファイルです。(Cocos2d.JSからダウンロードしたもの)
+ resource_xx.js: グラフィックやサウンド・エフェクトを設定するためのJSファイルです。
##ゲーム作成の説明
以下にゲーム作成の手順をステップバイステップで説明します。
###Step-1: 静止画グラフィックスを表示させる
始めにHTMLファイルを示します。 画面のキャンバス・サイズや背景色の設定等を行い全てのステップでほぼ同一ですが、各ステップ毎にタイトルと「src="main_01.js"」の部分を各ステップ番号に変更しています。
Step-1のデモ実行
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Cocos2d-html5 Sample_01: Set a static character image by T. Fujita on 2019/9/10</title>
<link rel="icon" type="image/GIF" href="../res/favicon.ico"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="full-screen" content="yes"/>
<meta name="screen-orientation" content="portrait"/>
<meta name="x5-fullscreen" content="true"/>
<meta name="360-fullscreen" content="true"/>
<style>
body, canvas, div {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
-khtml-user-select: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
</style>
</head>
<body style="padding:0; margin: 0; background: #000;">
<canvas id="gameCanvas" width="800" height="600"></canvas>
<script src="./src/loading.js"></script>
<script src="../frameworks/cocos2d-html5/CCBoot.js"></script>
<script src="main_01.js"></script>
</body>
</html>
次に「main_01.js」を示します。 ゲーム画面サイズ等を設定しますが、これは各ステップで内容は同一です。
cc.game.onStart = function(){
if(!cc.sys.isNative && document.getElementById("cocosLoading")) //If referenced loading.js, please remove it
document.body.removeChild(document.getElementById("cocosLoading"));
cc.view.setDesignResolutionSize(800, 600, cc.ResolutionPolicy.SHOW_ALL);
cc.view.resizeWithBrowserSize(true);
//load resources
cc.LoaderScene.preload(g_resources, function () {
cc.director.runScene(new GameScene());
}, this);
};
cc.game.run();
「project.json」ファイルの内容を示します。 各ステップ毎に読み込むファイルを指定します。
{
"project_type": "javascript",
"debugMode" : 1,
"showFPS" : true,
"frameRate" : 60,
"id" : "gameCanvas",
"renderMode" : 0,
"engineDir":"../frameworks/cocos2d-html5",
"modules" : ["cocos2d"],
"jsList" : [
"src/resource_01.js",
"src/app_01.js"
]
}
メイン・プログラムである「app_01.js」を示します。 Step-1では、背景画像とグラフィックス(player)を表示させています。
// Sample for Cocos2d.js by T. Fujita on 2019/9/10
var GameLayer = cc.Layer.extend({
sprite:null,
ctor:function () {
// Initialize
this._super();
// Get windows size
var size = cc.director.getWinSize();
var temp_x = size.width / 2;
var temp_y = size.height / 2;
var MS_x = 0;
var MS_y = 0;
var Speed = 1;
var act1, act2, acts;
// Create the background image
this.back_img = cc.Sprite.create(res.Back_jpg);
this.back_img.x = temp_x;
this.back_img.y = temp_y;
this.addChild(this.back_img, 0);
// Create the sprite for player
this.player = new cc.Sprite.create(res.PLAYER_png, cc.rect(48, 0, 48, 48));
this.player.x = temp_x;
this.player.y = temp_y;
this.addChild(this.player, 1);
}
});
var GameScene = cc.Scene.extend({
onEnter:function () {
this._super();
var layer = new GameLayer();
this.addChild(layer);
}
});
「resource_01.js」では、ゲーム内で使用する背景およびグラフィックスを指定します。
var res = {
Back_jpg : "../res/bg.jpg",
PLAYER_png : "../res/20190405_1372448.png",
};
var g_resources = [
//image
res.Back_jpg,
res.PLAYER_png,
//plist
//fnt
//tmx
//bgm
//effect
];
###Step-2: 静止画グラフィックスを移動させてみる
前述の「app_01.js」にマウスをクリックした位置あるいはキー入力の検知を追加し、それに合わせてplayerを移動(x方向用のact1及びy方向用のact2)させるものが「app_02.js」です。 追加個所を以下に示します。
Step-2のデモ実行
// Get the mouse's position at push down the bottun
cc.eventManager.addListener({
event: cc.EventListener.MOUSE,
onMouseDown: function(e) {
MS_x = Math.floor(e.getLocationX());
MS_y = Math.floor(e.getLocationY());
Move_set();
acts = cc.Sequence.create(act1, act2);
Player.runAction(cc.Sequence.create(acts));
}
}, this);
// Get the pressed key on keyboard
cc.eventManager.addListener({
event: cc.EventListener.KEYBOARD,
onKeyPressed: function(keyCode, event) {
if (keyCode == cc.KEY.left) {
MS_x = Player.x - P_size;
MS_y = Player.y;
}
if (keyCode == cc.KEY.right) {
MS_x = Player.x + P_size;
MS_y = Player.y;
}
if (keyCode == cc.KEY.up) {
MS_x = Player.x;
MS_y = Player.y + P_size;
}
if (keyCode == cc.KEY.down) {
MS_x = Player.x;
MS_y = Player.y - P_size;
}
Move_set();
acts = cc.Sequence.create(act1, act2);
Player.runAction(cc.Sequence.create(acts));
}
}, this);
return true;
// Set the action for the Player
function Move_set() {
Speed = Math.floor(Math.abs(Player.x - MS_x)) / 100;
act1 = cc.MoveTo.create(Speed, cc.p(MS_x, Player.y));
Speed = Math.floor(Math.abs(Player.y - MS_y)) / 100;
act2 = cc.MoveTo.create(Speed, cc.p(MS_x, MS_y));
return true;
}
###Step-3: アニメーション・グラフィックスを表示させる
「app_01.js」を元にplayerをアニメーションさせる「app_03.js」を作成します。 変更部分を次に示します。 Cocos2d.JSでは、RPGツクール等の歩行グラフィックスが使用できます。
Step-3のデモ実行
// Create the sprite for player
var frames = [];
for (i = 0; i < 3; i++) {
var frame = new cc.SpriteFrame(res.PLAYER_png, cc.rect( i * 48, 0, 48, 48));
frames.push(frame);
}
var animation_walk = new cc.Animation.create(frames);
animation_walk.setDelayPerUnit(15 / 60);
animation_walk.setRestoreOriginalFrame(false);
var action_walk = new cc.Animate(animation_walk, 1);
this.player = new cc.Sprite.create();
this.player.x = temp_x;
this.player.y = temp_y;
this.player.runAction(cc.RepeatForever.create(action_walk));
this.addChild(this.player, 1);
###Step-4: アニメーション・グラフィックスを移動させてみる
前述の「app_02.js」と「app_03.js」を組み合わせると共に前後(上下)左右それぞれの歩行グラフィックスを追加したものが「app_04.js」です。 追加分の一部として右側歩行分を以下に示します。
Step-4のデモ実行
var Player = cc.Layer.extend({
ctor: function() {
this._super();
},
initializedParam: function() {
direction = "stay";
},
walkRight: function() {
var frames = [];
for (i = 0; i < 3; i++) {
var frame = new cc.SpriteFrame(res.PLAYER_png, cc.rect( i * 48, 96, 48, 48));
frames.push(frame);
}
var animation_right = new cc.Animation.create(frames);
animation_right.setDelayPerUnit(15 / 60);
animation_right.setRestoreOriginalFrame(false);
var action_right = new cc.Animate(animation_right, 1);
if(flag == 0) {
player = new cc.Sprite.create();
player.setScale(Scale, Scale);
var act = cc.MoveTo.create(0.1, cc.p(PS_x, PS_y));
player.runAction(cc.Sequence.create(act));
}
var act_right = cc.MoveTo.create(Speed, cc.p(player.x + P_size, player.y));
player.runAction(cc.Sequence.create(act_right));
player.runAction(cc.RepeatForever.create(action_right));
this.addChild(player, 1);
flag = 1;
PC_x = player.x + P_size;
PC_y = player.y;
},
###Step-5: 壁等を表示させ、移動を制限する
「app_04.js」に「stages_01.js」で設定する各ステージを読み込みタイルデータを表示させたものが「app_05.js」です。 ここでは、壁を通り抜け出来ない設定とゴール、再度実行(Aタイル)のみ対応するようにしています。
Step-5のデモ実行
for (i=0; i<ROOM.length; i++) {
temp[i] = [];
for (j=0; j<ROOM[i].length; j++) {
temp[i][j] = ROOM[i].substr(j,1);
if(ROOM[i].substr(j,1) == "P") {
this.PLAYER = new Player();
this.PLAYER.x = 0;
this.PLAYER.y = 0;
this.addChild(this.PLAYER, 10);
PS_x = j * P_size + P_size/2;
PS_y = (ROOM.length - i - 1) * P_size + P_size/2;
}
if(ROOM[i].substr(j,1) == "A") {
this.again = cc.Sprite.create(res.AGAIN_png);
this.again.setScale(Scale, Scale);
this.again.x = j * P_size + P_size/2;
this.again.y = (ROOM.length - i - 1) * P_size + P_size/2;
this.addChild(this.again, 1);
}
if(ROOM[i].substr(j,1) == "G") {
this.goal = cc.Sprite.create(res.GOAL_png);
this.goal.setScale(Scale, Scale);
this.goal.x = j * P_size + P_size/2;
this.goal.y = (ROOM.length - i - 1) * P_size + P_size/2;
this.addChild(this.goal, 1);
}
else if(ROOM[i].substr(j,1) == "B") {
this.block = cc.Sprite.create(res.BLOCK_png);
this.block.setScale(Scale, Scale);
this.block.x = j * P_size + P_size/2;
this.block.y = (ROOM.length - i - 1) * P_size + P_size/2;
this.addChild(this.block, 2);
BLOCK[BL_counter] = this.block;
}
else if(ROOM[i].substr(j,1) == "w") {
this.wall_0 = cc.Sprite.create(res.WALL_00_png);
this.wall_0.setScale(Scale, Scale);
this.wall_0.x = j * P_size + P_size/2;
this.wall_0.y = (ROOM.length - i - 1) * P_size + P_size/2;
this.addChild(this.wall_0, 3);
}
else if(ROOM[i].substr(j,1) == "W") {
this.wall_1 = cc.Sprite.create(res.WALL_01_png);
this.wall_1.setScale(Scale, Scale);
this.wall_1.x = j * P_size + P_size/2;
this.wall_1.y = (ROOM.length - i - 1) * P_size + P_size/2;
this.addChild(this.wall_1, 4);
}
}
}
function Move_set() {
px = Math.round((PC_x - P_size/2) / P_size);
py = ROOM.length - 1 - Math.round((PC_y - P_size/2) / P_size);
if(px < 1) {px = 1;}
if(py < 1) {py = 1;}
if(direction == "left") {
if(flag != 0) {
PLAYER1.actionManager.removeAllActions();
}
if((pos[px - 1][py] =="w") ||(pos[px - 1][py] =="W")) {
PLAYER1.walkStay();
var act = cc.MoveTo.create(Speed, cc.p(px * P_size + P_size/2, PC_y));
player.runAction(cc.Sequence.create(act));
} else {
PLAYER1.walkLeft();
}
} else if(direction == "right") {
// Process for reaching the goal
function Check_Goal() {
if(pos[p_x][p_y] == "G") {
alert("Goal ! \n ここで使用しているキャラクターの一部に「どらぴか」様 \n URL: https://dorapika.wixsite.com/pikasgame \n作成の素材を使用しております。");
counter = counter + 1;
var layer = new GameLayer();
cc.director.runScene(layer);
}
}
// Process for again the same room
function Check_Again() {
if(pos[MS_x][ROOM.length - 1 - MS_y] == "A") {
alert("Again ! \n ここで使用しているキャラクターの一部に「どらぴか」様 \n URL: https://dorapika.wixsite.com/pikasgame \n作成の素材を使用しております。");
var layer = new GameLayer();
cc.director.runScene(layer);
}
}
各ステージを構成する「stage_01.js」の一部を以下に示します。 メモ帳等のエディターで修正・追加してみてください。
// All the rooms are here.
// by T. Fujita
//
// A: Again
// B: Block(Movable)
// F: Floor
// G: Goal
// P: Player
// W: Wall
var room = [];
room[0] = [ "WwwwwGwW",
"WFFFFFFW",
"WFFFFFFW",
"WFPFFFFW",
"WFFFFFFW",
"WwwwwwwA"]
room[1] = [ "WwwwwGwW",
"WFFFFBFW",
"WFFFFFFW",
"WFPFFFFW",
"WFFFFFFW",
"WwwwwwwA"]
room[2] = [ "WwwwwwwwwwwwwGwW",
"WFFFFFFFFFFFFFFW",
"WBBBBBBBBBBBBBBW",
"WFFFFFFFFFFFFFFW",
"WFFFFFFFFFFFFFFW",
"WFFFFFFFFFFFFFFW",
"WFFFFFFFFFFFFFFW",
"WFFFFFFFFFFFFFFW",
"WFFPFFFFFFFFFFFW",
"WFFFFFFFFFFFFFFW",
"WFFFFFFFFFFFFFFW",
"WwwwwwwwwwwwwwwA"]
###Step-6: ボックスを押して移動可能にさせる
「app_06.js」でplayerがBoxを押して移動できるようにしました。 これでゲームの基本部分は完成です。
Step-6のデモ実行
function Move_set() {
px = Math.round((PC_x - P_size/2) / P_size);
py = ROOM.length - 1 - Math.round((PC_y - P_size/2) / P_size);
b_x = 0;
b_y = 0;
if(direction == "left") {
if(flag != 0) {
PLAYER1.actionManager.removeAllActions();
}
if((pos[px - 1][py] =="w") ||(pos[px - 1][py] =="W")) {
PLAYER1.walkStay_L();
var act = cc.MoveTo.create(Speed, cc.p(px * P_size + P_size/2, PC_y));
player.runAction(cc.Sequence.create(act));
} else if((pos[px - 1][py] =="B") && (pos[px - 2][py] =="F")) {
pos[px][py] = "F";
pos[px - 1][py] = "P";
pos[px - 2][py] = "B";
for(i=0; i<BLOCK.length; i++) {
if((Math.floor(BLOCK[i].x / P_size) == (px - 1)) && ((ROOM.length - 1 - Math.floor(BLOCK[i].y / P_size)) == py)) {
BL_counter = i;
b_x = -1;
b_y = 0;
}
}
act_b = cc.MoveBy.create(Speed, cc.p(P_size * b_x, P_size * b_y));
if(BL_temp != 0) {
BLOCK[BL_counter].runAction(new cc.Sequence.create(act_b));
}
PLAYER1.walkLeft();
} else if ((pos[px - 1][py] =="B") && (pos[px - 2][py] !="F")) {
PLAYER1.walkStay_L();
} else {
if(pos[px - 1][py] != "G") {
pos[px][py] = "F";
pos[px - 1][py] = "P";
}
PLAYER1.walkLeft();
}
} else if(direction == "right") {
さらにゴール時の効果音を追加しました。 「resource_06_WSE.js」で効果音のファイルを追加した他、「app_06_WSE.js」でゴール時に効果音を作動させます。
Step-6効果音付きのデモ実行
// Process for reaching the goal
function Check_Goal() {
if(pos[p_x][p_y] == "G") {
cc.audioEngine.playEffect(res.GOAL_mp3);
alert("Goal! \n ここで使用しているキャラクターの一部に「どらぴか」様 \n URL: https://dorapika.wixsite.com/pikasgame 作成の素材を\nまた、サウンドは「soundeffect-lab https://soundeffect-lab.info/ 」様作成の素材を使用しております。");
counter = counter + 1;
var layer = new GameLayer();
cc.director.runScene(layer);
}
}
###Step-7: 迷路を作成しアニメーション・グラフィックスを移動させてみる
「app_06_WSE.js」を若干変更し、「maze_02.js」で作成した迷路を表示させたものが「app_07.js」です。 各グラフィックスのサイズは縦横共に1/2に設定しています。 迷路は、「Algoful : Algorithm for making a maze」のアルゴリズムを使用させていただきました。
Step-7のデモ実行
// This program is based on "http://algoful.com/Archive/Algorithm/MazeDig" by Algoful.
// by T. Fujita
var maze = [];
var startCells = [];
var Maze = function(X, Y)
{
var w = X; // 幅(奇数)
var h = Y; // 高さ(奇数)
if(w < 11) {w = 11;}
if(h < 11) {h = 11;}
if(w % 2 == 0) {w = w - 1;}
if(h % 2 == 0) {h = h - 1;}
var x;
var y;
var results = [];
var currentCells = [];
for (x = 0; x < w; x++) {
maze[x] = new Array();
for (y = 0; y < h; y++) {
if (x == 0 || y == 0 || x == w - 1 || y == h - 1) {
maze[x][y] = "F"; // 外周を通路に設定
} else {
maze[x][y] = "W"; // 残りを壁に設定
}
}
}
startCells[0] = [];
startCells[0][0] = (Math.floor(Math.random() * (w / 2 - 2))) * 2 + 1;
startCells[0][1] = (Math.floor(Math.random() * (h / 2 - 2))) * 2 + 1;
Dig(startCells[0][0], startCells[0][1]);
for (x = 0; x < w; x++)
{
for (y = 0; y < h; y++)
{
if (x == 0 || y == 0 || x == w - 1 || y == h - 1)
{
maze[x][y] = "W"; // 外周を壁に戻す
}
}
}
let i = 0;
while (maze[i][1] == "W") {
if(maze[i + 1][1] == "F") {
maze[i + 1][1] = "P";
break;
}
i = i + 1;
}
i = maze.length - 2;
let j = maze[0].length - 1;
while(maze[i][j] == "W") {
if(maze[i - 1][j - 1] == "F") {
maze[i - 1][j] = "G";
break;
}
i = i - 1;
}
for (y = 0; y < h; y++) {
results[y] = [];
for (x = 0; x < w; x++) {
results[y] = results[y] + maze[x][y];
}
}
// alert(results[0] + "\n" + results[1]);
return results;
}
function Dig(x, y)
{
while (true) {
var directions = [];
if(maze[x][y-1] == "W" && maze[x][y-2] =="W") {directions.push("UP");}
if(maze[x][y+1] == "W" && maze[x][y+2] =="W") {directions.push("DOWN");}
if(maze[x-1][y] == "W" && maze[x-2][y] =="W") {directions.push("LEFT");}
if(maze[x+1][y] == "W" && maze[x+2][y] =="W") {directions.push("RIGHT");}
if(directions.length == 0) {break;}
var rnd = Math.floor(Math.random() * directions.length);
switch (directions[rnd])
{
case 'UP':
SetPath(x, --y);
SetPath(x, --y);
break;
case 'DOWN':
SetPath(x, ++y);
SetPath(x, ++y);
break;
case 'LEFT':
SetPath(--x, y);
SetPath(--x, y);
break;
case 'RIGHT':
SetPath(++x, y);
SetPath(++x, y);
break;
}
// console.log(maze[0] + "\n" + maze[1] + "\n" + maze[2] + "\n" + maze[3] + "\n" + maze[4] + "\n" + maze[5]);
var cell = GetStartCell();
if(cell != null)
{
Dig(cell[0], cell[1]);
}
}
}
function SetPath(x, y)
{
maze[x][y] = "F";
if((x % 2 == 1) && (y % 2 == 1))
{
var Temp = [x, y];
startCells.push(Temp);
}
}
function GetStartCell()
{
if(startCells.length == 0) {return null;}
var rnd = Math.floor(Math.random() * startCells.length);
var cell = startCells[rnd];
startCells.splice(rnd, 1);
return cell;
}
##Reference
- github Cocos2d_Sample_games
- Home Page of Cocos2d-x
- Download page for Cocos2d-JS
- Home Page of PIKA's GAME
- Home Page of Pipoya
- Algoful : Algorithm for making a maze
- Home Page of soundeffect-lab
##おまけ
JavaScriptベースのゲームエンジンは他にも多数ありますが、その内の幾つかで同様のゲームを作成してみました。 ソースファイルを覘くとゲームエンジン別の特徴がうかがえるかと思います。
- Cocos2d.JSでゲームを作ってみた(本記事です)
- Phaser3.JSでゲームを作ってみた
- Phina.JSでゲームを作ってみた
- Babylon.jsで3Dゲームを作成してみた(その1:迷路編)
- Babylon.jsで3Dゲームを作成してみた(その2:脱出パズル編)
以上