ゲーム開発日記

楽しいよね。

【リリース】Light Music Game

今年頭くらいからぼちぼち作っていたLight Music Gameがリリースされました。

避けゲーと音ゲーが融合したゲームで、
同じ色に当て続けると音がなり音楽のように聞こえます。

避けゲーの厳しい死にゲー要素と音ゲーの音楽を使った没入感がウリです。
よければDLお願いします。

www.youtube.com

Light Music - Google Play の Android アプリ
Light Music Gameを App Store で

[cocos2dx 3.3rc]Android開発環境構築 ~Mac~

新しくMacを買い替えて、cocos2dxの開発環境をセットアップしなおしたので、メモ

前提
・cocos2dxは3.3rc
mac book pro

インストール先を作成

mkdir ~/opt

ダウンロード

Android NDK

Android NDK | Android Developers
上記をダウンロードして、~/optに展開
シンボリックリンクを張りました。

$ cd ~/opt
$ ln -s android-ndk-r10c/ android-ndk

Android SDK

Android SDK | Android Developers
上記をダウンロードして、~/optに展開

ant

僕はHomebrewを使ってインストールしました。

$ brew install ant
$ ant -version
Apache Ant(TM) version 1.9.4 compiled on April 29 2014

~/optはこんな感じになりました(NDK -> android-ndk、SDK -> adt-bundle)

$ cd ~/opt
$ ls -l
drwxrwxr-x@  4 staff   136  7  2 12:18 adt-bundle
lrwxr-xr-x   1 staff    17 11  3 14:39 android-ndk -> android-ndk-r10c/
drwxr-xr-x  26 staff   884 11  3 14:38 android-ndk-r10c

環境設定

cocos2dxのディレクトリに入って、./setup.pyを実行し、Pathを設定する

.bash_profileの設定は下記のようになりました。

# Add environment variable COCOS_CONSOLE_ROOT for cocos2d-x
export COCOS_CONSOLE_ROOT=~/Desktop/cocos2d-x-3.3rc0/tools/cocos2d-console/bin
export PATH=$COCOS_CONSOLE_ROOT:$PATH

# Add environment variable NDK_ROOT for cocos2d-x
export NDK_ROOT=~/opt/android-ndk
export PATH=$NDK_ROOT:$PATH

# Add environment variable ANDROID_SDK_ROOT for cocos2d-x
export ANDROID_SDK_ROOT=~/opt/adt-bundle/sdk
export PATH=$ANDROID_SDK_ROOT:$PATH
export PATH=$ANDROID_SDK_ROOT/tools:$ANDROID_SDK_ROOT/platform-tools:$PATH

# Add environment variable ANT_ROOT for cocos2d-x
export ANT_ROOT=/usr/local/Cellar/ant/1.9.4/libexec/bin
export PATH=$ANT_ROOT:$PATH

プロジェクト作成&実行

先ほどの設定を有効にして、プロジェクト作成

$ source ~/.bash_profile
$ cocos new HelloWorld -p com.akichim.HelloWorld -l cpp -d ~/

エミュレータの設定してないと、実行時にエラーになります

$ cd ~/HellowWorld
$ cocos run -p android
...
...
building apk
Android platform not specified, searching a default one...
Can't find right android-platform for project : "/Users/nownabe/projects/HelloCocos/proj.android". The android-platform should be equal/larger than 10

エミュレータをインストールするためにandroidを実行。

$ android

今回はandroid5を丸っとインストールしました。(ほかのosバージョンでも良いです)

確認。

$ android list target
Available Android targets:
----------
----------
id: 1 or "android-21"
     Name: Android 5.0
     Type: Platform
     API level: 21
     Revision: 1
     Skins: HVGA, QVGA, WQVGA400, WQVGA432, WSVGA, WVGA800 (default), WVGA854, WXGA720, WXGA800, WXGA800-7in
 Tag/ABIs : android-tv/armeabi-v7a, android-tv/x86, default/armeabi-v7a, default/x86, default/x86_64

エミュレータ作成。最初のコマンドでyesを入力して、gpuをyesにしないとunfortunately has stoppedでエラーになります。

$ android create avd -n cocos -t 1 -b default/armeabi-v7a

Do you wish to create a custom hardware profile [no]yes

....
DPad support: Whether the device has DPad keys
hw.dPad [yes]:

GPS support: Whether there is a GPS in the device.
hw.gps [yes]:

GPU emulation: Enable/Disable emulated OpenGLES GPU
hw.gpu.enabled [no]:yes

GSM modem support: Whether there is a GSM modem in the device.
hw.gsmModem [yes]:

Keyboard support: Whether the device has a QWERTY keyboard.
hw.keyboard [no]:
....

エミュレータ起動。

$ emulator -avd cocos

サンプルプロジェクト実行。

$ cd ~/HellowWorld
$ cocos run -p android

以上で実行できました!

mysqlのロックまとめ

サマリー

・レコードがない状態でロックをとるとギャップロックが発生し、キーにしたもののレコードの間で共有ロックをとる
  • >レコードが存在しない可能性がある場合は、ロックをとらない
・ロックは値でとるので、INDEXで複数同じ値が入る時は注意 ->ロックはPRIMARY KEYで基本とる

・分離レベル

mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+

・テーブル構造

CREATE TABLE `user_ticket` (
  `id`                        bigint(20)  UNSIGNED NOT NULL, -- シーケンシャルid
  `user_id`                int(10)       UNSIGNED NOT NULL, -- チケット送りもと
  `to_user_id`           int(10)       UNSIGNED NOT NULL, -- チケット送り先
  `is_received`          tinyint(3)   UNSIGNED NOT NULL, -- 受け取ったかどうか
  PRIMARY KEY (`id`),
  KEY `i1` (`user_id`, `to_user_id`, `is_received`),
  KEY `i2` (`to_user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=sjis;

・レコード

for i in {1..100}; do mysql -e "INSERT INTO user_ticket VALUES(${i}, ${i}, 101 - ${i}, 0);"; done;

for i in {201..300}; do mysql -e "INSERT INTO user_ticket VALUES(${i}, ${i}, 301 - ${i}, 0);"; done;
mysql> select * from user_ticket limit 10;
+----+---------+------------+-------------+
| id | user_id | to_user_id | is_received |
+----+---------+------------+-------------+
|  1 |       1 |        100 |           0 |
|  2 |       2 |         99 |           0 |
|  3 |       3 |         98 |           0 |
|  4 |       4 |         97 |           0 |
|  5 |       5 |         96 |           0 |
|  6 |       6 |         95 |           0 |
|  7 |       7 |         94 |           0 |
|  8 |       8 |         93 |           0 |
|  9 |       9 |         92 |           0 |
| 10 |      10 |         91 |           0 |
+----+---------+------------+-------------+
10 rows in set (0.00 sec)

・PRIMARY KEY のロック検証
trxA

mysql> begin;

mysql> select * from user_ticket where id = 1 for update;

trxB

mysql> begin;

# 同じ値ならロック競合
mysql> select * from user_ticket where id = 1 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

# 違うなら競合しない
mysql> select * from user_ticket where id = 2 for update;
+----+---------+------------+-------------+
| id | user_id | to_user_id | is_received |
+----+---------+------------+-------------+
|  2 |       2 |         99 |           0 |
+----+---------+------------+-------------+

・PRIMARY KEY のギャップロック

trxA

mysql> begin;

mysql> select * from user_ticket where id = 105 for update;
Empty set

trxB

mysql> begin;

mysql> select * from user_ticket where id = 100 for update;
+-----+---------+------------+-------------+
| id  | user_id | to_user_id | is_received |
+-----+---------+------------+-------------+
| 100 |     100 |          1 |           0 |
+-----+---------+------------+-------------+

mysql> select * from user_ticket where id = 105 for update;
Empty set

# id 101 - 200ロック
mysql> INSERT INTO user_ticket VALUES(101, 101, 1, 0);
Lock wait timeout exceeded; try restarting transaction

# 間にレコードがあるので、301 以上にロックはかかってない
mysql> INSERT INTO user_ticket VALUES(301, 101, 1, 0);
Query OK, 1 row affected

indexの場合

trxA

mysql> begin;

mysql> select * from user_ticket where user_id = 1 and to_user_id = 100 and is_received = 0 for update;

trxB

mysql> begin;

# user_id同じだとロックウェイト
mysql> select * from user_ticket where user_id = 1 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

# to_user_idだけで同じ値でもロックウェイト
mysql> select * from user_ticket where to_user_id = 100 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

# 先頭のuser_idが違えば、残り同じでもロック競合しない
mysql> select * from user_ticket where user_id = 2 and to_user_id = 100 and is_received = 0 for update;
Empty set

# to_user_id違えば、ロック競合しない
mysql> select * from user_ticket where to_user_id = 1 for update;
+-----+---------+------------+-------------+
| id  | user_id | to_user_id | is_received |
+-----+---------+------------+-------------+
| 100 |     100 |          1 |           0 |
+-----+---------+------------+-------------+

# user_idが同じだとロックウェイト
mysql>  INSERT INTO user_ticket VALUES(500, 1, 500, 1);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

# 先頭のuser_idが違えば、残り同じでも競合しない
mysql>  INSERT INTO user_ticket VALUES(501, 10, 100, 0);
Query OK, 1 row affected

trxA

mysql> begin;

mysql> select * from user_ticket where user_id = 200 for update;

trxB

mysql> begin;

mysql>  INSERT INTO user_ticket VALUES(102, 10, 100, 0);

# user_idの100 - 200の間でギャップロックしてる
mysql> INSERT INTO user_ticket VALUES(103, 150, 100, 0);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

# 間でなければ入る
mysql> INSERT INTO user_ticket VALUES(102, 10, 100, 0);
Query OK, 1 row affected

パズロラ風操作でぷよぷよ風に消す

作ってみました。

良くない書き方などあれば、教えてもらえると助かります。
ver3.0で動きます。
https://github.com/akichim21/cocos2dx-ver3-public/tree/master/PuzzleMock

Scene遷移の順番について(onEnter,onExit, onEnterTransitionDidFinish)

FirstSceneから初めて、クリックしたらSecondSceneにかわり、またクリックするとSecondSceneにかわる時のメソッドの順番を調べました。

FirstScene.h

#include "cocos2d.h"


class FirstScene : public cocos2d::CCLayer
{
public:
    FirstScene();
    ~FirstScene();
    
    static cocos2d::CCScene* scene();
    
    virtual bool init();
    
    virtual bool ccTouchBegan(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);
    
    virtual void onExit();
    virtual void onEnter();
    virtual void onEnterTransitionDidFinish();
    
    CREATE_FUNC(FirstScene);
};

FirstScene.cpp

#include "FirstScene.h"
#include "SecondScene.h"

using namespace cocos2d;

CCScene* FirstScene::scene()
{
	CCLOG("===========================================");
	CCLOG("%s: FirstScene", __FUNCTION__);
    
    CCScene* scene = CCScene::create();
    FirstScene* layer = FirstScene::create();
    
    scene->addChild(layer);
    return scene;
}

bool FirstScene::init()
{
    if (CCLayer::init())
    {
        CCLOG("%s: FirstScene", __FUNCTION__);
        
        CCLabelTTF* label = CCLabelTTF::create("First Scene", "Marker Felt", 64);
        label->setColor(ccGREEN);
        CCSize size = CCDirector::sharedDirector()->getWinSize();
        label->setPosition(CCPointMake(size.width / 2, size.height / 2));
        this->addChild(label);
        
        this->setTouchEnabled(true);
        
        return true;
    }
    return false;
}

FirstScene::FirstScene()
{
    CCLOG("%s: FirstScene", __FUNCTION__);
}

FirstScene::~FirstScene()
{
    CCLOG("%s: FirstScene", __FUNCTION__);
}

bool FirstScene::ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent)
{
    CCLOG("%s:FirstScene", __FUNCTION__);
    CCDirector::sharedDirector()->replaceScene(SecondScene::scene());
    
    return true;
}

void FirstScene::onExit()
{
    CCLOG("%s:FirstScene", __FUNCTION__);
    
    CCLayer::onExit();
}

void FirstScene::onEnter()
{
    CCLOG("%s: FirstScene", __FUNCTION__);
    
    CCLayer::onEnter();
    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, 0, true);
}

void FirstScene::onEnterTransitionDidFinish()
{
    CCLOG("%s: FirstScene", __FUNCTION__);
    
    CCLayer::onEnterTransitionDidFinish();
}

SecondScene.h

#include "cocos2d.h"


class SecondScene : public cocos2d::CCLayer
{
public:
    SecondScene();
    ~SecondScene();
    
    static cocos2d::CCScene* scene();
    
    bool init();
    
    virtual bool ccTouchBegan(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);
    
    virtual void onExit();
    virtual void onEnter();
    virtual void onEnterTransitionDidFinish();
    
    CREATE_FUNC(SecondScene);
};
#include "FirstScene.h"
#include "SecondScene.h"


using namespace cocos2d;

CCScene* SecondScene::scene()
{
	CCLOG("===========================================");
	CCLOG("%s: SecondScene", __FUNCTION__);
    
    CCScene* scene = CCScene::create();
    CCLayer* layer = SecondScene::create();
    
    scene->addChild(layer);
    return scene;
}

bool SecondScene::init()
{
    if (CCLayer::init())
    {
        CCLOG("%s: SecondScene", __FUNCTION__);
        
        CCLabelTTF* label = CCLabelTTF::create("Second Scene", "Marker Felt", 64);
        label->setColor(ccGREEN);
        CCSize size = CCDirector::sharedDirector()->getWinSize();
        label->setPosition(CCPointMake(size.width / 2, size.height / 2));
        this->addChild(label);
        
        this->setTouchEnabled(true);
        
        return true;
    }
    return false;
}

SecondScene::SecondScene()
{
    CCLOG("%s: SecondScene", __FUNCTION__);
}


SecondScene::~SecondScene()
{
    CCLOG("%s: SecondScene", __FUNCTION__);
}

bool SecondScene::ccTouchBegan(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent)
{
    CCDirector::sharedDirector()->replaceScene(FirstScene::scene());
    
    return true;
}

void SecondScene::onExit()
{
    CCLOG("%s: SecondScene", __FUNCTION__);
    
    CCLayer::onExit();
}

void SecondScene::onEnter()
{
    CCLOG("%s: SecondScene", __FUNCTION__);
    
    CCLayer::onEnter();
    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, 0, true);
}

void SecondScene::onEnterTransitionDidFinish()
{
    CCLOG("%s: SecondScene", __FUNCTION__);
    
    CCLayer::onEnterTransitionDidFinish();
}


ログ:
Cocos2d: ===========================================
Cocos2d: scene: FirstScene
Cocos2d: FirstScene: FirstScene
Cocos2d: init: FirstScene
Cocos2d: onEnter: FirstScene
Cocos2d: onEnterTransitionDidFinish: FirstScene
Cocos2d: ccTouchBegan:FirstScene
Cocos2d: ===========================================
Cocos2d: scene: SecondScene
Cocos2d: SecondScene: SecondScene
Cocos2d: init: SecondScene
Cocos2d: onExit:FirstScene
Cocos2d: ~FirstScene: FirstScene
Cocos2d: onEnter: SecondScene
Cocos2d: onEnterTransitionDidFinish: SecondScene


SecondScene::initの後にFirstScene::onExitとデストラクタが呼び出されており、
シーンの狭間でFirstSceneとSecondSceneの両方がメモリにのっかてしまう問題って解消したんだろうか?時間あればもう少し実験する。

inspired by

cocos2dで作る iPhone&iPadゲームプログラミング

cocos2dで作る iPhone&iPadゲームプログラミング

第5章-1

cocos2d-xを縦持ちの画面にする

cocos2d-xはデフォルトで横持ちだが、縦の画面にしたい時の設定。

 

ios/RootViewController.mm

 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {

       -returnUIInterfaceOrientationIsLandscape( interfaceOrientation );

    +returnUIInterfaceOrientationIsPortrait( interfaceOrientation );

}

 実機はこれだけでいいらしいが、シミュレーターは変わらないので以下も設定

ios/Info.plist

Supported interface orientations

-Item 0 => Landscape (left home button)

-Item 1 => Landscape (right home button)

+Item 0 => Portrait (bottom home button)

+Item 1 => Portrait (top home button) 

Landscape => Portaitに 変えればおけ!