cheka.jp 超不定期で更新する写真達。お口直しにどうぞ。

【ゲームを作ろう】衝突を検知して当たり判定を考える2

今日は実際に手を動かしてチェックしてみました。
最終的にパーティクルも利用しているので、まだ整理が必要な箇所もありますが気にせず進めます。

前回の復習

想定しているオブジェクトはこんな感じでした。

継承

それぞれの子クラスで当たり判定を実装します。
判定内容は以下の2つです。
1.敵は弾丸と主人公
2.主人公はアイテムと障害物

主人公側は色々検討中なので敵との当たり判定を書いてみます。

親クラス

ヘッダと実装内容です。
パーティクルを追加するLayerを当たり判定のメソッドへ渡します。

class ParentSprite : public cocos2d::Sprite
{
private:
    //当たりを判定する範囲
    float radius;
public:
    //範囲を設定する
    void setRadius(float radius);
    //範囲を取得する
    float getRadius();
    //当たりが発生した場合の呼ばれる
    bool collisionDetected(ParentSprite* sender,cocos2d::Layer* layer);
};
#include "ParentSprite.h"

USING_NS_CC;

/*
 当たりを判定する範囲
 */
void ParentSprite::setRadius(float radius)
{
    this->radius = radius;
}

/*
 当たりが発生した場合の呼ばれる
 */
bool ParentSprite::collisionDetected(ParentSprite* sender,cocos2d::Layer* layer)
{
    //当たりの場合はtrue
    return false;
}

/*
 範囲を取得する
 */
float ParentSprite::getRadius()
{
    return this->radius;
}

子クラス

スプライトシートからキャラクターを追加します。

#include "cocos2d.h"
#include "ParentSprite.h"

class CharacterSprite : public ParentSprite
{
public:
    //自分のタグ
    static const int TAG = 100;
    //キャラクターの生成
    static cocos2d::Sprite* createCharacter(cocos2d::Size frame);
};
#include "CharacterSprite.h"

USING_NS_CC;

/*
キャラクターの生成
*/
cocos2d::Sprite* CharacterSprite::createCharacter(cocos2d::Size frame)
{
    //キャラクターの作成
    CharacterSprite* character = new CharacterSprite();
    Rect rect = SpriteFrameCache::getInstance()->getSpriteFrameByName("chara_0")->getRect();
    character->initWithSpriteFrame(SpriteFrame::create("spritesheets.png",rect));
    character->setRadius(character->getContentSize().width);
    character->setTag(CharacterSprite::TAG);
    //スタートポジション
    character->setPosition(Point(frame.width/2,character->getContentSize().height));
    return character;
}

スプライトシートから弾を生成します。
キャラクターの少し前から発射して、画面の外まで行ったら親から削除します。
また、スピードを一定にするためにタッチポジション + コンテンツの縦サイズ + 画面サイズまで移動させるようにします。

#include "cocos2d.h"
#include "ParentSprite.h"

class BulletSprite : public ParentSprite
{
private:
    //敵のライフを減らすパワー
    int power;
public:
    //自分のタグ
    static const int TAG = 200;
    //弾クラスの生成
    static cocos2d::Sprite* createBullet(cocos2d::Point touchPoint,cocos2d::Size frame);
    //パワーの設定
    void setPower(int power);
    //パワーの取得
    int getPower();
    //アニメーション完了後の弾丸削除
    void removeBullet();
};
#include "BulletSprite.h"

USING_NS_CC;

/*
 弾の作成
 */
cocos2d::Sprite* BulletSprite::createBullet(Point touchPoint,Size frame)
{
    //弾丸の作成
    BulletSprite* bullet = new BulletSprite();
    Rect rect = SpriteFrameCache::getInstance()->getSpriteFrameByName(StringUtils::format("bullet_%d",(rand() % 7)))->getRect();
    bullet->initWithSpriteFrame(SpriteFrame::create("spritesheets.png",rect));
    bullet->setRadius(bullet->getContentSize().width);
    bullet->setTag(BulletSprite::TAG);
    bullet->setPower(1);
    //スタートポジション
    bullet->setPosition(touchPoint);
    MoveTo* moveTo = MoveTo::create(3,Point(touchPoint.x,touchPoint.y+frame.height+bullet->getContentSize().height));
    //アニメーション終了後に呼ぶメソッドを指定
    cocos2d::CallFunc *moveToDone = CallFunc::create([bullet]()
                                                     {
                                                         bullet->removeBullet();
                                                     });
    //アニメーションを実行
    bullet->runAction(Sequence::create(moveTo, moveToDone, NULL));    
    return bullet;
}

/*
 パワーの設定
 */
void BulletSprite::setPower(int power)
{
    this->power = power;
}

/*
 パワーの取得
 */
int BulletSprite::getPower()
{
    return this->power;
}

/*
 弾の削除
 */
void BulletSprite::removeBullet()
{
    //画像を削除する
    this->removeFromParent();
}

最後に当たり判定を実装する敵クラスです。
画面の外まで移動して親から削除するロジックは同じです。
当たり判定でspriteのタグから相手を判断し、相手に応じて処理内容を変更します。

#include "cocos2d.h"
#include "ParentSprite.h"
#include "CharacterSprite.h"
#include "BulletSprite.h"
class EnemySprite : public ParentSprite
{
private:
    //倒すのに必要な弾数
    int life;
public:
    //自分のタグ
    static const int TAG = 300;
    //弾クラスの生成
    static cocos2d::Sprite* createEnemy(cocos2d::Size frame);
    //必要なライフの設定
    void setLife(int life);
    //アニメーション完了後の敵削除
    void removeEnemy();
    //当たりが発生した場合の呼ばれる
    bool collisionDetected(ParentSprite* sender,cocos2d::Layer* layer);
};
#include "EnemySprite.h"

USING_NS_CC;

/*
 敵の作成
 */
cocos2d::Sprite* EnemySprite::createEnemy(Size frame)
{
    //弾丸の作成
    int enemyNum = (rand() % 2);
    CCLOG("%d",enemyNum);
    EnemySprite* enemy = new EnemySprite();
    Rect rect = SpriteFrameCache::getInstance()->getSpriteFrameByName(StringUtils::format("enemy_%d_0",enemyNum))->getRect();
    enemy->initWithSpriteFrame(SpriteFrame::create("spritesheets.png",rect));
    enemy->setRadius(enemy->getContentSize().width);
    enemy->setTag(EnemySprite::TAG);
    enemy->setLife(enemyNum+1);
    //スタートポジション
    int x = frame.width / ((rand() % 5) + 2) + enemy->getContentSize().width;
    enemy->setPosition(Point(x,frame.height));
    //はしまで移動する
    MoveTo* moveTo = MoveTo::create(5,Point(x,-1*enemy->getContentSize().height));
    //アニメーション終了後に呼ぶメソッドを指定
    cocos2d::CallFunc *moveToDone = CallFunc::create([enemy]()
                                                     {
                                                         enemy->removeEnemy();
                                                     });
    //アニメーションを実行
    enemy->runAction(Spawn::createWithTwoActions(Animate::create(anim),Sequence::create(moveTo, moveToDone, NULL)));
    return enemy;
}

//必要なライフの設定
void EnemySprite::setLife(int life)
{
    this->life = life;
}

/*
 敵の削除
 */
void EnemySprite::removeEnemy()
{
    //画像を削除する
    this->removeFromParent();
}

/*
 当たりが発生した場合の呼ばれる
 */
bool EnemySprite::collisionDetected(ParentSprite* sender,cocos2d::Layer* layer)
{

    //当たり判定結果
    bool collition = false;
    //自分の範囲
    Rect myRect = Rect(this->getPosition().x - (this->getRadius()/2),
                                   this->getPosition().y - (this->getRadius()/2),
                                   this->getRadius(),
                                   this->getRadius());
    //相手の範囲
    Rect senderRect = Rect(sender->getPosition().x - (sender->getRadius()/2),
                                   sender->getPosition().y - (sender->getRadius()/2),
                                   sender->getRadius(),
                                   sender->getRadius());
    //当たり判定
    if(myRect.intersectsRect(senderRect))
    {
        //当たった相手は親から削除する
        sender->removeFromParent();
        //パーティクル
        if(sender->getTag() == CharacterSprite::TAG)
        {
            //ゲームキャラクターと衝突
            ParticleSystemQuad* p1 = ParticleSystemQuad::create("particle_over.plist");
            p1->setAutoRemoveOnFinish(true);
            p1->setPosition(this->getPosition());
            layer->addChild(p1,1);
        }
        else
        {
            //弾と衝突
            //ライフを減らす
            ((BulletSprite*)sender)->getPower();
            this->life -= ((BulletSprite*)sender)->getPower();
            ParticleSystemQuad* p1 = ParticleSystemQuad::create("particle_collision.plist");
            p1->setAutoRemoveOnFinish(true);
            p1->setPosition(this->getPosition());
            layer->addChild(p1,1);
        }

        //ライフが無くなったら親から削除する
        if(this->life<=0)
        {
            //パーティクル
            ParticleSystemQuad* p2 = ParticleSystemQuad::create("particle_remove.plist");
            p2->setAutoRemoveOnFinish(true);
            p2->setPosition(this->getPosition());
            layer->addChild(p2,1);
            this->removeFromParent();
        }
        //画像を削除する

        collition = true;
    }
    //結果の返却
    return collition;
}

当たり判定を呼び出す

後はゲーム中の画面から定期的に当たり判定チェックメソッドを呼び出すだけです。
アイテム判定は考え中なのでコメントアウトしています。

//当たり判定
void GameScene::doCollision(float dt)
{
    if(this->modeGame == GameScene::MODE_GAME_ON)
    {
        //当たり判定を行う
        for(Node* child:this->batchNode->getChildren())
        {
            int tagChk = child->getTag();
            //敵側から判定する
            if (tagChk == EnemySprite::TAG)
            {
                EnemySprite* enemy = (EnemySprite*)child;
                for(Node* bullet:this->batchNode->getChildren())
                {
                    int tag = bullet->getTag();
                    if (tag == BulletSprite::TAG)
                    {
                        //判定相手が弾
                        if(enemy->collisionDetected((ParentSprite*)bullet,this))
                        {
                            break;
                        }
                    }
                    else if(tag == CharacterSprite::TAG)
                    {
                        //判定相手がキャラクター
                        if(enemy->collisionDetected((ParentSprite*)this->character,this))
                        {
                            this->modeGame = GameScene::MODE_GEME_OVER;
                            return;
                        }
                    }
                }
            }
//            else if(tagChk == CharacterSprite::TAG)
//            {
//                //アイテムとの衝突判定を行う TODO
//            }
        }
    }
}

パフォーマンス見ながらロジックを調整する必要はありますが、今のところは大丈夫そうです。
ここにアニメーションや背景スクロールを組み合わせるとゲームっぽくなっていきます。

Add a Comment

メールアドレスが公開されることはありません。