LoginSignup
3
4

More than 3 years have passed since last update.

Cocos2d.JSでゲームを作ってみた

Last updated at Posted at 2019-10-06

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のデモ実行

Cocos2d_Sample_01.html
<!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」を示します。 ゲーム画面サイズ等を設定しますが、これは各ステップで内容は同一です。

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.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)を表示させています。

app_01.js
// 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」では、ゲーム内で使用する背景およびグラフィックスを指定します。

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のデモ実行

app_02.js(追加部分)
// 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のデモ実行

app_03.js(追加分)
// 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のデモ実行

app_04.js(追加の一部分)
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のデモ実行

app_05.js(タイルデータ表示部分)
    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);
        }
        }
    }
app_05.js(左側の壁対応部分)
    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") {
app_05.js(ゴール対応部分と再度実行部分)
// 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」の一部を以下に示します。 メモ帳等のエディターで修正・追加してみてください。

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のデモ実行

app_06.js(左側の壁対応にBOX移動を追加した部分)
    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効果音付きのデモ実行

app_06_WSE.js(ゴール時の効果音を追加した部分)
// 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のデモ実行

maze_02.js
// 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

  1. github Cocos2d_Sample_games
  2. Home Page of Cocos2d-x
  3. Download page for Cocos2d-JS
  4. Home Page of PIKA's GAME
  5. Home Page of Pipoya
  6. Algoful : Algorithm for making a maze
  7. Home Page of soundeffect-lab

おまけ

JavaScriptベースのゲームエンジンは他にも多数ありますが、その内の幾つかで同様のゲームを作成してみました。 ソースファイルを覘くとゲームエンジン別の特徴がうかがえるかと思います。

以上

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4