1ヶ月でiOS・Android対応のクソゲーを作るためのレシピ

おっ立ち野郎が2014年12月29日15:48:11に投稿しました。

C++

14

お気に入り

準備中

Views

このレシピで作るもの

今回は今話題の「DOGECOIN」で有名な柴犬の「かぼすちゃん」を使ってゲームを作りたいと思います。

ということで、以下のようなゲームを作ります。

かぼすちゃんが次々と空から降ってくる
かぼすちゃんが地面に付く前にタッチで消していく
かぼすちゃんが一匹でも地面についたらゲームオーバー
ちなみにゲーム名はDoge Defenseです。

Cocos2dxを使って作っていきます。


目次

Cocos2d-xを使う

今回はCocos2d-xを使ってゲームを作っていきます。

http://www.cocos2d-x.org/

Cocos2d-xはスマホ向け2Dゲームを作るためのゲームエンジンで、今スマホ2Dゲーム界で熱いらしいです。

有名どころだとドラクエのゲームがCocos2d-xで作られたらしいです。

http://www.dragonquest.jp/dqmsl/

他にもCocos2d-xで作られたゲームはこちらにあります

http://www.cocos2d-x.org/games

めちゃめちゃあります。もちろんぴよ飛ばしもCocos2dxで作りました。

僕が何より凄いと思ったのは、一つコードをかけば、AndroidにもiOSにも対応したアプリを作ることができること。わざわざコードをiOS用、Android用と2つ書くなんてやってられませんね。Cocos2d-x凄い。

日本語の情報も多いのでこれでやっていきます。

手順1 開発環境をつくってHello Worldする(1~3時間)

最初は開発環境を作りましょう。この記事ではWindows7 + Eclipseでやっていきます。

実際にAndroidの実機で「Hello World」するところまでやりますね。(iOSで動かすにはMacが必要です。)

しかし安心してください。コードは一行も書きません。

Eclipse(ADT)+Android SDK

http://developer.android.com/sdk/index.html

ここからダウンロードしてください。最初からAndroid開発用にカスタマイズされたEclipseを手に入れることができます。

解凍して、適当なフォルダに突っ込んでください。

僕はここに

C:\Devel\adt-bundle-windows-x86_64-20140321

日本語化(オプション)

しなくてもいいのですが。

http://mergedoc.sourceforge.jp/

上からプラグイン・ダウンロードから1.4.0をダウンロードして解凍してください。

Eclipseを開いていたら閉じてください。

feauresフォルダとpluginsフォルダ、eclipse.exe -clean.cmdファイルをEclipseのディレクトリへつっこんでください。はい、全部上書きしましょう。

そしてeclipse.iniを右クリックから編集で開き、以下を最後の行にコピペしてください。

-javaagent:plugins/jp.sourceforge.mergedoc.pleiades/pleiades.jar

eclipse.exe -clean.cmdからeclipseを起動して、日本語化完了です。

Android NDK

[https://developer.android.com/tools/sdk/ndk/index.html:title]

問題があるようなのでダウンロードURLの「r9d」の部分を「r8e」に変更し、ダウンロードして適当なフォルダに解凍してください。

C:\Devel\android-ndk-r8e

Cocos2d-x

http://www.cocos2d-x.org/download

ここから「Download v2.2.3」をダウンロードして解凍し、適当なフォルダに突っ込んでください。

v3.xはまだ日本語情報があまりなく、初心者には厳しいものがありますので、v2.2.3をダウンロードしてください。

C:\Devel\cocos2d-x-2.2.3

Pythonのインストール

こちらから2.7.6のインストーラをDLして実行してください。新しくゲームを作るときに必要にな、配布されたツールを動かすのに使います。

https://www.python.org/download/

PATHも通してください

http://www.pythonweb.jp/install/setup/index1.html

コマンドプロントから(python --version)ができるようになったら成功です

Cygwin

ここからCygwinをDLしてインストールしてください

http://cygwin.com/install.html

インストーラの指示通りににDLすればいいです。

途中でmakeと入力してインストールしましょう

20140401143935.png

そしてPythonと同じ手法で

C:\cygwin\bin

にパスを通してください(cygwinの部分は各自変更してください)

JDK

ここからインストールしてください

http://www.oracle.com/technetwork/java/javase/downloads/index.html

Eclipseの設定

Eclipseの設定に入ります。

Cocos2dxのフォルダをリンクされたリソースに追加

ウィンドウ->設定->一般->ワークスペース->リンクされたリソース

より新規ボタンを押してCocos2dxのフォルダを指定します

  • 名前: COCOS2DX
  • ロケーション: C:\Devel\cocos2d-x-2.2.3

f:id:ottati:20140401185816p:plain

C++のビルド設定

ウィンドウ->設定 ->C/C++->ビルド->環境

より、追加ボタンを押して以下3つの値を入力します。

  • 変数: CYGWIN
  • 値: nodosfilewarning

  • 変数: NDK_ROOT

  • 値: C:\Devel\android-ndk-r8e

  • 変数: SHELLOPTS

  • 値: igncr

libcocos2dxをインポートする

環境構築も終盤です。

ファイル->インポート->Android->Existing Android Code Into Workspace

より

C:\Devel\cocos2d-x-2.2.3\cocos2dx\platform\android\java

をルートディレクトリに入力してください。

「プロジェクトをワークスペースにコピー」にはチェックをいれないでください。

20140401185816.png

そして完了ボタンを押してください。

そうすると左側のプロジェクト・エクスプローラーと呼ばれる場所に"libcocos2dx"が表示されると思います。

そしてコンソールには「Unable to resolve target 'android-8'」と表示されると思います。これはandroid-8、つまり Android2.2のSDKがないということです。

なのでウィンドウ->Android SDK マネージャーよりAndroid2.2のSDKをインストールしてください。

以下のような感じでインストールしましょう。

20140401190932.png

結構DLに時間がかかります。なので次にいきましょう。

プロジェクトを作成する

やっとゲームのプロジェクトを作成することができるところまできました。

Windowsキーを押しながらRキーを押して「cmd」と入力してください。

コマンドプロントが出てくると思います。黒くてなんだか嫌ですが安心してください。コマンドを使うのはこの時くらいです。

cd C:\Devel\cocos2d-x-2.2.3\tools\project-creatorと入力してください。

それかエクスプローラで上記フォルダまでいって何も無いところでシフトキーを押しながら右クリック、「コマンド ウィンドウをここで開く」をクリックしてください。

そして以下を入力してください

python create_project.py

これでcreate_project.pyの使い方が出てきます。create_project.pyはゲームのプロジェクトを作成するものです。

プロジェクト名、パッケージ名、言語名を指定してプロジェクトを作ります

今回は

  • プロジェクト名: MyGame
  • パッケージ名: com.example.MyGame
  • 言語: cpp

とします。cppはC++のことです。

よって入力するコードは次のようになります

20140401192642.png

「Have Fun!」と言われたらプロジェクトが正常に作られたことになります。

Have Funの前をよく見ると生成されたプロジェクトフォルダの位置が示されています

今回は以下となります

C:\Devel\cocos2d-x-2.2.3\projects\MyGame

create_project.pyはprojectsフォルダにプロジェクトを生成するようになっているんですね。

MyGameをEclipseで読み込む

では早速読み込みましょう。「Hello World」直前です。

ファイル->新規->インポート->Android->Existing Android Code Into Workspace

より先ほど作ったプロジェクトのフォルダにあるproj.androidを指定しましょう

20140401193250.png

完了ボタンを押すと、Eclipseが頑張り始めますので温かい目で見てあげましょう。なにかエラーがでましたらエラー内容をコピってググりましょう。

実際に実機で動かしてみる

USBケーブルでAndroidとPCをつないで実行してみましょう。

Androidの設定から開発者向けオプションのUSBデバッグにチェックを入れてください。

20140401201006.png

AndroidにMyGameという名前でアプリがインストールされました!

Androidで自動的にアプリが立ち上がらない場合は手動でアイコンをタップして起動してください。

こんな感じで表示されたら成功です!!

20140401202518.png

準備はできました。あとはゲームを作るだけです。

番外編: よくわからないところでエラーが出て実行出来ない時

ウィンドウ->設定->C/C++->コード解析

についてるチェックを適当に外しましょう。

手順2 HelloWorldを他の文字列に変えてみる。 (1分)

では実際にゲームの内容を変更してみます。

MyGame内のClassesの中にあるC++ファイルを変更、作成することでゲームを作っていきます。

とりあえずHelloWorldScene.cppを開いてみましょう。実機で表示されたものはHelloWorldScene.cppでプログラムされています。

始めにAppDelegate.cppが呼ばれ、AppDelegate.cppからHelloWorldScene.cppを呼び出すような流れとなっています。

HelloWorld::init()

このメソッドでHelloWorldや画像の表示の処理をしています。

この中から以下のコードを見つけてください

    CCLabelTTF* pLabel = CCLabelTTF::create("Hello World", "Arial", 24);

これでHelloWorldというラベルを生成しています。

なのでちょこっと変えてみましょう。

    CCLabelTTF* pLabel = CCLabelTTF::create("Have a nice day", "Arial", 24);

保存したらF11を押して実行してみましょう。

HelloWorldがHave a nice dayに変更されたと思います。

このようにClassesの中をいじくってゲームを作っていく流れとなります。

手順3 C++の初心者になる(2~10日)

Cocos2d-xではC++を使います。なのでC++の基礎をやります。

しかし、やらなくてもこの記事に書いてある通りにコードを書いていけば一つのゲームアプリが完成します。とりあえず作っちゃおうという方は飛ばしてください。

とりあえずこちらのサイトの項目を全て見たり書き写したりしてください。

http://www.asahi-net.or.jp/~yf8k-kbys/newcpp0.html

「前提知識を必要としない」がモットーの資料なので、初心者にぴったりなわけです。実際に僕もぴよ飛ばしを作る前まではC++のことを何一つ知らなかったので、このサイトを使って勉強しました。

人生における最大の才能は「おもしろい」と思う性格でしょう。

などと、面白いコメントも沢山あり、楽しめるでしょう...

手順4 クソゲーを作ってみる(0時間~3時間)

では実際にクソゲーを作っていきましょう!

今回は今話題の「DOGECOIN」で有名な柴犬の「かぼすちゃん」を使ってゲームを作りたいと思います。

Dogecoinって何?って方はこちら(楽しい動画もありますよXD)

http://dogecoin.com/

かわいい。

ということで、以下のようなゲームを作ります。

  1. かぼすちゃんが次々と空から降ってくる
  2. かぼすちゃんが地面に付く前にタッチで消していく
  3. かぼすちゃんが一匹でも地面についたらゲームオーバー

ちなみにゲーム名はDoge Defenseです。

想像図。

20140404170806.png

作りました。

タッチしてスタートだ!!

20140404145521.png

かぼすちゃんが降ってくるのでタッチで消そう!!

20140404145333.png

一匹でも落ちたらゲームオーバーだ!!

20140404145508.png

これを手順1で作ったMyGameで作っていきます。

素材とかをDLする

githubにソースコードと一緒に素材もアップしました。ソースコードは完成されているので記事を見る上で参考にしてください。

https://github.com/ottatiyarou/DogeDefense

画像(美的センスが合わないね!って場合は差し替えてください)とか、音楽ファイル(天下の魔王魂さんからお借りしました)とか入ってます。

よくわからない方はページ右下の「Download ZIP」からdlしてください。

「コードを一行も書きたくない」「よくわからない」って場合はMyGameフォルダ以下のClassesフォルダとResourcesフォルダを上書きして実行しちゃってください。すぐにかぼすちゃんまみれになれます。

じゃあ早速作っていきましょう!

サンプルのお掃除

なんかHelloWorldScene.cppを開くと色々ごちゃごちゃしているので、綺麗にしましょう。

init()の中身を以下を残して消しましょう。

bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !CCLayer::init() )
    {
        return false;
    }


    return true;
}

そしてmenuCloseCallbackも消しちゃいましょう

void HelloWorld::menuCloseCallback(CCObject* pSender)
{
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) || (CC_TARGET_PLATFORM == CC_PLATFORM_WP8)
    CCMessageBox("You pressed the close button. Windows Store Apps do not implement a close button.","Alert");
#else
    CCDirector::sharedDirector()->end();
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
    exit(0);
#endif
#endif
}

最後にHelloWorldScene.hの

    // a selector callback
    void menuCloseCallback(CCObject* pSender);

を消して実行してみてください。

ただの真っ暗の画面が表示されたら掃除成功です!

画面の設定をする

ゲーム内で使う画面サイズを決めます。

AppDelegate.cppの

pDirector->setOpenGLView(pEGLView);

の直下に以下を描いてください

    CCSize designSize = CCSize(480, 320);
    CCEGLView::sharedOpenGLView()->setDesignResolutionSize(designSize.width,
            designSize.height, kResolutionExactFit);

CCEGLView::sharedOpenGLView()->setDesignResolutionSizeはゲーム内で使う画面のサイズを決めます。

実際に実機に表示するときにはこれを引き伸ばしたり縮めたりして調節します。

20140402233759.png

どう引き伸ばすか(縮めるか)は第三引数に指定します。kResolutionExactFitはアスペクト比を無視して画面一杯に引き伸ばす方法です。

こちらが参考になると思います

http://hiratti41.blogspot.jp/2012/10/cocos2d-xsetdesignresolutionsize.html

今回は横幅480px, 縦幅320pxでいきます。例えば画面の横半分のサイズの画像を貼り付けたい場合は(240, 320)のサイズの画像を作ればいいわけです。

舞台を作る

画面のサイズ(480px, 320px)を考慮しつつ、かぼすちゃんが降ってくる舞台を創りましょう。

  • ウィンドウのサイズを取得する
  • 太陽を貼っつける
  • 草を貼っつける
  • 背景を黒=>青
ウィンドウのサイズを取得する

上で指定した値、480, 320は以下の関数で取得します。

CCDirector::sharedDirector()->getWinSize();

CCSizeオブジェクトが返ります。これはwidthとheightというメンバ変数を持っています。

HelloWorldScene.hを開いて

#include "cocos2d.h"

の下に

USING_NS_CC;

と書きましょう。USING_NS_CCは

using namespace cocos2d

を示すマクロで、CCPlatformMacros.hで宣言されています。

そしてスプライトを貼っつけたりするときによく使うのでprivateに以下を宣言しましょう。

CCSize _winSize;

そして.cppのほうのinit()のreturn true;の前に以下を書きましょう。

_winSize = CCDirector::sharedDirector()->getWinSize();

これで_winSize.width, _winSize.heightで画面の横幅、縦幅を簡単に取得できるようになりました。

太陽を貼っつける

太陽を貼っつけましょう。Resourcesのsun.pngを使います。

_winSizeを宣言した後に以下を書いて実行してください。

    // sun
    CCSprite * sun = CCSprite::create("sun.png");
    sun->setAnchorPoint(ccp(1, 1));
    sun->setPosition(ccp(_winSize.width, _winSize.height));
    this->addChild(sun, 0);

画面右上になんとも可愛らしい太陽が現れました。

画像を貼っつけるときはCCSpriteを生成してsprite->setPosition()で座標を決め、this->addChild()で画面にはっつけるという流れとなります。

setAnchorPointはスプライトの中心点を決めるメンバ関数です。setPositionで座標を決めるときに参照されます。

20140404164701.png

デフォルトではccp(0.5, 0.5)、つまり画像の真ん中です。

ccpはCCPointを簡単に生成できるマクロです。

setPosition()で座標を決めています。先ほど作った_winSizeを使っていますね。
this->addChild(sun, 0)の0の部分は奥行きzの数値です。大きいほうが前にでてきます。

20140404165527.png

草を貼っつける

太陽と同じ流れで貼っつけます。草の画像はgrasses.pngです。

太陽をaddChildした下に書いてください。

    // grasses
    CCSprite * grasses = CCSprite::create("grasses.png");
    grasses->setAnchorPoint(ccp(0, 0));
    grasses->setPosition(ccp(0, 0));
    this->addChild(grasses, 3);

実行すると美味しそうな草が貼っ付けられたことがわかります。

背景の色を黒から青へ

太陽があるのに空が黒いのは全くもって変なので、明るくしましょう。

HelloWorldScene.hの

class HelloWorld: public cocos2d::CCLayer

class HelloWorld: public cocos2d::CCLayerColor

として、

.cppの

    // 1. super init first
    if (!CCLayer::init()) {
        return false;
    }

    // 1. super init first
    if (!CCLayerColor::initWithColor(ccc4(200, 230, 255, 255))) {
        return false;
    }

として実行してください。明るくなりましたね。

CCLayerColorはCCLayerを継承したクラスで、色を指定することができます。
ccc4はccColor4Bオブジェクトを返すマクロです。RGBαを引数にとっています。

これで舞台は完成しました!!

わんこを降らせる

さて、さっそくわんこを降らせましょう。わんこを降らせるロジックはこんな感じです。

20140404171726.png

わんこを最初に大量につくっといて、順番にふらせます。しかしはてなブログのお絵描き機能、使いづらいですね。

わんこを降らす準備

では初めにわんこのプール(わんこ待機場)を作りましょう。

.hのprivateに

CCArray * _doges;  // わんこ待機場

を宣言しましょう。CCArrayにはスプライトとかを突っ込むことができます。

あとデストラクタが宣言されていないと思うのでpublicのほうに以下を宣言しておいてください。

~HelloWorld();

いよいよdogeを作ります。

.cppのUSING_NS_CCの下に

#define NUM_DOGES 25

と宣言してください。待機場にわんこは最大で25匹たまります。(つまり画面上に一度にでてくるわんこのMAXも25です)

とりあえず待機場を作ります。

init()の草を追加したコードの下に以下です。

    // doges
    _doges = CCArray::create();

さあわんこを25匹作ってCCArrayの中につっこみましょう。

しかし待ってください。25匹も普通に作ると沢山わんこを描かなければならないので処理が重たくなってしまいます。

なのでCCSpriteBatchNodeというのを使います。

同じ画像(テクスチャ)のスプライトを大量生産する時は、そのテクスチャを持ったバッチノードを作り、そこにスプライトを追加していきます。これによって複数回しなければならなかった描画処理をたった一回で行うことができます。

バッチノードは普通にthis->addChildではっつけてください。

    CCSpriteBatchNode * dogeNode = CCSpriteBatchNode::create("doge.png");
    this->addChild(dogeNode, 0);

それでは柴犬を25匹作りましょう。

柴犬のスプライトを作る時はバッチノードからテクスチャを持ってきてcreate()の代わりにcreateWithTextureを使います。

    for (int i = 0; i < NUM_DOGES; i++) {
        CCSprite * doge = CCSprite::createWithTexture(dogeNode->getTexture());
        doge->setVisible(false);
        doge->setScale(0.5f);
        dogeNode->addChild(doge, 1);
        _doges->addObject(doge);
    }

setVisible(false);では最初は画面から見えないようにしています。

それとかぼすちゃんの顔をちょっとでかくしてしまいました。なのでsetScale(0.5f)でちっちゃくしています。

あとは作ったスプライトをバッチノードに追加し、わんこ待機場にも追加しておきます。

そして先ほどのコードの最後に以下を追加してretainしてください。

    _doges->retain();

最後に#define NUM_DOGES 25の下に以下を書いてください

HelloWorld::~HelloWorld() {
    CC_SAFE_RELEASE(_doges);
}

retain(), CC_SAFE_RELEASEなどについてはこちらの記事が詳しくまとめてらっしゃいます。

http://brbranch.jp/blog/201311/cocos2d-x/cocos2dx_memory_leak/

実行してエラーがでなかったら多分成功です。よくわからない時は一回一回実行してみるといいかもしれません。

わんこを降らす

一応このゲームのメインな処理です。

待機場から順番にわんこを降らせます。

再掲(する価値はこれにあるのか...)

20140404171726.png

.hのほうのprivateに以下を宣言してください。

    int _dogeIndex;
    float _dogeTimer;
    float _dogeInterval;

_dogeIntervalごとに_dogeIndex番目のわんこを降らせていきます。

_dogeTimerは時間を貯める変数です。_dogeTimerが_dogeIntervalを超えたら_dogeIndex番目のわんこを降らせて、_dogeTimerを0に戻します。

あとpublicの方に

void update(float dt);

を宣言してください。
この関数は1フレーム毎に一回呼び出されます。デフォルトでは1秒間に60回呼び出されます。

しかしそのままではupdate関数は呼び出されないのでinit()のreturn trueの前でthis->scheduleUpdate();しましょう。

ついでに先ほど宣言した変数の初期化もしましょう。

// 初期化
_dogeTimer = 0;
_dogeInterval = 0;
_dogeIndex = 0;

// update関数が呼び出されるように
this->scheduleUpdate();

と書いてください。これでupdate関数が呼び出されるようになりました。

update関数を宣言しましょう。少し長いですが、わかりやすさのために一気に書いたほうがよかったのです。

void HelloWorld::update(float dt) {
    _dogeTimer += dt;
    if (_dogeTimer >= _dogeInterval) {
        // インデックスのわんこを取得
        CCSprite * doge = (CCSprite *) _doges->objectAtIndex(_dogeIndex);

        if (doge->isVisible()) { // 既に画面上にそのわんこが居たら何もしない
            return;
        }

        // インデックスの更新
        _dogeIndex++;
        if (_dogeIndex == NUM_DOGES) {
            _dogeIndex = 0;
        }

        // 犬が出てくるx座標と犬の落下地点のx座標を決める
        int x = arc4random() % (int) (_winSize.width * 0.8f)
                + _winSize.width * 0.1f;
        int target_x = rand() % (int) (_winSize.width * 0.8f)
                + _winSize.width * 0.1f;
        doge->stopAllActions();  // 犬のアクションを止める

        //犬を画面のちょうど見えないところに設置
        doge->setPosition(
                ccp(x,
                        _winSize.height
                                + doge->boundingBox().size.height * 0.5f));

        // わんこのアクションを作成

        // わんこがくるくる回転するアクション
        float rotateSpeed = (arc4random() % 10) * 0.1f;
        CCActionInterval* rotate = CCRotateBy::create(rotateSpeed, -90);  
        CCAction* repeatRotate = CCRepeatForever::create(rotate);

        // わんこが落ちていくアクション
        CCFiniteTimeAction* sequence = CCSequence::create(
                CCMoveTo::create(3.0f,
                        ccp(target_x, doge->boundingBox().size.height * 0.3f)),
                NULL);

        doge->setVisible(true);
        doge->runAction(repeatRotate);
        doge->runAction(sequence);

        // reset Doge Timer
        _dogeTimer = 0;
        _dogeInterval = 0.2f + (arc4random() % 5) * 0.1f;
    }
}

早速実行してみてください。わんこがくるくる回りながら落ちてきて下に溜まっていくと思います。なんて素晴らしい画なんでしょう♡

スプライトのメンバ関数、boundingBox()はスプライト自身のwidth, heightを持ったオブジェクトを返すものです。

以下のコードではわんこ置き場にある_dogeIndex番目のわんこを取り出しています。

CCSprite * doge = (CCSprite *) _doges->objectAtIndex(_dogeIndex);

25匹でてきてもう落ちてこないのは以下のコードでチェックしているからです。わんこを落とすときにsetVisible(true)の処理を与えているのでこのチェックが使えます。(あとでタッチした時にsetVisible(false)の処理を与えます。)

        if (doge->isVisible()) { // 既に画面上にそのわんこが居たら何もしない
            return;
        }

ランダムを使うときはarc4random()を使うと楽です。

arc4random() % 4

で0~3の値を取得できます。

以下のコードではわんこのアクションを作っています。

        // わんこがくるくる回転するアクション
        float rotateSpeed = (arc4random() % 10) * 0.1;
        CCActionInterval* rotate = CCRotateBy::create(rotateSpeed, -90);  // 回転アクション
        CCAction* repeatRotate = CCRepeatForever::create(rotate); // 無限リピートアクション

        // わんこが落ちていくアクション
        CCFiniteTimeAction* sequence = CCSequence::create(
                CCMoveTo::create(3.0f,
                        ccp(target_x, doge->boundingBox().size.height * 0.3f)),
                NULL);

CCActionはdoge->runAction();で使うことが出来ます。

CCSequenceは複数のアクションを順番に実行していくアクションです。複数?一個しかアクション無いじゃん。と思ったあなたは正解です。実はこのあと落下時のアクションを足します。なのでCCSequenceを使っているのです。

以下では落下地点(ccp内の座標)まで1秒かけて到達するように動かすというアクションを生成しています。

                CCMoveTo::create(1.0f,
                        ccp(target_x, doge->boundingBox().size.height * 0.3f)),

実行しているアクションはdoge->stopAllActions()で中止することができます。

cocos2dxでは様々なアクションが用意されていて、簡単にスプライトを動かしたり点滅させたりジョジョに色を変えたりすることが出来ます。

こちらの記事ではさまざまなアクションが画像付きで紹介されています。

http://brbranch.jp/blog/201309/cocos2d-x/cocos2d-x_animation/

タッチで消す処理

メイン2です。落ちてくるわんこをタッチして消す処理をします。

とりあえずタッチした時にログを出してみましょう。

.hのpublicに以下のコードを追加してください。

bool ccTouchBegan(CCTouch * touch, CCEvent * event);

そしてinit()のthis->scheduleUpdate();の上に以下のコードを追加してください。

    this->setTouchMode(kCCTouchesOneByOne);  // シングルタッチモード
    this->setTouchEnabled(true);  // タッチ機能の有効化

そして.cppに以下の関数を追加してください。

bool HelloWorld::ccTouchBegan(CCTouch * touch, CCEvent * event) {
    CCLOG("touched");
    return true;
}

実行してみてください。画面をタッチする度にLogCatにtouchedと表示されたと思います!

ちゃんとタッチが認識されたので、今度はタッチしたところが犬の座標とぶつかるか見ていきます。ぶつかっていたら犬を消しましょう。

ccTouchBeganの中身を以下のようにしましょう。

    CCSprite * doge;
    CCObject * obj;

    CCARRAY_FOREACH(_doges, obj) {
        doge = (CCSprite *) obj;
        if (!doge->isVisible()) {
            continue;
        }
        if (doge->boundingBox().containsPoint(touch->getLocation())) { // タッチされた
            doge->stopAllActions();
            doge->setVisible(false);
        }
    }
    return true;

実行してみてください。降ってくるわんこをタッチするとわんこが消えたと思います。

CCArrayは以下のようにして中身を取り出すことが出来ます。

    CCSprite * doge;
    CCObject * obj;

    CCARRAY_FOREACH(_doges, obj) {
        doge = (CCSprite *) obj;
    }

書いたコードでは、最初に犬のvisibleを見ています。消えていたらタッチしても意味ないのでcontinueします。

そうでないならば、touch->getLocation()でタッチされた座標の情報をもったCCPointオブジェクトを取得し、doge->boundingBox().containsPoint()で座標がわんこと被っているかみています。

もしかぶっていたらわんこのアクションを止め、setVisible(false)します。これでタッチでわんこを消すことが出来ました!

わんこが地面にぶつかった時の処理

これは柴犬を地面に貯めていくゲームではありません!

わんこが地面につくまでにタッチされなかったらゲームを終了させるべきです。

privateに

void hitGround();

を宣言してください。

この関数はかぼすちゃんが地面についたら実行されるもので、全てのわんこの動きを止め、ゲームの更新を止める働きをします。

void HelloWorld::hitGround() {
    // わんこのアクションを止める
    CCSprite * doge;
    CCObject * obj;
    CCARRAY_FOREACH(_doges, obj) {
        doge = (CCSprite *) obj;
        doge->stopAllActions();
    }

    // ゲームの更新を止める
    _running = false;

}

ところでまだ_runningを宣言していませんでしたね。

privateに以下を宣言してください。

    bool _running;

init()のthis->setToucheModeの前に

_running = true;

を書いてください。

そしてupdate()関数の一番上に以下を書いてください。

    if (!_running) {
        return;
    }

これで_runningがtrueの時にだけゲームの更新処理が行われるようになりました。

次にかぼすちゃんの落下処理シーケンスを変更して、落下後にhitGroundを呼び出すようにします。

        CCFiniteTimeAction* sequence = CCSequence::create(
                CCMoveTo::create(3.0f,
                        ccp(target_x, doge->boundingBox().size.height * 0.3f)),
                // hitGround呼び出し
                CCCallFunc::create(this,
                        callfunc_selector(HelloWorld::hitGround)),
                NULL);

これで実行してみてください。かぼすちゃんが一匹でも地面に付いたらゲームが止まるようになりました。だんだんゲームらしさが出てきましたね。

スタート、ゲームオーバーを作る

だいたいゲームが完成してきました。

ココらへんでスタート、ゲーム中、ゲームオーバーを作りましょう。

遷移としてはこんな感じです。

20140404185537.png

スタートの時にタッチするとゲーム中になり、ゲームオーバ時にタッチするとゲーム中になります。

値をリセットする関数を作る

スタート時、ゲームオーバ時にゲーム中に使う、値を初期化する関数を作ります。

void resetGame();を宣言し、.cppで以下を書いてください。

void HelloWorld::resetGame() {
    _dogeTimer = 0;
    _dogeInterval = 0;
    _dogeIndex = 0;
    _running = true;

    CCSprite * doge;
    CCObject * obj;
    CCARRAY_FOREACH(_doges, obj) {
        doge = (CCSprite *) obj;
        doge->setVisible(false);
    }
}
スタート画像、ゲームオーバ画像を貼っつける

スタート画像のスプライト、ゲームオーバ画像のスプライトをinit()で作ります。

_doges->retain();の下に書きましょう。

    // start sprite
    _start = CCSprite::create("start.png");
    _start->setPosition(ccp(_winSize.width * 0.5f, _winSize.height * 0.5f));
    this->addChild(_start, 2);

    // game over sprite
    _gameOver = CCSprite::create("gameover.png");
    _gameOver->setPosition(ccp(_winSize.width * 0.5f, _winSize.height * 0.5f));
    _gameOver->setVisible(false);
    this->addChild(_gameOver, 2);

※ それぞれprivateで宣言してください。

    CCSprite * _start;
    CCSprite * _gameOver;

そしてinit()内の_running = true; をfalseに変えてください。

一緒にinit()内の以下のコードもどうせresetGame()初期化するので消しちゃいましょう

    _dogeTimer = 0;
    _dogeInterval = 0;
    _dogeIndex = 0;

ちなみにここで実行するとスタート画像が出たままタッチしても何も反応しないようになっていると思います。

スタート時、ゲームオーバー時にタッチされたらresetGame()を呼ぶ

ccTouchBeganで今がゲームのどんな状態か判断して処理を分けます。

ccTouchBegan関数の一番上に以下を追加してください。

    if (!_running) {
        if (_start->isVisible() || _gameOver->isVisible()) {
            this->resetGame();
        }
        return true;
    }

そしてresetGame()に以下を追加してください。

    _start->setVisible(false);
    _gameOver->setVisible(false);

コード通り、ゲーム中に入ったらスタートスプライトとゲームオーバスプライトのvisibleをfalseにする処理です。

実行シてみてください!これでスタート画面でタッチするとresetGame()を呼び、ゲーム中へ遷移することが可能になりました。

わんこが墜落したらゲームオーバに移行する

しかしまだゲームオーバへ移行できていませんね。hitGround()でゲームオーバ画面を呼び出しましょう。

void gameOver();を宣言して.cppに以下の関数を書いてください

void HelloWorld::gameOver() {
    _gameOver->setVisible(true);
}

ただゲームオーバ画像のvisibleをtrueにしただけです。これによってタッチすることで再スタートすることができるようになりました。

そしてhitGround()でこの関数を呼び出します。

hitGround()内の_running = false;の直後に以下を書いてください

this->scheduleOnce(schedule_selector(HelloWorld::gameOver), 0.5f);

普通にthis->gameOver();で呼び出してもいいのです。しかしこのゲームはタッチしまくるゲームです。ゲームオーバになったことに気付かずにタッチしつづけていつの間にかゲームが再スタートされたなんてことになってしまいます。

なので0.5秒後にgameOver関数を呼び出しています。

実行してください!かぼすちゃんが地面に落ちて画面が固まってから0.5f秒後にゲームオーバ画像が出てきて再スタート可能になっていることがわかると思います。

それっぽくなってきましたね!! much amaze!!

難易度を上げる

もうほぼゲーム自体は完成と言ってもいいくらいですが、このままだと簡単すぎて一生終わりません。なのでかぼすちゃんを消す度に難易度、つまり落下速度を上げていくことにします。

float _difficulty;を宣言してresetGame()内で

_difficulty = 3.0f;

と書き、

        // わんこが落ちていくアクション
        CCFiniteTimeAction* sequence = CCSequence::create(
                CCMoveTo::create(_difficulty,
                        ccp(target_x, doge->boundingBox().size.height * 0.3f)),
                CCCallFunc::create(this,
                        callfunc_selector(HelloWorld::hitGround)),
                NULL);

と変更してください。(3.0fの部分を_difficultyに)

_difficultyの値を低くすればするほどわんこの落下速度は早くなります。

そこで_difficultyを操作して難易度を上げる関数を作りましょう。

void increaseDifficulty();を宣言して

.cppの方に

void HelloWorld::increaseDifficulty() {
    _difficulty -= 0.03f;
}

と書いてください。

これでincreaseDifficulty()を呼ぶ度に難易度が上がります。

ではこの関数は消す度に呼ぶようにしましょう。

ccTouchBeganの

if (doge->boundingBox().containsPoint(touch->getLocation())) {

ブロックの最後らへんにでも

this->increaseDifficulty();

と書きましょう。

これで実行してみてください。

だんだんと早くなるようになったと思います。難易度の実装ができましたね!

スコアを表示しよう

いくらかぼすちゃんを消したのかをカウントするスコアを作りましょう。

20140405104941.png

いくら消したか保持する変数を作る

とりあえず_scoreという整数を代入する変数を作ってタッチするたびにインクリメントしましょう。

int _score;を宣言し、

resetGame()内で以下を書き足し、

_score = 0;

ccTouchBeganの

this->increaseDifficulty();

の上あたりに_score++;と書きましょう。

これでカウントできるようになりました。

スコアを表示するラベルを作る

では実際に画面上にスコアを表示してみましょう。

ここで問題なのはCCLabelTTFでラベルを生成して更新していくとめちゃめちゃ重いってことです。

なのでCCLabelBMFontを使います。CCLabelBMFontはCCLabelTTFとは違い、ttfファイルではなく、.fntファイルを読み込み、文字が並んだ画像から文字を切り出していくというやり方でラベルを作ります。今回のようなスコアを頻繁に更新するような場合はこっちを使います。

ここでやっかいなのは、その画像とか.fntファイルをどうやって作るかってことですが、Windowsならhttp://www.angelcode.com/products/bmfont/ を使えばすぐにできます。無料です。

ただ今回はこっちで作ってあるので新しく作る必要はありません。

CCLabelBMFont * _scoreLabel;を宣言し、

init()内のゲームオーバスプライトを作ったすぐ下に以下を書いてください

    // scoreLabel
    _scoreLabel = CCLabelBMFont::create("0", "font.fnt");
    _scoreLabel->setAnchorPoint(ccp(0, 1));
    _scoreLabel->setPosition((ccp(20, _winSize.height - 20)));
    this->addChild(_scoreLabel, 2);

実行すると0と書かれたラベルが左上に見えると思います。今度はこれを更新していきましょう。

スコアラベルを操作する

スコアラベルを操作するタイミングは2つです。

  • _scoreのインクリメント時に一緒に更新する
  • resetGame時に0に戻す

実装は簡単です。

ccTouchBegan()の_score++;の下に

_scoreLabel->setString(
                    CCString::createWithFormat("%d", _score)->getCString());

と書きましょう。setStringではcharを引数にとるので変換して与えましょう。

そしてresetGame()の _score = 0;の下に

_scoreLabel->setString("0");

と書きましょう。

実行してみて遊んでください!

いい感じにスコアが更新されるようになりました。

BGMと効果音をつける

ゲームがほぼ完成したので、最後にBGMと効果音を付けましょう!ぐっとゲームっっぽさが上がりますよ!

20140405104900.png

プリロード

AppDelegate.cppの頭のほうに以下と

#include "SimpleAudioEngine.h"

以下を

using namespace CocosDenshion;

追記してください。

そしてapplicationDidFinishLaunching()内の

CCEGLView::sharedOpenGLView()->setDesignResolutionSize(designSize.width,
            designSize.height, kResolutionExactFit);

のすぐ下に以下を書いてください。

SimpleAudioEngine::sharedEngine()->preloadBackgroundMusic(
            CCFileUtils::sharedFileUtils()->fullPathForFilename("bgm.mp3").c_str());
    SimpleAudioEngine::sharedEngine()->preloadEffect(
            CCFileUtils::sharedFileUtils()->fullPathForFilename("dog.mp3").c_str());
    SimpleAudioEngine::sharedEngine()->preloadEffect(
            CCFileUtils::sharedFileUtils()->fullPathForFilename("touch.mp3").c_str());

これでプリロード処理が自動で行われます。とても便利ですね!

あと最後の方にあるバックグラウンドに回った時、戻った時の処理のSimpleAudioEngineの部分を以下のようにコメントアウトしてください。

// This function will be called when the app is inactive. When comes a phone call,it's be invoked too
void AppDelegate::applicationDidEnterBackground() {
    CCDirector::sharedDirector()->stopAnimation();

    // if you use SimpleAudioEngine, it must be pause
    SimpleAudioEngine::sharedEngine()->pauseBackgroundMusic();
}

// this function will be called when the app is active again
void AppDelegate::applicationWillEnterForeground() {
    CCDirector::sharedDirector()->startAnimation();

    // if you use SimpleAudioEngine, it must resume here
    SimpleAudioEngine::sharedEngine()->resumeBackgroundMusic();
}
BGMの再生、ストップ、再開

プリロードした音楽ファイルを実際にならしてみましょう。

ゲーム中にBGMを鳴らすようにしてみます。

HelloWorldScene.cppのinit()の頭の方に先ほどと同じように

#include "SimpleAudioEngine.h"

using namespace CocosDenshion;

を書き、

this->scheduleUpdate();の下に

SimpleAudioEngine::sharedEngine()->playBackgroundMusic("bgm.mp3", true);
SimpleAudioEngine::sharedEngine()->pauseBackgroundMusic();

と書いてください。これでいつでも再生したり、停止したりすることができるようになりました。

では鳴らす、停止するタイミングを考えましょう。

  • resetGame()が呼ばれた時に再生
  • かぼすが地面に落ちた時に停止

よってresetGame()内に以下,

SimpleAudioEngine::sharedEngine()->rewindBackgroundMusic();
SimpleAudioEngine::sharedEngine()->resumeBackgroundMusic();

hitGround()内に以下

SimpleAudioEngine::sharedEngine()->pauseBackgroundMusic();

を書いてください。

実行するとゲーム中だけbgmが流れるようになったと思います。

ちなみに

SimpleAudioEngine::sharedEngine()->rewindBackgroundMusic();

はBGMを始めからに戻す処理です。

効果音を付ける

では最後に効果音を付けてみましょう。付けるタイミングは

  • かぼすちゃんを消すとき
  • かぼすちゃんが落ちた時
  • resetGame時

とします。

よってccTouchBeganの

this->increaseDifficulty();

の直後に

SimpleAudioEngine::sharedEngine()->playEffect("touch.mp3");

と書きましょう。

次にresetGame()内、hitGround()内に

SimpleAudioEngine::sharedEngine()->playEffect("dog.mp3");

と書きましょう。

実行すると効果音がなるようになりました。

完成!!!!

遂にDoge Defenseの完成ですね!

お疲れ様でした。かぼすちゃんかわい。

このC++コード一本でiPhoneアプリにもAndroidアプリにもその他色々にも出来るのですから凄いですね。

間違いがあったら@ottatiyarou2までお願いします。(その他文句もあれば...

おわりに

こんな感じで誰でも長くて1ヶ月もあればゲームアプリ作れますね!っていう記事でした。

今ではこんな簡単にゲームアプリが作れるので、開いた時間をゲームアプリ開発に使ったら面白いんじゃないかと思います。

このレシピを書いたシェフ

おっ立ち野郎

@ottatiyarou

コードレシピの管理人です。
「for文とか関数とかの基礎はやったけど、実際アプリとかどうやって作るのか分からない」を解決するようなサービスにしていきたいです。


このシェフが書いたレシピ