何千番煎じかわかりませんが、作りました。
作ったのは結構前だったんですが、iOSの申請が通るまでに時間を要して、ようやく通ったので公開です。
画像はお蔵入りになったタイトルです。
作り方は続きから。
Cocos2d-x 3.0 beta2
今回はまだベータバージョンのCocos2d-x 3.0を利用しました。
先に言っておきますが、3.0はまだ鬼門です。
毎日仕様が変わる上、バグ修正なども随時行われているため、問題が起こった時にめちゃくちゃ困ります。
公式フォーラムやgitのリポジトリをチェックすることで、解決できる問題もあるので、若干上級者向けと言えそうです。
また、2.xバージョンを利用していた人からすれば、3.0はかなり仕様が変わっているため、前に行っていたやり方を再度調べなおす必要があるという二度手間さがあります。
正式バージョンが公開されてから3.0を触るのが吉ですね。
この3.0を使った理由は、新しい環境に慣れておきたいと思ったのと、2Dエンジンが外部ライブラリではなく、コアライブラリとして埋め込まれ使えるようになったからです。
今まではBox2DかChipmunkという2種類の2D物理エンジンが予め組み込まれてはいたのですが、外部ライブラリとして呼び出して利用する、という使い方でした。
3.0からはこれがCocosのコアライブラリに機能として組み込まれています。
今まではサンプルでもBox2Dが良くつかわれていたのですが、この組み込み物理エンジンにはChipmunkが採用されているようです。
Cocos2d-x3.0で追加された仕組みを見ていると、Unityを意識している感じがありますね。
自機プレイヤーの動きを作る
できるだけ元のゲームの動きを忠実にしてみたいとは思っていましたが、モノホンの方はどうみても物理エンジンを使っているような動きではありません。
おそらくサイン波による上下運動と重力加速度の計算を毎フレームに行って自機位置を決めているものと思われます。
かなり安定した動きなので、という勝手な推測ですが。
ですが、今回は似たような動きを物理エンジンを利用して再現してみました。
仕組みは単純です。
自機はスタート位置から、左右には動きません。
赤矢印の通りに、上下にのみ動きます。
後は背景と障害物を、右から左へ流していけば、自機は右に向かって進んでいるように見えます。
次に重力空間を作るために、シーンの初期化処理を書き換えます。
Scene* GameMain::createScene()
{
//auto scene = Scene::create();
auto scene = Scene::createWithPhysics();
Vect gravity;
gravity.setPoint(0, -50);
scene->getPhysicsWorld()->setGravity(gravity);
//scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
scene->getPhysicsWorld()->setSpeed(6.0f);
// ‘layer’ is an autorelease object
auto layer = GameMain::create();
// add layer as a child to scene
scene->addChild(layer);
// return the scene
return scene;
}
従来であれば、Box2Dやchipmunkを使って、重力空間を作成する必要がありましたが、Cocos2d-x3.0では、初期化時に createWithPhysics()を呼んでやるだけで、重力空間を作成することができます。
その後初期パラメーターとして、重力方向等を設定します。
auto player = Sprite::create(“justin_anime01.png”);
auto pb = PhysicsBody::createBox(Size(player->getContentSize().width-30, player->getContentSize().height-10));
pb->setEnable(false);
pb->setMass(1.0f);
pb->setVelocity(Vect(0,1));
pb->setVelocityLimit(200.0);
pb->setRotationEnable(false);
pb->setAngularVelocityLimit(150.0f);
player->setPosition(Point(228, 708));
player->setPhysicsBody(pb);
this->addChild(player);
まずはSpriteでもなんでも良いのでNodeを作成します。
次に重力に影響を受けるボディを作成します。
ボディは1Nodeに対して、1つのみ設定できるような感じですね。
PhysicsBodyを今回はボックスタイプで作成しました。
本当ならば、ツールを使ってキャラクタの縁に合わせたボディを作成してあげるのが良いですが、めんどうだしこれでいいやと思いました。
その代わり、キャラクタの凹凸分、サイズを縮小してボディを作成しています。
ちなみに、ボディのサイズはあくまで、重力の影響を受けるのと当たり判定を受持ますので、Nodeの大きさとは同じでなくても構いません。
ボディのsetEnableにより、重力影響のOn/Offを切り替えられるので、初期化時はOffにしています。
ゲームを開始しようと、タップをしたときにこれをOnに切り替えると、プレイヤーが重力の影響を受けるようになります。
試しに、このsetEnableをtrueにして、動かしてみると、開始後すぐにプレイヤーは下へ落下していくことでしょう。
タップの検知
タップをしたときの検知方法が3.0から変わっています。
auto taplistener = EventListenerTouchAllAtOnce::create();
taplistener->onTouchesBegan = CC_CALLBACK_2(GameMain::onTouchBegan, this);
this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(taplistener, this);
EventListenerというものを利用して行うことになりました。
使い方は見ればなんとなくわかるかもしれません。
また、3.0ではC++11が利用できるので、ラムダ式というものが利用でき、Javaでよくある匿名メソッドが利用できます。
Objective-CだとBLOCK文みたいなものですね。
taplistener->onTouchesBegan = [this](const std::vector<cocos2d::Touch *> &touches, cocos2d::Event *event){
};
ラムダ式で書くとこんな感じになります。
これでタップをした時の処理が書けますね。
自機のジャンプ
タップを検知したら、上方向へ衝撃(Impluse)を加えてあげましょう。
player->getPhysicsBody()->applyImpulse(Vect(0, JUMP_UP), Point(0, player->getContentSize().height*-1));
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect(“se_tap.mp3”);
JUMP_UPはここでは 180.0f を定義してあります。
同時に音を鳴らしています。
衝突の検知
後は、自機と同じように、障害物を作成してあげて、障害物と自機が衝突したときになんらかの処理をすれば良いように思います。
衝突の検知もEventListenerで登録します。
auto p_listener = EventListenerPhysicsContact::create();
p_listener->onContactBegin = CC_CALLBACK_2(GameMain::onContactBegin, this);
this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(p_listener, this);
これで、衝突が最初に発生したタイミングで、イベントを検知できます。
衝突判定を色付けでわかりやすく出すとこんな感じです。
この障害物が右から左へ流れてきます。
自機と、障害物が衝突すると、先ほどのイベントが呼ばれます。
障害物と障害物の間に見えない障害物を設置して、これと接触したら
カウントを増やすようにしています。
後は障害物にそれぞれ、異なるTagを設定しておいて、設定したイベントで
タグ判定をすれば、どのオブジェクトと衝突したのかはすぐにわかります。
その際に、OnContactBeginイベントの戻り値はbool型なので、
falseを返してあげれば、衝突を回避することも可能です。
ポイントをカウントする箇所では、falseを返すことで、物体に衝突せず
すり抜ける効果を出しています。
if ( ( contact.getShapeA()->getBody()->getTag() == 20000 || contact.getShapeB()->getBody()->getTag() == 20000 ) ) {
now_point++;
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect(“se_pointget.mp3”);
this->setDisplay();
return false;
}
onContactBeginの引数にある contactから、衝突したShape(面)が取得できるので、そこから元のオブジェクトを広い、タグで検知します。
setDisplay() は画面のスコアを更新する処理です。
障害物にぶつかった時のエフェクト
これもモノホンを参考にやってみました。
単純に、
1.真っ白な画像を用意して、一瞬でフェードイン・フェードアウトを行う
2.画面全体をゆらゆら揺らす
ということで実現してみました。
Cocos2dはこの手のアクションはお手の物なので、Rootノードを、MoveToアクションを使って、上下左右に揺らすだけです。
こんな感じですぐに作れてしまいます。
ちょっと細かい説明省いていますが、需要があればまた書きます。
実質ほぼ3.0の仕様を調べている時間だったので、メインプログラム部分は1日で終わると思います。
コメント
初めまして。
突然のコメント失礼致します。
以前2chのまとめブログを見ていた時に、管理人様の投稿がまとめられていたのを拝見し、こちらのブログに飛んできました。
iPhoneアプリ作成に興味がありコンタクトを取らせて頂きました。
いくつかお聞きしたいことがあるのですがよろしいでしょうか?
ご返信お待ちしております。
どうぞどうぞ。よければTwitterとかで連絡してみてください。