モーリーのメモ

アプリ開発等(プログラミング、CG作成)、興味を持ったことを実践してまとめるブログです。

モーリーのメモ

公式サイトのチュートリアルをやってみる その3(完) - コーディング:Cocos2d-x v3.7(JavaScript)

f:id:mmorley:20151105174450p:plain
 
 下記の記事の続きです。
 

  
 今回は、ここまでの作業で作成した画面にゲームの要素やUIの機能を実装します。
 ランナー(プレイヤー)の動き、そしてコインや岩との衝突判定の処理は物理演算エンジンの『Chipmunk』を利用しています。
 作業は全て『Cocos Code IDE』で行います。前回『Cocos Studio 2』からパブリッシュした続きから作業します。

公式サイトのチュートリアルからの変更点

 チュートリアルと違い『Cocos Studio 2』で画面を作成しているので細かい変更点は多々ありますが、その他に大きなものとしては下記の変更を行っています。

  • ランナーの操作をフリック入力から、単純にタッチ入力に変更
    • マウス操作だと操作が難しいためとソースコードの簡略化のため。
  • クラスごとにファイルを分けない。

ファイルの追加

音声ファイルのインポート

  1. 『Cocos Studio 2』を起動
  2. 『Cocos Studio 2』の画面左の『Explorer』の『HelloCocos-Parkour(プロジェクト名)』の『res』フォルダを右クリックし、『インポート』をクリック
  3. 開いたウインドウで『一般』-『ファイル・システム』を選択し、『次へ』をクリック
    f:id:mmorley:20151102145906p:plain
  4. 『次のディレクトリーから(Y):』の『参照』をクリック
  5. ディレクトリの選択画面で、『その1』でダウンロードしたチュートリアルのデータの『Parkour/res』フォルダを選択し、『OK』をクリック
  6. インポート画面で、下記の3個の『.mp3』ファイルにチェックを付けた後、『終了』をクリック
    f:id:mmorley:20151102151407p:plain
    • background.mp3
    • jump.mp3
    • pickup_coin.mp3

JavaScriptファイルの追加

  1. Explorer』の『src』フォルダを右クリックし、『New』-『JavaScriptファイル』をクリック
  2. 下記のように設定し、『終了』をクリック
    • 『ファイル名』:PlayScene

『project.json』の編集

  1. Explorer』の『project.json』を右クリックし、『Open With』-『テキストエディタ』をクリック
  2. 下記のように編集
    • 『"modules"』に、『"chipmunk"』を追加
    • 『 "jsList"』に、『 "src/PlayScene.js"』を追加
{
    "project_type": "javascript",

    "debugMode" : 1,
    "showFPS" : true,
    "frameRate" : 60,
    "id" : "gameCanvas",
    "renderMode" : 0,
    "engineDir":"frameworks/cocos2d-html5",

    "modules" : ["cocos2d", "cocostudio, chipmunk"],

    "jsList" : [
        "src/resource.js",
        "src/app.js",
        "src/PlayScene.js"
    ]
}

『main.js』の編集

 実行時の画面サイズを『480 * 320』に設定します。
 『cc.view.setDesignResolutionSize()』の引数を下記のように変更します。

cc.game.onStart = function(){
    cc.view.adjustViewPort(true);
    cc.view.setDesignResolutionSize(480, 320, cc.ResolutionPolicy.SHOW_ALL); // 画面サイズと表示方法の設定
    cc.view.resizeWithBrowserSize(true);
    //load resources
    cc.LoaderScene.preload(g_resources, function () {
        cc.director.runScene(new HelloWorldScene());
    }, this);
};

『resource.js』の編集

 下記のようにゲームで使用する画像や音声等のリソースファイルを追加します。

var res = {
		helloBG_png : "res/helloBG.png",
		MainScene_json : "res/MainScene.json",
		PlayScene_json : "res/PlayScene.json",
		CoinNode_json : "res/CoinNode.json",
		map_png : "res/map.png",
		map00_tmx : "res/map00.tmx",
		map01_tmx : "res/map01.tmx",
		Plist_plist : "res/Plist.plist",
		Plist_png : "res/Plist.png",
		background_plist : "res/background.plist",
		background_png : "res/background.png",
		running_plist : "res/running.plist",
		running_png : "res/running.png",
		background_mp3 : "res/background.mp3",
		Jump_mp3 : "res/jump.mp3",
		pickup_coin_mp3 : "res/pickup_coin.mp3"
};

var g_resources = [];
for (var i in res) {
	g_resources.push(res[i]);
}

『app.js』の編集

 タイトル画面を表示し、スタートボタンにタッチしたら『PlayScene』に画面遷移するように、タッチイベントを設定します。
『HelloWorldLayer』を下記のように編集します。

var HelloWorldLayer = cc.Layer.extend({ 
	sprite:null,
	ctor:function () {
		this._super(); // 親ノードのメソッドを使用可能にする

		// スプライトシートをキャッシュに登録
		cc.spriteFrameCache.addSpriteFrames(res.background_plist); // コインと岩
		cc.spriteFrameCache.addSpriteFrames(res.running_plist); // ランナー
		
		var jsonMainScene = ccs.load(res.MainScene_json); // MainSceneのjsonファイルの読み込み
		this.addChild(jsonMainScene.node); // レイヤーに追加

		var buttonStart = ccui.helper.seekWidgetByName(jsonMainScene.node, "ButtonStart"); // スタートボタンを取得
		buttonStart.addTouchEventListener(function(sender, type){ // タッチイベントを設定
			switch (type) {
			case ccui.Widget.TOUCH_ENDED: // ボタンを離した時
				cc.director.runScene(new PlayScene()); // PlaySceneに画面遷移
				break;
			}
		}, this);

		return true;
	}
});

『PlayScene.js』の編集

 下記のように編集します。
 中身についてはソースコードの後に説明します。
 『===数字.タイトル===』は説明のために割り振っています。

//========== 1.グローバル変数の定義 ==========
var g_groundHeight = 57; // 地面の高さ
var g_runnerStartX = 80; // ランナーの最初の位置のx座標
var g_runnerStartY = 85; // ランナーの最初の位置のy座標

//========== 2.列挙型の定義 ==========
//衝突タイプ(衝突時の判別用)
if(typeof SpriteTag == "undefined") {
	var SpriteTag = {};
	SpriteTag.runner = 0; // ランナー
	SpriteTag.coin = 1; // コイン
	SpriteTag.rock = 2; // 岩
};

//ランナーの状態
if (typeof RunnerState == "undefined") {
	var RunnerState = {};
	RunnerState.running = 0; // ランニング
	RunnerState.jumpUp = 1; // ジャンプ上昇中
	RunnerState.jumpDown = 2; // ジャンプ下降中
	RunnerState.gameOver = 3; // ゲームオーバー
}

//========== 3.オブジェクトの設定・配置を行うクラス ==========
//コインクラス
var Coin = cc.Class.extend({ // cc.Classを継承
	space:null, // Spaceを保持
	jsonCoinNode:null, // CoinNodeのJSONデータ(ノードとアクション)を保持
	shape:null, // Shapeを保持
	mapIndex:0, // 配置したマップのIndexを保持
	ctor:function(parent, space, pos, mapIndex){ // コンストラクタ
		this.space = space; // Spaceを取得
		this.jsonCoinNode = ccs.load(res.CoinNode_json); // JSONファイルの読込
		this.jsonCoinNode.node.runAction(this.jsonCoinNode.action); // アニメーションを設定
		this.jsonCoinNode.action.play("animation", true); // アニメーションを再生
		this.jsonCoinNode.node.setPosition(pos); // 位置を設定
		parent.addChild(this.jsonCoinNode.node); // 親ノードに追加
		var sprite = this.jsonCoinNode.node.getChildByName("SpriteCoin"); // スプライトの取得
		var radius = 0.95 * sprite.getContentSize().width / 2; // コインのShapeの半径
		var body = new cp.StaticBody(); // 静的ボディ
		body.p = pos; // Bodyの位置を設定
		this.shape = new cp.CircleShape(body, radius, cp.vzero); // 円形状のShapeを作成
		this.shape.setCollisionType(SpriteTag.coin); // 衝突タイプ(衝突イベントの識別番号)を設定
		this.shape.setSensor(true); // true:衝突イベントのみで、物理的に衝突しない
		this.space.addStaticShape(this.shape); // Spaceに静的ボディを追加
		this.mapIndex = mapIndex; // 配置したマップのIndexを保持
	},
	removeFromParent:function(){ // コインを削除
		this.space.removeStaticShape(this.shape); // SpaceからShapeを削除
		this.shape = null; // メモリ解放
		this.jsonCoinNode.node.stopAllActions(); // アニメーションを停止
		this.jsonCoinNode.node.removeFromParent(); // 親ノードから削除
		this.jsonCoinNode.node = null; // メモリ解放
		this.jsonCoinNode.action = null; // メモリ解放
		this.jsonCoinNode = null; // メモリ解放
	}
});

//岩クラス
var Rock = cc.Class.extend({ // cc.Classを継承
	space:null, // Spaceを保持
	sprite:null, // スプライトを保持
	shape:null, // Shapeを保持
	mapIndex:0, // 配置したマップのIndexを保持
	ctor:function(parent, space, posX, mapIndex){ // コンストラクタ
		this.space = space; // Spaceを取得
		var spriteFrame = cc.spriteFrameCache.getSpriteFrame("rock.png"); // スプライトシートから画像を取得
		this.sprite = new cc.Sprite(spriteFrame); // スプライトを作成
		var size = this.sprite.getContentSize(); // スプライトのサイズを取得
		var pos = cc.p(posX, size.height / 2 + g_groundHeight); // 配置する座標を計算
		this.sprite.setPosition(pos); // スプライトの位置を設定
		parent.addChild(this.sprite); // 親ノードに追加
		var body = new cp.StaticBody(); // 静的ボディを作成
		body.p = pos; // ボティの位置を設定
		this.shape = new cp.BoxShape(body, size.width, size.height); // 四角形状のShapeを作成
		this.shape.setCollisionType(SpriteTag.rock); // 衝突タイプ(衝突イベントの識別番号)を設定
		this.space.addStaticShape(this.shape); // Spaceに静的ボディを追加
		this.mapIndex = mapIndex; // 配置したマップのIndexを保持
	},
	removeFromParent:function(){ // 岩を削除
		this.space.removeStaticShape(this.shape); // SpaceからShapeを削除 
		this.shape = null; // メモリ解放
		this.sprite.removeFromParent(); // 親ノードから削除
		this.sprite = null; // メモリ解放
	}
});

//========== 4.ゲームのメインとなるクラス ==========
var PlayLayer = cc.Layer.extend({ // cc.Layerを継承
	space:null, // Spaceを保持
	jsonPlayScene:null, // PlaySceneのJSONデータ(ノードとアクション)を保持
	nodeGameLayer:null, // ゲームレイヤーを保持
	nodeGameOverLayer:null, // ゲームオーバーレイヤーを保持
	labelCoin:null, // コイン数ラベルを保持
	labelMeter:null, // メートル数ラベルを保持
	coins:0, // 取得コインの個数
	map00:null, // マップ00を保持
	map01:null, // マップ01を保持
	mapWidth:null, // マップの幅を保持
	mapIndex:0, // 現在のマップのIndex
	objects:[], // 配置済オブジェクト(コインと岩)の配列
	spriteRunner:null, // ランナーのスプライトを保持
	bodyRunner:null, // ランナーのBodyを保持
	state:RunnerState.running, // ランナーの状態を保持
	ctor:function () { // コンストラクタ
		this._super(); // 親クラスのメソッドを継承

		// ========== 4-1.画面のオブジェクトの取得と設定 ==========
		this.jsonPlayScene = ccs.load(res.PlayScene_json); // PlaySceneのJSONファイルを読み込む
		this.addChild(this.jsonPlayScene.node); // レイヤーに追加
		this.jsonPlayScene.node.runAction(this.jsonPlayScene.action); // ランナーのアニメーションの準備
		this.nodeGameLayer = this.jsonPlayScene.node.getChildByName("NodeGameLayer"); // GameLayerを取得
		this.map00 = this.nodeGameLayer.getChildByName("Map00"); // マップ00を取得
		this.map01 = this.nodeGameLayer.getChildByName("Map01"); // マップ01を取得
		this.mapWidth = this.map00.getContentSize().width; // マップの幅を取得
		this.labelCoin = this.jsonPlayScene.node.getChildByName("LabelCoin"); // LabelCoinの取得
		this.labelMeter = this.jsonPlayScene.node.getChildByName("LabelMeter"); // LabelMeterの取得
		this.nodeGameOverLayer = this.jsonPlayScene.node.getChildByName("NodeGameOverLayer"); // GameOverLayerの取得
		var buttonRestart = this.nodeGameOverLayer.getChildByName("ButtonRestart"); // ButtonRestartの取得
		buttonRestart.addTouchEventListener(function(sender, type){ // タッチイベントを設定
			switch (type) {
			case ccui.Widget.TOUCH_ENDED: // ボタンを離した時
				// リスタート処理
				for (var int = this.objects.length - 1; int >= 0; int--) { // 配置したオブジェクト(コインと岩)を削除
					var object = this.objects[int]; // オブジェクトを取得
					object.removeFromParent(); // 削除処理を実行
					this.objects.splice(int, 1); // 配列から削除
				}
				this.initGame(); // ゲームの初期設定
				this.nodeGameOverLayer.setVisible(false); // GameOverLayerを非表示
				cc.director.resume(); // メインループの再開
				break;
			}
		}, this);

		// ========== 4-2.物理演算の設定 ==========
		this.space = new cp.Space(); // Space(物理演算する空間)を作成
		this.space.gravity = cp.v(0, -350); // 重力を作成
		
		// 地面を作成
		var wallBottom = new cp.SegmentShape( // 線形状のShapeを作成
				new cp.StaticBody(), // 静的ボディを作成
				cp.v(0, g_groundHeight),  // 始点座標
				cp.v(4294967295, g_groundHeight), // 終点座標 4294967295はint最大値
				0); // 線の太さ
		this.space.addStaticShape(wallBottom); // Spaceに地面(静的ボディ)を追加

		// ランナーとオブジェクト(コインと岩)の衝突設定
		this.space.addCollisionHandler( // 衝突イベントを設定
				SpriteTag.runner, SpriteTag.coin, // ランナーとコインの衝突
				this.collisionCoinBegin.bind(this), // 衝突開始時
				null, null, null // その他のイベントはなし
		);
		this.space.addCollisionHandler( // 衝突イベントを設定  
				SpriteTag.runner, SpriteTag.rock, // ランナーと岩の衝突
				this.collisionRockBegin.bind(this), // 衝突開始時
				null, null, null // その他のイベントはなし
		);

		// ランナーの設定
		this.spriteRunner = this.nodeGameLayer.getChildByName("SpriteRunner"); // ランナーのスプライトを取得
		var size = this.spriteRunner.getContentSize(); // スプライトのサイズを取得
		this.bodyRunner = new cp.Body(1, cp.momentForBox(1, size.width, size.height)); // 四角形状のBodyを作成
		this.space.addBody(this.bodyRunner); // BodyをSpaceに追加
		var shapeRunner = new cp.BoxShape(this.bodyRunner, size.width - 14, size.height); // 四角形状のShapeを作成
		shapeRunner.setCollisionType(SpriteTag.runner); // 衝突タイプ(衝突イベントの識別番号)を設定
		this.space.addShape(shapeRunner); // SpaceにShapeを追加

		cc.eventManager.addListener({ // タッチイベントを登録
			event: cc.EventListener.TOUCH_ONE_BY_ONE, // シングルタッチのみ対応
			swallowTouches:false, // 以降のノードにタッチイベントを渡す
			onTouchBegan:function(){ // タッチ開始時
				this.jump(); // ランナーのジャンプを実行
				return false; // 以降の処理はしない
			}.bind(this) // タッチ開始時
		}, this);

		// ========== 4-3.ゲーム開始処理 ==========
		this.initGame(); // ゲームの初期設定
		cc.audioEngine.playMusic(res.background_mp3, true); // BGMを再生
		this.scheduleUpdate(); // 周期処理を開始
		return true;
	},
	loadObjects:function(map, mapIndex){ // マップにコインと岩を配置
		// コインを配置
		var coinGroup = map.getObjectGroup("coin"); // マップからコインのオブジェクトグループを取得
		var coinArray = coinGroup.getObjects(); // コインのオブジェクト配列を取得
		for (var int = 0; int < coinArray.length; int++) { // 各オブジェクトの位置にコインを配置
			this.objects.push( // 配置済みオブジェクト配列に追加
					new Coin( // コインを作成
							this.nodeGameLayer, // コインを追加するレイヤー
							this.space, // コインを追加するSpace
							cc.p(coinArray[int].x + this.mapWidth * mapIndex, coinArray[int].y), // コインの位置
							mapIndex // 配置するマップのIndex
					)
			);
		}

		// 岩を配置
		var rockGroup = map.getObjectGroup("rock"); // マップから岩のオブジェクトグループを取得
		var rockArray = rockGroup.getObjects(); // 岩のオブジェクト配列を取得
		for (var int = 0; int < rockArray.length; int++) { // 各オブジェクトの位置に岩を配置
			this.objects.push( // 配置済みオブジェクト配列に追加
					new Rock( // 岩を作成
							this.nodeGameLayer, // 岩を追加するレイヤー
							this.space, // 岩を追加するSpace
							rockArray[int].x + this.mapWidth * mapIndex, // 岩の位置のx座標
							mapIndex  // 配置するマップのIndex
					)
			);
		}
	},
	initGame:function(){ // ゲームの初期設定
		this.loadObjects(this.map00, 0); // マップ00にコインと岩を配置
		this.loadObjects(this.map01, 1); // マップ01にコインと岩を配置
		this.nodeGameLayer.setPosition(cp.vzero); // GameLayerを初期位置に配置
		this.map00.setPosition(cp.vzero); // マップ00を初期位置に配置
		this.map01.setPosition(cc.p(this.mapWidth, 0)); // マップ01を初期位置に配置
		this.mapIndex = 0; // 現在のマップIndexを0にする
		this.coins = 0; // 取得コイン数を0にする
		this.labelCoin.setString("Coins:0"); // labelCoinの表示を初期化
		this.labelMeter.setString("0M"); // labelCoinの表示を初期化
		
		this.bodyRunner.setVel(cp.vzero); // 速度を0にする
		this.bodyRunner.p = cc.p(g_runnerStartX, g_runnerStartY); // ランナーを初期位置に配置
		this.bodyRunner.w = 0; // ランナーの角度を0にする
		this.spriteRunner.setPosition(this.bodyRunner.p); // 位置を同期
		this.spriteRunner.setRotation(-cc.radiansToDegrees(this.bodyRunner.w)); // 角度を同期
		this.state = RunnerState.running; // ランナーの状態をランニングにする
		this.jsonPlayScene.action.play("Running", true); // ランニングのアニメを再生
		this.bodyRunner.applyImpulse(cp.v(150, 0), cp.vzero); // ランナーに力を加えてスタートさせる
		return;
	},
	collisionCoinBegin:function(arbiter, space){ // 衝突開始時イベント
		var shapes = arbiter.getShapes(); // 衝突したShapeの取得
		this.space.addPostStepCallback(function(){ // ステップ処理終了時に実行
			for (var int = 0; int < this.objects.length; int++) { // 衝突したコインを探す
				var object = this.objects[int]; // 配置済みオブジェクトの取得
				if (object.shape == shapes[1]) { // 衝突したコインの場合
					object.removeFromParent(); // 削除処理を実行
					this.objects.splice(int, 1); // 配列から削除
					break; // 処理を抜ける
				}
			}
		}.bind(this)); // レイヤーのthisを使えるようにする
		this.labelCoin.setString("Coins:" + ++this.coins); // 取得コイン数を加算
		cc.audioEngine.playEffect(res.pickup_coin_mp3); // コイン取得時のSEを鳴らす
		return true;
	},
	collisionRockBegin:function(arbiter, space){
		cc.director.pause(); // メインループを一時停止
		this.state = RunnerState.gameOver; // ランナーの状態をゲームオーバーにする
		this.nodeGameOverLayer.setVisible(true); // GameOverLayerを表示
		return true;
	},
	update:function(dt){
		this.space.step(dt); // 物理演算のステップ処理

		// ランナーのスプライトとBodyの同期
		this.spriteRunner.setPosition(this.bodyRunner.p); // 位置を同期
		this.spriteRunner.setRotation(-cc.radiansToDegrees(this.bodyRunner.w)); // 角度を同期

		// ランナーの移動量でスコアとレイヤー位置を更新
		var eyeX = this.spriteRunner.getPositionX() - g_runnerStartX; // ランナーの移動量を計算
		this.nodeGameLayer.setPosition(cc.p(-eyeX, 0)); // ランナーを定位位置に表示するため、GameLayerを逆に動かす
		this.labelMeter.setString(parseInt(eyeX / 10) + "M"); // ランナーの移動量を表示
		this.checkAndReload(eyeX); // マップの張替え処理

		// ランナーのステータスとアニメーションの切り替え
		var vel = this.bodyRunner.getVel(); // ランナーの速度を取得
		if (this.state == RunnerState.jumpUp) { // ランナーの状態がジャンプ上昇中の場合
			if (vel.y < 0.1) { // 上昇速度が落ちた場合
				this.state = RunnerState.jumpDown // ランナーの状態をジャンプ下降中に設定
				this.jsonPlayScene.action.play("JumpDown", false); // 加工中のアニメーションを再生
			}
		} else if (this.state == RunnerState.jumpDown) { // ランナーの状態が加工中の場合
			if (vel.y == 0) { // ランナーの下降速度が0の場合
				this.state = RunnerState.running // ランナーの状態をランニングに設定
				this.jsonPlayScene.action.play("Running", true); // ランニングのアニメーションを再生
			}
		}
	},
	checkAndReload:function(eyeX){ // マップの張替え処理
		var newMapIndex = parseInt(eyeX / this.mapWidth); // マップのIndexを計算
		if (this.mapIndex == newMapIndex) return; // マップのIndexに変化がない場合は処理を抜ける
		if (0 == newMapIndex % 2) { // マップのIndexが2で割り切れる場合
			this.map01.setPositionX(this.mapWidth * (newMapIndex + 1)); // マップ01を配置
			this.loadObjects(this.map01, newMapIndex + 1); // マップにコインと岩を配置
		} else { // それ以外の場合
			this.map00.setPositionX(this.mapWidth * (newMapIndex + 1)); // マップ00を配置
			this.loadObjects(this.map00, newMapIndex + 1); // マップにコインと岩を配置
		}
		var removeMapIndex = newMapIndex - 1; // オブジェクトを削除するマップのIndexを計算
		for (var int = this.objects.length - 1; int >= 0 ; int--) { // 削除するオブジェクトを探す
			var object = this.objects[int]; // 配置済みオブジェクトを取得
			if (object.mapIndex == removeMapIndex) { // 削除対象のマップのIndexの場合
				object.removeFromParent(); // 削除処理を実行
				this.objects.splice(int, 1); // 配列から削除
			}
		}
		this.mapIndex = newMapIndex; // 現在のマップのIndexを更新
	},
	jump:function(){ // ランナーのジャンプ処理
		if (this.state == RunnerState.running) { // ランナーの状態がランニングの場合
			this.state = RunnerState.jumpUp; // ランナーの状態を上昇中にする
			this.jsonPlayScene.action.play("JumpUp", false); // 上昇中のアニメーションを再生
			cc.audioEngine.playEffect(res.Jump_mp3); // ジャンプ時のSEを鳴らす
			this.bodyRunner.applyImpulse(cp.v(0, 250), cp.v(0, 0)); // 上向きの力を加える
		}
	}
});

//========== 5.シーンを作成するクラス ==========
var PlayScene = cc.Scene.extend({ // シーンを作成
	onEnter:function () { // 表示開始時
		this._super(); // 親クラスのメソッドを継承
		var layer = new PlayLayer(); // レイヤーを作成
		this.addChild(layer); // レイヤーをシーンに追加
	}
});

『1.グローバル変数の定義』

 プロジェクト全体で使用する変数です。
 本来なら定数で定義したいところですが、IEの『const』サポートの有無がわからないので変数で定義しています。

『2.列挙型の定義』

 列挙型の書き方はチュートリアルの書式に倣いました。

『3.オブジェクトの設定・配置を行うクラス』

 オブジェクト(コインと岩)を配置するクラスです。
 チュートリアルでは、スプライトを『cc.PhysicsSprite』で作成していますが、ここではコインは『Cocos Studio 2』で作成したノードを使用し、岩は『cc.Sprite』を使用しています。
 『cc.PhysicsSprite』は物理演算上の物体を保持できますが、クラスがその役割を担っています。またコインも岩も静的ボディなので、配置した後に物体とスプライトの位置の同期を行う必要はありません。

『4.ゲームのメインとなるクラス』

4-1.4-1.画面のオブジェクトの取得と設定

 『Cocos Studio 2』で作成した画面の各オブジェクトを取得し、機能を設定しています。

4-2.物理演算の設定

 物理演算エンジンの設定を行っています。
 地面はランナーの進行方向である画面右側に最大限伸ばしています。
 また、ランナーとコイン、ランナーと岩が接触したときの処理を設定しています。
 ランナーも『cc.PhysicsSprite』を使用していません。物体とスプライトの位置・角度の同期は周期処理(update:function(){})の中で自前で行っています。
 ランナーのジャンプ操作は、レイヤーのタッチイベントに設定しています。

4-3.ゲーム開始処理

 スコア表示、ステージの配置、ランナーの位置・状態等をゲーム開始時の状態に設定し、ゲームのメインループを開始します。
 BGMも流します。

メソッド

loadObjects()

 タイルマップに配置したオブジェクトの位置情報を取得し、コインと岩を配置します。
 配置したオブジェクトは配列(objects[])で管理します。

initGame()

 スコア表示、ステージの配置、ランナーの位置・状態等をゲーム開始時の状態に設定します。
 ゲーム開始時とリスタート時に実行します。

collisionCoinBegin()

 ランナーとコインが接触した時の処理です。
 物理演算で衝突した物体をオブジェクトの配列(objects[])から検索し、削除します。
 スコアを加算し、SEを鳴らします。

collisionRockBegin()

 ランナーと岩が接触した時の処理です。
 ランナーが岩に接触すると即ゲームオーバーです。
 リスタートボタンを含んだゲームオーバー時のレイヤーを表示します。

update()

 ゲームのメインループとなる周期処理です。
 『cc.PhysicsBody』を使用していないので、ランナーのスプライトの位置と角度を自前で同期します。
 ランナーは実際には前方に進んでいますが、ランナーが進んだ分だけゲームのステージを逆方向に動かすことでランナーを定位置に表示しています。
 parseIntは小数点以下を切り捨てです。ちなみにMath.floor()は切り下げです。
 ランナーの進んだピクセルの10分の1をゲーム内の1Mとしています。
 ランナーの現在の状態(state)と物理的な上昇速度によって、ランナーのアニメーションを切り替えます。

checkAndReload()

 ステージの表示位置をチェックして、画面外に移動したタイルマップを表示中のタイルマップの後ろに移動させています。
 移動する前に取り残したコインを全て削除し、移動後にコインを再配置します。
 コインと岩の自身を削除するメソッド(removeFromParent())の名前を同一にしているのでまとめて削除できます。
 削除対象のコインはmapIndexで判断しています。

jump()

 ランナーに上向きのインパルスを与えることで、ジャンプしています。
 ジャンプ時にランナーの状態(state)とアニメーションを変更し、SEを鳴らします。

『 5.シーンを作成するクラス』

 ゲームのメインレイヤーである『PlayLayer』を含んだシーンを作成します。

デモ

 作成したゲームのデモです。
 画面をタッチするとランナーがジャンプします。
 *音が出ますのでご注意を。
http://githubmorley.github.io/cocosprojects-pages/hellococos07/
  
 以上です。

あとがき

 今回『cc.PhysicsSprite』を使用しなかったのは、まず単純に『Cocos Studio 2』で作成したノードやスプライトを『cc.PhysicsSprite』に変換する方法がわからなかったためです。『cc.PhysicsSprite』の子ノードに追加しても子ノードは物体(cc.Body)と同期して動きませんでした。ただそこまで『cc.PhysicsSprite』にこだわらなくてもいいかなと思い、自分で管理するようにしました。
 
 チュートリアルのゲームのリスタート処理は、単にrunScene(new PlayScene())でシーンを作り直すだけなのですが、何故か6回目のリスタートでアプリが落ちてしまうという現象がありました。Xcodeのsimulatorでも同様でした。
 結局解決が出来なかったので、シーンを作りなおすのではなく、再度初期設定をするという方法をとりました。
 ちなみにブラウザ起動だとシーンの作り直しでも大丈夫でした。
 
 また別の問題として、これはブラウザ起動の方だけですが、変数の値が意図せず書き換わるという現象が起きました。具合的にはランナーのスタート位置を変数で保持していたのですが、これがなぜかランナーの現在位置に書き換わっていました。同じソースコードMac上のデバッグ実行ではこの問題は起きていません。結局これも原因がわからず、変数で保持するのをやめて対処しました。はじめはブラウザでもうまく動いてたと思うのですが気のせいだろうか、、、
 私の単純なケアレスミスか、Cocos2d-xやJavaScriptの理解が浅いせいかもしれません。