[C++] Cocos2D-xでゲームを作る前の準備で書いた通り、チーム内のメンバーでゲームを作り合おうという話から、Cocos2d-xに手を出したのでそのメモです。
今回は、まったくの未経験からCocos2d-xでゲームを作るまでのメモを書いています。
(ゲームはAndroidはリリース、iOSは申請中です。ゲームのリンク先は一番下にあります)
ログ出力
ログ出力はlog
関数を利用します。非常にシンプル。
log("hoge");
数値をstringに変換する
スコア表示など、intやfloatなどをstringに変換したいケースはたくさんあると思います。
Cocos2d-xではStringUtils
関数群が実装されているので、これを利用すると便利です。
// `toString`を使ってスコアをラベル表示する
auto label = cocos2d::Label::createWithSystemFont(cocos2d::StringUtils::toString(this->score), "arial", 24.0f);
this->addChild(label);
ランダムな値を得る
int randomNum = arc4random() % 10;
こうすることで0〜9のランダムな値を取得できます。
マイナスを含んだランダムな範囲を得る
単に上記の応用ですが、範囲としたい値(例えば-10〜10)の最大値から、最大値の倍の値までのランダムな値を減算することで得られます。式にすると以下のようになります。
int randNum = 10 - (arc4random() % 21); // => -10〜10
スプライト(画面になにか描画する)
スプライトはCocos2d-xの世界での標準的なひとつの描画単位です。
(2D系は概ねスプライトと呼ぶようです)
ラベルを作る
ラベルはcocos2d::Label
クラスを利用します。
auto label = cocos2d::Label::createWithSystemFont("hoge", "arial", 35.0f);// 文字とフォントサイズを渡して生成
label->setPosition(10, 10); // 位置を設定
this->addChild(label); // 生成したら`cocos2d::Layer`に追加する
画像からスプライトを生成
画像からも手軽にスプライトを生成することができます。
auto imageSprite = cocos2d::Sprite::create("filename.png");
cocos2d::Sprite::create
メソッドに、ファイル名を指定するだけです。
プリミティブなスプライトを生成する
auto rect = cocos2d::Sprite::create();
スプライトの基準点を変更する
スプライトの基準点はanchorPoint
というプロパティで決まります。
デフォルトは(0.5, 0.5)
のようです。(つまり中心)
ちなみに座標系としては左下が(0.0, 0.0)
、右上が(1.0, 1.0)
となります。
これはCocos2d-xの位置の座標系と同じですね。
テクスチャアトラスを使ってスプライトを生成する
Cocos2d-xには「テクスチャアトラス」という仕組みがあります。
ざっくり言うと、複数ある画像をひとつの画像にまとめて、まとめた画像から座標を指定することで該当の画像を切り出す、というものです。
テクスチャアトラスを利用するメリットは複数画像をひとつにまとめられる、ということと、テキスチャがメモリにロードされるときに発生する可能性のある余分なエリアを最小限にする、というメリットがあります。
cocos2d::SpriteFrameCache
テクスチャアトラスは複数画像をひとつにまとめるため、手作業で管理するのはだいぶ骨が折れます。
しかしcocos2d::SpriteFrameCache
クラスを利用することで手軽に利用することができるようになります。
また、複数にまとめた画像を切り出すのに利用されるメタ情報を管理するものとして「plist」が使われます。
plistやテクスチャアトラスを作るにはいくつかのツールがあるようですが、FreeのものとしてはShoeboxというものがあります。
(Cocos2d-x向けに書きだす設定もあります。こちらの記事に書いてありました)
コード例
SpriteFrameCache::getInstance()->addSpriteFramesWithFile("hoge.plist");
addSpriteFramesWithFile
メソッドに、plistファイル名を渡すだけで自動的にフレームキャッシュを生成してくれます。
テクスチャアトラスから切り出してスプライトを生成するには以下のようにします。
auto sprite = Sprite::createWithSpriteFrameName("hoge.png"); // ここで指定するファイル名は、テクスチャアトラスに変換する前のファイル名(plistに設定が保存されている)
これはゲーム内で実際に使っているテクスチャアトラスのサンプルです。
Sprite Sheet Animation
SpriteFrameCache
を使うことで、テクスチャアトラスから簡単にスプライトを生成できました。
こうしたテクスチャアトラスのもうひとつのメリットとしては、スプライトアニメーションを手軽に利用できる点もあります。
以下のように各フレームのテクスチャを配列に格納し、Animation
クラス、Animate
クラスを利用することで簡単にスプライトアニメーションを実現することができます。
// SpriteFrameCacheにplistを登録する
SpriteFrameCache::getInstance()->addSpriteFrameWithFile("sprites.plist");
// 配列に各フレームを格納
cocos2d::Vector<cocos2d::SpriteFrame*> animFrames(10);
char str[100] = {0};
// 各フレームを、plistに登録されているファイル名を元に生成
for (int i = 1; i <= 10; i++) {
sprintf(str, "sprite_%02d.png", i);
auto frame = cocos2d::SpriteFrameCache::getInstance()->getSpriteFrameByName(str);
animFrames.pushBack(frame);
}
// 上記フレーム配列からアニメーションを生成
auto animation = cocos2d::Animation::createWithSpriteFrames(animFrames, 0.1f);
auto animate = cocos2d::Animate::create(animation);
// 無限ループのアクションを生成
auto repeat = cocos2d::RepeatForever::create(animate);
// スプライトを生成してアクションを実行(空のスプライトでも大丈夫)
auto sprite = cocos2d::Sprite::createWithSpriteFrame(animFrames.front());
sprite->setPosition(cocos2d::Vec2(100, 100));
sprite->runAction(repeat);
this->addChild(sprite);
上記はCocos2d-x(Sprite Sheet Animation)のサイトを参考にしました。
cocos2d::SpriteBatchNodeを使って高速描画
背景など、ひとつの画像を使いまわして繰り返し敷き詰める、というのはよくあると思います。
しかし、cocos2d::Sprite
を複数生成して描画するとコストが高くなります。
(1スプライトに1draw callがかかる)
そこで、cocos2d::SpreiteBatchNode
の出番です。
これは、生成時に渡したテクスチャの情報を複数複製しても、draw callが1回で済む、というもの。
イメージとしては複数の細かいテクスチャを巨大なテクスチャひとつにまとめてくれるものです。
以下のように利用します。
cocos2d::SpriteBatchNode *node = SpriteBatchNde::create("filename.png");
this->addChild(node);
// 10 x 10のタイル上にスプライトを並べる
for (int i = 0; i < 100; i++) {
cocos2d::Sprite *tile = Sprite::createWithTexture(node->getTexture());
int col = i % 10;
int row = i / 10;
int x = 10 * col;
int y = 10 * row;
tile->setPosition(x, y);
node->addChild(tile);
}
比較的大きな画像を100,000個生成してiPhone5で試したところ、通常のスプライトだと5FPSだったのがSpriteBatchNodeにしたら15FPSくらいまでになったので、だいぶ高速化しているのが分かります。
cocos2d::Spriteクラスを継承する
cocos2d::Sprite
クラスを継承する際、コンストラクタ経由で初期化はしないようです。(そもそも、こうした継承はありなのか分かりませんが・・)
Objective-Cのように、init
メソッドとコンビニメソッドを利用して初期化するのが作法のようです。
実際のコードは以下
HogeSprite* HogeSprite::create()
{
HogeSprite *pRet = new HogeSprite();
if (pRet && pRet->init()) {
pRet->autorelease();
// 他に必要な初期化処理
}
else {
delete pRet;
pRet = NULL;
return NULL;
}
}
上記はCREATE_FUNC
マクロがやってくれることを書きだしたものです。
余談
ちなみにinit
メソッド自体をオーバーライドして拡張する場合は以下のようにすればOKかも?
class HogeSprite : public cocos2d::Sprite
{
typedef cocos2d::Sprite inherited;
public:
bool init();
}
/////////////////////////////////
bool HogeSprite::init()
{
if (inherited::init()) {
// 初期化処理
return true;
}
return false;
}
cocos2d::Directorシングルトンクラス
画面サイズなどの様々な状態を管理するクラス。
ヘッダを読むと、OpenGLのcontextの初期化なども担っている様子。
インスタンスはgetInstance
メソッドを利用して取得します。
cocos2d::Director::getInstance()->methodName();
ゲームを一時停止(pause)/再開(resume)する
ゲームを一時停止したり、ということはよくある欲求だと思います。
その場合、手軽に全体の更新を止めるにはcocos2d::Director
クラスのメソッドで実現できます。
// 一時停止
cocos2d::Director::getInstance()->pause();
// 一時停止を再開
cocos2d::Director::getInstance()->resume();
イベントを設定する
タッチイベントをスプライトに設定する
下記コードはこちらの記事を参考にさせて頂きました。
onTouchBeganで透明度を変更
タッチを開始したときに、対象オブジェクトの透明度を下げるサンプルです。
また、ヒットテストを行い、対象のスプライトをタッチしていた場合に限りtrue
を返します。
こうすることで、対象スプライト以外がタッチされていた場合にその後のイベント(onTouchMoved
など)が実行されなくなります。
using namespace cocos2d;
// シングルタッチイベントリスナーを作成
auto listener1 = EventListenerTouchOneByOne::create();
listener1->setSwallowTouches(true);
// onTouchBeganのイベントコールバック(ラムダ式)
listener1->onTouchBegan = [](Touch *touch, Event *event) {
auto target = static_cast<Sprite *>(event->getCurrentTarget());
// ボタンに対する相対的な位置を取得する
// `convertToNodeSpace`で、対象ノードの座標系での位置に変換
Point locationInNode = target->convertToNodeSpace(touch->getLocation());
// コンテンツのサイズ
Size s = target->getContentSize();
// ヒットテスト用コンテンツ矩形を作成
Rect rect = Rect(0, 0, s.width, s.height);
// 生成したヒットテスト用矩形内にタッチポイントがあるかチェック
if (rect.containsPoint(locationInNode)) {
log("Touch Began at x: %f, y: %f", locationInNode.x, locationInNode.y);
return true;
}
return false;
};
// `sprite`ノードに、イベントリスナーを設定
aNode->getEventDispatcher->addEventListenerWithSceneGraphPriority(listener1, sprite);
タッチ位置の判定について
ここの処理で覚えておきたいのはタッチ位置の変換処理の部分。
具体的に言うと以下。
Point locationInNode = target->convertToNodeSpace(touch->getLocation());
// --- 中略 ---
if (rect.containsPoint(locationInNode)) {
// hit!
}
target
(aNode->getEventDispatcher
で登録した対象のノード)の座標空間に変換するために、convertToNodeSpace
メソッドを実行して、タッチ位置を変換しています。
そしてヒットテスト用の矩形を作成し、変換後の位置(要は原点がヒットテスト用矩形と同じになる)が含まれているかを確認しています。
もし含まれていたらtrue
を返し、ヒットしたことを知らせます。
ちなみに「左下」が原点になるようです。
原点についてはあまり直感的な位置ではないので注意が必要ですね。
onTouchMovedで要素を移動してみる
さて、上記処理でスプライトをタッチした場合にさらにドラッグでスプライトを移動できるようにしてみます。
listener1->onTouchMoved = [](Touch *touch, Event *event) {
auto target = static_cast<Sprite*>(event->getCurrentTarget());
auto delta = touch->getDelta();
Vec2 currentPos = target->getPosition();
currentPos += delta;
target->setPosition(currentPos);
};
onTouchBegan
と同様にターゲットを取得します。
そしてtouchオブジェクトのgetDelta
メソッドで、前回との差分位置を取得し、ターゲットの現在の位置に足し込みます。
そしてそれを再セットすれば、ドラッグ処理の完成です。
クラスのメソッドをコールバックに指定する
CC_CALLBACK_#
というマクロを使って、簡単に(ラムダ式ではなく)メソッドをコールバックに指定することができます。
ちなみに#
の部分は0〜3の数字が入り、コールバックが受け取る引数の数によって使い分けるようです。
(コールバック自体の引数が1つの場合はCC_CALLBACK_1
を使う)
ちなみにStack overflowで説明されているのを見つけました → What is the difference between all the CC_CALLBACK_# macros?
今回作ったゲーム内でのサンプルですが、以下のように指定します。
// Touch event listenerのインスタンスを生成
auto *touchListener = EventListenerTouchOneByOne::create();
// onTouchBeganにメソッドを指定(onTouchBeganの引数は2個なので`CC_CALLBACK_2`を使う)
touchListener->onTouchBegan = CC_CALLBACK_2(GameLayer::onTouchBegan, this);
タイマー(schedule)の使い方
Cocos2d-xではいわゆるタイマー処理はschedule
という機能を使って実現します。
具体的には以下のようにします。
this->schedule(schedule_selector(Hoge::method), 5.0f);
ここでのthis
はLayer
クラスを継承したクラスです。
schedule_selectorはマクロになっていて実際にはstatic_cast<cocos2d::SEL_SCHEDULE>(&_SELECTOR)
と変換されるようです。
第二引数はインターバルで、ここで指定した秒数間隔で渡した関数が繰り返し実行される仕組みになっています。
一度だけ実行するタイマーを設定する「scheduleOnce
」
schedule
を使うとインターバルに設定した時間間隔でずっと処理を実行してくれます。
定期的に実行したい場合には便利ですが、一定時間後に一度だけなにかを実行したい、という欲求はゲームではよくあります。
その場合はschedule
ではなくscheduleOnce
メソッドを使います。
使い方はschedule
とほぼ同じで、インターバルの代わりに実行を遅延させたい時間を指定します。
this->scheduleOnce(schedule_selector(Hoge::method), 3.0f);
一度だけ実行するタイマーを設定する「ラムダ式版」
DelayTimeとCallFuncを使います。
スプライトにはアクションを実行するrunAction
メソッドがあります。
それに、いくつかのクラスを併用することでラムダ式で一定時間後の処理を書くことが出来ます。
利用するクラスは以下の3つ。
クラス | 説明 |
---|---|
cocos2d::Sequence |
Sequence は引数に渡されたFiniteTimeAction クラスのオブジェクトを「順次」実行していく |
cocos2d::DelayTime | 引数に渡された秒数分、処理を遅延させる |
cocos2d::CallFunc | 引数に渡されたラムダ式関数をFiniteTimeAction として実行する |
実際にこれを使った例が以下です。
this->runAction(cocos2d::Sequence::create(cocos2d::DelayTime::create(2), cocos2d::CallFunc::create([this]() {
this->hogeMethod();
}), NULL));
フレーム単位での繰り返し処理
ゲームを作る上で欠かせないループ処理。
Cocos2d-xでは、scheduleUpdate
メソッドを実行するとループが開始されるようになっています。
上記メソッドを実行すると毎フレームにupdate
メソッドを実行しようとします。
そのため、Layer
クラスを継承したクラスは、update
メソッドを実装しておかないとなりません。
Objective-Cのdelegate
をイメージすると分かりやすいと思います。
// 定義
class HogeLayer : public cocos2d::Layer
{
private:
void udpate(float frame);
}
// 実装
// this->scheduleUpdate()を実行するとフレーム単位で以下が自動的に呼ばれるようになる
void HogeLayer::update(float frame
{
log("frame is %f", frame);
}
cocos2d::Sequenceを使って繰り返し処理
メソッドとして定義するのではなく、ラムダ式でさくっと繰り返し処理を書きたい場合があるかと思います。
そんなときは、Sequence
とRepeat
、DelayTime
を使って細かい繰り返し処理を書くことが出来ます。
cocos2d::FiniteTimeAction *repeatAction = cocos2d::Sequence::create(cocos2d::CallFunc::create([]() {
int x = arc4random() % 5;
int y = arc4random() % 5;
auto pos = Vec2(x, y);
this->setPosition(pos);
}), cocos2d::DelayTime::create(0.1f), NULL);
this->runAction(Repeat::create(repeatAction, 10));
上記サンプルはランダムに生成したx
、y
の位置に0.1秒間隔で10回処理を実行するものです。
例えばブルブル震えるようなアニメーションなどに使えますね。
ちなみにRepeat::create
の第二引数は繰り返す回数ですが、-1
を指定することで 無限回のループ を作ることもできます。
音を再生する
やはり、ゲームの出来を左右するのは音と言っても過言ではありません。
効果音が入るだけでぐっとゲームらしくなるし、ぜひとも入れたい要素のひとつです。
Cocos2d-xでは音も手軽に利用できるようになっています。
BGMを再生する
ゲームに音楽は欠かせません。
BGMはCocosDenshion::SimpleAudioEngine
を使い、以下のようにすることで簡単に再生できます。
// BGMのボリュームを1.0に設定(設定値は0.0〜1.0の間)
CocosDenshion::SimpleAudioEngine::getInstance()->setBackgroundMusicVolume(1.0);
// BGMを再生する
CocosDenshion::SimpleAudioEngine::getInstance()->playBackgroundMusic("hoge.mp3", true);
SE(効果音)を再生する
SE (Sound Effect) も大事な要素です。
BGMよりも利用する機会が多いでしょう。
BGMと同様、CocosDenshion::SimpleAudioEngine
を使って以下のようにします。
// preload
CocosDenshion::SimpleAudioEngine::getInstance()->preloadEffect("se.mp3");
// SEを再生
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("se.mp3");
その他、制御メソッド
メソッド/変数名 | 意味 |
---|---|
rewindBackgroundMusic() |
BGMを先頭から再生 |
pauseBackgroundMusic() |
BGMの再生を一時停止 |
resumeBackgroundMusic() |
BGMを再開 |
isBackgroundMusicPlaying |
BGM再生中か |
stopEffect(soundID) |
再生時に取得したint型のIDを指定してSEを止める |
pauseEffect(soundID) |
IDを指定して一時停止 |
resumeEffect(soundID) |
IDを指定して再生を再開 |
ちなみにDenshion
はなんかの英単語かと思ったら「電子音」という意味でした( ;´Д`)
物理エンジン
※ Cocos2d-xから、物理エンジンは「Chipmunk2D」が標準となったようです。
やはりゲームと言ったら物理エンジンは重要な要素です。
物理エンジンは様々ありますが、基本的な概念はどれも同じです。
剛体と呼ばれる、衝突判定を持つオブジェクト、重力を表す「世界」、そして物体同士がぶつかったか判定するContact(Collision)などです。
(ちなみに過去に物理エンジンの仕組みを理解するため、本を読みながら物理エンジンを自作した際にまとめた記事があるので、興味がある方はこちら(自作2D物理エンジンを作った話)を参照ください)
大まかなセットアップの流れは以下になります。
- 物理演算される世界を作る
- 剛体の元となるスプライトを作成する
- 物理演算の対象となる物体を定義する(
PhysicsBody
) - 物体の重さや形状などを設定
- (3)をスプライトに設定(
sprite->setPhysicsBody()
)
物理演算される世界を作る
実際のところ、物理演算世界の生成は簡単です。
以下のように、Sceneの生成メソッドを変えるだけです。
auto scene = cocos2d::Scene::createWithPhysics();
auto world = scene->getPhysicsWorld(); // 物理演算する「世界」の取得
auto gravity = cocos2d::Vec2(0, -50); // 下方向に向かって重力を設定する
world->setGravity(gravity);
world->setSpeed(6.0f);
余談
ちなみに、以下のようにすることでデバッグモードでレンダリングされるようになります。
デバッグモードでは、普段は可視化されない物理演算の対象となる形を表示してくれます。
world->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
衝突判定(BitMask)
物理エンジンには一般的に、衝突判定にBitMaskを用います。
Cocos2d-xも同様で、以下のように3つのBitMaskが用意されています。
Bit mask type | 意味 |
---|---|
CategoryBitMask | カテゴリ |
ContactBitMask | 接触判定 |
CollisionBitMask | 衝突判定 |
それぞれの意味は、Cocos2d-xのwikiから引用すると、
There are three values: CategoryBitmask, ContactTestBitmask and CollisionBitmask. you can use corresponding get/set method to get/set them. They are tested by logical and operation. When CategoryBitmask of one body and with ContactTestBitmask of another body with the result doesn't equal to zero, the contact event will be sended, overwise the contact event won't be sended. When CategoryBitmask of one body and with CollisionBitmask of another body with the result doesn't equal to zero, they will collied, overwise it won't. be ware, in default, CategoryBitmask value is 0xFFFFFFFF, ContactTestBitmask value is 0x00000000, and CollisionBitmask value is 0xFFFFFFFF, it means all body will collide with each other but with out send contact event by default.
と記載があります。
それぞれのデフォルト値は
Bit mask type | デフォルト値 |
---|---|
CategoryBitmask | 0xFFFFFFFF |
ContactTestBitmask | 0x00000000 |
CollisionBitmask | 0xFFFFFFFF |
になります。
基本的にCategoryBitMaskとContactTestBitmaskか、CollisionBitmaskが評価されます。つまり、
- カテゴリ x コンタクト
- カテゴリ x コリジョン
のふたつです。
そしてそれぞれのBitmaskは理論積として計算され、結果がゼロ以外の場合に検出対象となります。
ContactとCollisionの違い
Contactは接触、Collisionは衝突を意味する単語です。
なんとなくどちらも同じような感じに思えますが、前者は「接触したかどうか」の判定に対し、後者は「衝突するか」の判定です。
つまり、接触したものの衝突を行わない場合は、接触したことを検知しつつも跳ね返ったりしない、といったことができます。
上記の具体的な例で言えば、とあるエリアにオブジェクトが侵入したか、といった判定を行いたい場合に有効です。
その場合、とあるエリアを表す範囲を設定し、接触判定のみ有効になるようにします。
すると接触判定された場合にイベントが発生しますが、衝突処理はされないため、とあるエリアにオブジェクトが侵入した、というイベントだけを受け取ることができるわけです。
衝突した際にイベントを受け取る
タッチイベントなどと同様、物理エンジンによって衝突が検知された際にもイベントを受け取って処理を行うことができます。
衝突イベントはいくつか種類があり、衝突開始時、衝突中、衝突の解消前、衝突の解消後(跳ね返りなどが計算されたあと)の4つが用意されています。
最初の衝突開始時など、bool
を返すイベントは、false
を返すことでその後のフェーズのイベントをスキップさせることができます。また、衝突開始時のコールバック内でfalse
を返すと、衝突そのものを無効にすることができます。
衝突を検知したが、特定の条件下では衝突をなかったことにしたい、などという場合に利用できます。
実装コード
// Listenerインスタンスを生成する
auto contactListener = EventListenerPhysicsContact::create();
// `onContactBegin`イベントにメソッドを設定
contactListener->onContactBegin = CC_CALLBACK_1(GameLayer::onContactBegin, this);
CCControl系を使用する
こちらの記事(cocos2d-xのGUIを使う)を参考にさせて頂きました。
どうやら、CCControl系のクラスを利用する場合は、Extensionを別途追加する必要があるようです。
Extensionは、ダウンロードしてきたCocos2d-xのディレクトリの中にextensions
というディレクトリがあるので、その中から必要なものをコピーします。
同じディレクトリ内にあるcocos-ext.h
を読み込ませることでExtensionが利用できるようになります。
ControlSliderを使う
以下のように生成し、イベントを登録します。
auto aSlider = cocos2d::ControlSlider::create("slider-background.png", "slider-progress.png", "slider-thumbnail.png");
aSlider->addTargetWithActionForControlEvents(this, cccontrol_selector(aClass::method), cocos2d::extension::Control::EventType::VALUE_CHANGED);
create
メソッドに指定している3つの画像はそれぞれ、「スライダーの背景」「スライダーを伸ばした際のゲージ」「スライダーのボタン」です。
今回作成したゲームで実際に使っているのは以下の画像です。
MotionStreakクラスでエフェクトを作る
cocos2d::MotionStreak
クラスを使うと、剣の軌跡のようなエフェクトを表現できます。
(今回作ったゲームには採用していませんが・・)
static int streak_tag = 1000;
static int streak_layer = 10000;
auto listener = cocos2d::EventListenerTouchOneByOne::create();
listener->onTouchBegan = [this](Touch *touch, Event *event) -> bool {
this->removeChildByTag(streak_tag);
auto streak = cocos2d::MotionStreak::create(0.8f, 1, 20, Color3B(200, 200, 250), "streak_texture.png");
streak->setBlendFunc(BlendFunc::ADDITIVE);
streak->setPosition(300, 300);
this->addChild(streak, streak_layer, streak_tag);
return true;
};
listener->onTouchMoved = [=](Touch *touch, Event *event) {
auto streak = (cocos2d::MotionStreak*)this->getChildByTag(streak_tag);
streak->setPosition(touch->getLocation());
};
this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this);
作ったアプリたち
今回の「ゲームレース」では社内のメンバー4人で「誰が一番売り上げるか」というルールで勝負しましたw
作られたゲームたちを紹介します。(申請中のものもあるので、順次、公開され次第更新していく予定です)
8月頭から企画、実装を開始して9月に申請する、というルールでした。
1ヶ月という短い期間ながら、みんな面白いゲームを作ったのでよかったら遊んでみてください( *'-')
Swipe Defender
まずは自分から。
今回の「ゲームレース」で作ったゲームは「Swipe Defender」というゲームです。
スワイプでバリアを張って、隕石を打ち返すシンプルアクションゲームです。
すしとぼく
inon29さんの作品「すしとぼく」。シンプルながらかわいいイラストとポップな音が癖になります。
Android版。(iOSはただいま申請中)