触ってみたcocos2d-x！！

Fusic アドベントカレンダー3日目です。

記念すべき 2日目は、@gorogoroyasuの力作でした。
ここだけの話ですが、@gorogoroyasuはハードルを下げると言いつつ
上げてしまって、少し書き辛いですが、
頑張ります。

ということで、Fusic アドベントカレンダー3日目です。

## まずもってお前だれ？
・Fusicの2年目のチョです。
・出身：韓国。
・しかし、日本に馴染みすぎと言われます。
・会社のみんなからは「お前日本人やろ？」と言われています。
・だから？結構日本語話せるので、他のところで会ったら、楽しく話ししたいです！怖がらないで。。。😢

## テーマ
今回のテーマは「やっていなかったことをやる」、「とりあえずハードルをあげる」というテーマで書きます。

## 普段なにやっているの？
Cakephp3やAWSなど色んなものを利用しWeb開発をやっています。
うちは言語と環境の制約がなく自由に仕事できて、
楽しくやってます！
なのであんまり触ることないゲームをやってみようと思って、
cocos2d-xを選択！

## どうやってハードルをあげるつもり？
それはですね。
入社してからよく言われた話ですが、
どこかで発表するとき@debilityの話題を話すと
いいよと言われてました。
真面目といえばチョと呼ばれるような人間なので。
仕方なく@debilityの出現するゲームを作りました！

## 対象
cocos2d-xをとりあえず使ってみたい人
macを使っている人
いずれmacを使う人


## どんなゲーム？
さて、ここから本番です。

最後の目標として、
下記で表示されているゲームを作ります。

![image](https://qiita-image-store.s3.amazonaws.com/0/74128/86cab389-280f-34d1-5085-6bd65c025d4c.png)

## そもそもcocos2d-xとは？
・open sorce cocos2dエンジンのbranchの一つ。
・色んなプラットホームを提供する2dゲームの一つ。

## 提供されているプラットホームは何があるの？
・Mac OSX、IOS
・Android
・Windows、Windows Phone
・Linux Ubuntu
色々できそう。。。

## 長所は？
・Exampleが多くて、誰でも簡単にできる
・容量が少ないゲーム開発ができる！

## cocos2Dの構成
▼背景
・一つ一つのSceneがあって、Sceneを重ねて作る感じ。
▼Layer
・Sceneに含まるObject
・ユーザーのイベントを受けられる
▼Sprite
・実際に動くキャラクターなど
▼Action
・cocos2Dノード属性を時間によって変更するClass
・Spriteにアニメーションの設定ができる！

奥が深い。。。

## ここまで理解したら、実際にコードを書いて見ようじゃないか！
### install
・http://www.cocos2d-x.org/download このサイトから Cocos2d-xの最新版をDownloadしよう！
・DownloadするとzipファイルがDownloadされるので、unzipしてください。
・で、unzipしたら一個のフォルダーができています。そこに入ってください。
・そのフォルダーには `setup.py` があるはずです。そのファイルをrunしましょう！因みに実行コマンドは `python setup.py` です。
・そうすると英語で色々聞かれると思いますが、全部Enterを押して結構です。
・で、最後に `source ~/.bash_profile` を叩いてください。
・ここまでがinstallです。あら素敵。

### 次はProjectを作りましょう！
・コマンドは `cocos new -l cpp -d ~/Cocos2d-x-Tutorial SimpleGame` です。
・上記のコマンドを叩くことで `SimpleGame` というprojectが出来上がります！あら簡単。

### で、ここから一番楽しいコーディング！
* 今回はXcodeを使って進めます。

#### 作ったProjectでとりあえず実装してみる
・Xcodeで先作ったProjectを開いてみましょう。
ある位置は `~/Cocos2d-x-Tutorial/SimpleGame/proj.ios_mac/` の中に `SimpleGame.xcodeproj` があります。それを入れてください。
・開くとこんな画面が出てきます。そこで実行するデバイスの設定を切り替えましょう。
・そのようにして、実行ボタンを押すと下記のような画面が開けます。
・これで開発環境は準備できました！

#### キャラクターを置いてみる！
・先ほど説明した `Sprite` を利用しキャラクターを画面に配置してみます。
・`Sprite` を使うためには `cocos2d` namespaceをusingする必要があります。
・`HelloWorldScene.h` の一番上に `using namespace cocos2d;` と書いてください。
・そして、Helloworld classの中に

```
using namespace cocos2d;

#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__

#include "cocos2d.h"

class HelloWorld : public cocos2d::Layer
{
public:
    static cocos2d::Scene* createScene();

    virtual bool init();
    
    // a selector callback
    void menuCloseCallback(cocos2d::Ref* pSender);
    
    // implement the "static create()" method manually
    CREATE_FUNC(HelloWorld);
    void addMonster(float dt);
    bool onTouchBegan(Touch *touch, Event *unused_event);
    bool onContactBegan(PhysicsContact &contact);
private: //ここです。
    Sprite* _player;
};

#endif // __HELLOWORLD_SCENE_H__

```

を宣言します。
・宣言した_player変数を設定して配置してみます。
・表示したい画像をSimpleGame/Resourcesにdrag and dropしてください。
  ・用意する画像は「print.png」、「monster.png」、「projectile.png」です。
・そうすると下記のような画面が出てきて、下記の画面のように設定してください。
![image](https://qiita-image-store.s3.amazonaws.com/0/74128/1e73c36d-7b6e-d99b-3c58-5d61ca169ea7.png)

・HelloWorldScene.cppの中の `HelloWorld::init` の中に下記のコードを書いてください。

```
    // 1
    if ( !Layer::init() ) {
        return false;
    }
    // 2
    auto origin = Director::getInstance()->getVisibleOrigin();
    auto winSize = Director::getInstance()->getVisibleSize();
    // 3
    auto background = DrawNode::create();
    background->drawSolidRect(origin, winSize, Color4F(0.6,0.6,0.6,1.0));
    this->addChild(background);
    // 4
    _player = Sprite::create("prin.png");
    _player->setPosition(Vec2(winSize.width * 0.1, winSize.height * 0.5));
    this->addChild(_player);
    
    srand((unsigned int)time(nullptr));
    this->schedule(schedule_selector(HelloWorld::addMonster), 1.5);
    
    auto eventListener = EventListenerTouchOneByOne::create();
    eventListener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this);
    this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(eventListener, _player);
    
    auto contactListener = EventListenerPhysicsContact::create();
    contactListener->onContactBegin = CC_CALLBACK_1(HelloWorld::onContactBegan, this);
    this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(contactListener, this);

    return true;
```

・このように書くことでキャラクターの配置ができます！
・これで、自分が尊敬している@debility先輩が出現しました！見てください。あの姿格好いいっしょ！
・で、感想はさておき。

#### モンスターが来るように書いてみましょう。
・`PhysicsCategory` を `HelloWorldScene.cpp` の一番上に書いておきます。

```
enum class PhysicsCategory {
    None = 0,
    Monster = (1 << 0),    // 1
    Projectile = (1 << 1), // 2
    All = PhysicsCategory::Monster | PhysicsCategory::Projectile // 3
};
```

・先のように `void addMonster(float dt);` を `HelloWorldScene.h` に宣言しておきます。
・そして、 `HelloWorldScene.cpp` で先宣言したaddMonsterの中身を作っていきます。中身は下記のように

```
    auto monster = Sprite::create("monster.png");
    
    // 1
    auto monsterContentSize = monster->getContentSize();
    auto selfContentSize = this->getContentSize();
    int minY = monsterContentSize.height/2;
    int maxY = selfContentSize.height - monsterContentSize.height/2;
    int rangeY = maxY - minY;
    int randomY = (rand() % rangeY) + minY;
    
    monster->setPosition(Vec2(selfContentSize.width + monsterContentSize.width/2, randomY));
    this->addChild(monster);
    
    // 2
    int minDuration = 2.0;
    int maxDuration = 4.0;
    int rangeDuration = maxDuration - minDuration;
    int randomDuration = (rand() % rangeDuration) + minDuration;
    
    // 3
    auto actionMove = MoveTo::create(randomDuration, Vec2(-monsterContentSize.width/2, randomY));
    auto actionRemove = RemoveSelf::create();
    monster->runAction(Sequence::create(actionMove,actionRemove, nullptr));
    
    // 1
    auto monsterSize = monster->getContentSize();
    auto physicsBody = PhysicsBody::createBox(Size(monsterSize.width , monsterSize.height),
                                              PhysicsMaterial(0.1f, 1.0f, 0.0f));
    // 2
    physicsBody->setDynamic(true);
    // 3
    physicsBody->setCategoryBitmask((int)PhysicsCategory::Monster);
    physicsBody->setCollisionBitmask((int)PhysicsCategory::None);
    physicsBody->setContactTestBitmask((int)PhysicsCategory::Projectile);
    
    monster->setPhysicsBody(physicsBody);
```

*すみません。今回はコードの説明はあんまりしません。ご了承ください。

#### 次には押した時に攻撃できるようにしましょう。
・HelloWorldScene.hに宣言されている `onTouchBegan` を実装します。
コードの下記のとおり。

```
bool HelloWorld::onTouchBegan(Touch *touch, Event *unused_event) {
    // 1  - Just an example for how to get the  _player object
    //auto node = unused_event->getCurrentTarget();
    
    // 2
    Vec2 touchLocation = touch->getLocation();
    Vec2 offset = touchLocation - _player->getPosition();
    
    // 3
    if (offset.x < 0) {
        return true;
    }
    
    // 4
    auto projectile = Sprite::create("projectile.png");
    projectile->setPosition(_player->getPosition());
    this->addChild(projectile);
    
    // 5
    offset.normalize();
    auto shootAmount = offset * 1000;
    
    // 6
    auto realDest = shootAmount + projectile->getPosition();
    
    // 7
    auto actionMove = MoveTo::create(2.0f, realDest);
    auto actionRemove = RemoveSelf::create();
    projectile->runAction(Sequence::create(actionMove,actionRemove, nullptr));
    
    auto projectileSize = projectile->getContentSize();
    auto physicsBody = PhysicsBody::createCircle(projectileSize.width/2 );
    physicsBody->setDynamic(true);
    physicsBody->setCategoryBitmask((int)PhysicsCategory::Projectile);
    physicsBody->setCollisionBitmask((int)PhysicsCategory::None);
    physicsBody->setContactTestBitmask((int)PhysicsCategory::Monster);
    projectile->setPhysicsBody(physicsBody);
    
    return true;
}
```

#### 次はあたったらモンスターが画面からなくなるように書いてみましょう。
・HelloWorld::createSceneを下記のように書いて、

```
Scene* HelloWorld::createScene()
{
    auto scene = Scene::createWithPhysics();
    scene->getPhysicsWorld()->setGravity(Vec2(0,0));
    scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);

    // 'layer' is an autorelease object
    auto layer = HelloWorld::create();

    // add layer as a child to scene
    scene->addChild(layer);

    // return the scene
    return scene;
}
```

・onContactBeganの中身を書くことで実現されます。

```
bool HelloWorld::onContactBegan(PhysicsContact &contact) {
  auto nodeA = contact.getShapeA()->getBody()->getNode();
  auto nodeB = contact.getShapeB()->getBody()->getNode();
 
  nodeA->removeFromParent();
  nodeB->removeFromParent();
  return true;
}
```

## 感想
・やっぱ、新しいことをするのは楽しい。
・これからもどんどんチャレンジしていきたい。