Prainブログ

ゲーム開発とかIT小話とかその他雑記のブログ

*

ゲーム制作 Cocos2d-x関連 第14回 「プレイヤー攻撃によるダメージ計算、ターン交代」

      2017/05/31

今回はプレイヤーのターン、敵のターンを交互に行うようにしていきます。また、それぞれのターンで相手に攻撃を行い、ダメージ計算もしていきます。

 

 

 

スポンサーリンク

 

 

ターン交代の仕様

ターンの交代のやり方は至ってシンプルで、前回追記した「_state」の内容を変更するだけです。ただし、変更するタイミングには注意しなければなりません。このゲームでは

・プレイヤー攻撃が終わったら敵のターンに移行

・敵の攻撃が終わったらプレイヤーのターンに移行

としましょう。

 

 

攻撃力決定の仕様、ダメージ計算の仕様

プレイヤーの攻撃力決定の仕様

・「基礎攻撃力(100) × ボールにヒットした回数」を攻撃力とする

・敵の弱点属性のドロップにヒットした場合は基礎攻撃力(100)×1.5とする。

・敵の耐性属性のドロップにヒットした場合は基礎攻撃力(100)×0.5とする。

 

敵の攻撃力決定の仕様

・50、100、150、200のいずれかをランダムで決定し、攻撃力とする

 

ダメージ計算の仕様

・攻撃力をそのままダメージとしてHPから減じる

 

属性の仕様

・赤>緑>青>赤

・黄≧紫≧黄

(例: 赤>緑 なら赤は緑にとって弱点属性であり、緑は赤にとって耐性属性)

(例: 黄≧紫 なら紫は黄色にとって弱点属性)

 

 

 

それではターンの交代、プレイヤーの攻撃、敵の攻撃プログラムをまとめて実装してきます。

 

 

プログラムの実装

 

BaseChara.h

// HP表示
cocos2d::Label* _hpLabel = nullptr; // HPを表示するラベル
float _hpLabelPosX; // HPラベルの表示位置X
float _hpLabelPosY; // HPラベルの表示位置Y
cocos2d::Label* createHpLabel(); // HPラベルを生成して返却する

bool _doAttack = false; // 攻撃を行っているかどうかを判断するフラグ

 

・Label

Cocos2d-xのクラスで、画面上に文字を表示します。

 

 

BaseChara.cpp BaseChara::createHpLabel

cocos2d::Label* BaseChara::createHpLabel(){
    if(_hpLabel != nullptr){
        _hpLabel->removeFromParent();
    }
    
    _hpLabel = cocos2d::Label::createWithTTF(std::to_string(_hp), "fonts/arial.ttf" , 60);
    _hpLabel->setColor(cocos2d::Color3B::BLACK);
    _hpLabel->setPosition(cocos2d::Vec2(_hpLabelPosX, _hpLabelPosY));
    
    return _hpLabel;
}

 

 

HelloWorldScene.h

    std::map<COLOR, int> _dropHitCountMap; // ヒットしたドロップの種類と回数を記録する
    int playerAttack(); // ヒットしたドロップに応じてトータルの攻撃力を決定する

 

・std::map

C++のコンテナです。マップのキーを指定すると、キーと対になる値を取得します。今回定義したマップCOLOR型のキーとint型の値を持つマップです。

例えば、赤2、黄5、青1、緑2、紫3ずつドロップにボールがヒットした場合、下図の様なイメージのマップとなるようプログラムで記述しています。

 

map

 

例えば、「_dropHitCountMap[TYPE_RED]」と記述することでint型の2を返します。

 

 

HelloWorldScene.cpp HelloWorld::init

bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Layer::init() )
    {
        return false;
    }
    
    Size visibleSize = Director::getInstance()->getVisibleSize();

    
    // プレイヤー(ボール)
    _ball = BaseChara::create("ball.png");
    _ball->setPosition(Vec2(visibleSize.width/2, visibleSize.height/2));
    _ball->_hp = 1000;
    _ball->_attackPoint = 100;
    
    // プレイヤーHPの表示
    Label* playerHp = Label::createWithTTF("PLAYER HP", "fonts/arial.ttf" , 60);
    playerHp->setColor(Color3B::BLACK);
    playerHp->setPosition(Vec2(600, 260));
    this->addChild(playerHp, 1, 0);
    
    _ball->_hpLabelPosX = 700;
    _ball->_hpLabelPosY = 200;
    _ball->_hpLabel = _ball->createHpLabel();
    this->addChild(_ball->_hpLabel, 1, 0);
    
    this->addChild(_ball, 1, 0);
    // ドロップ5種
    // 赤
    BaseChara* dropRed = BaseChara::create("color_drop_red.png");
    dropRed->setPosition(Vec2(100.0, 600.0));
    dropRed->_colorType = TYPE_RED;
    this->addChild(dropRed, 1, 1);
    
    // 黄
    BaseChara* dropYellow = BaseChara::create("color_drop_yellow.png");
    dropYellow->setPosition(Vec2(200, 700));
    dropYellow->_colorType = TYPE_YELLOW;
    this->addChild(dropYellow, 1, 2);
    
    // 青
    BaseChara* dropBlue = BaseChara::create("color_drop_blue.png");
    dropBlue->setPosition(Vec2(100, 1000));
    dropBlue->_colorType = TYPE_BLUE;
    this->addChild(dropBlue, 1, 3);
    
    // 緑
    BaseChara* dropGreen = BaseChara::create("color_drop_green.png");
    dropGreen->setPosition(Vec2(400, 600));
    dropGreen->_colorType = TYPE_GREEN;
    this->addChild(dropGreen, 1, 4);
    
    // 紫
    BaseChara* dropPurple = BaseChara::create("color_drop_purple.png");
    dropPurple->setPosition(Vec2(500, 900));
    dropPurple->_colorType = TYPE_PURPLE;
    this->addChild(dropPurple, 1, 5);
    
    // 背景
    Sprite* background = Sprite::create("background.png");
    background->setPosition(Vec2(visibleSize.width/2, visibleSize.height/2));
    this->addChild(background, 0, 6);
    
    // 敵
    _enemy = BaseChara::create("enemy.png");
    _enemy->setPosition(Vec2(visibleSize.width / 2, visibleSize.height - _enemy->getContentSize().height / 2));
    _enemy->_hp = 5000;
    // 敵HPの表示
    Label* enemyHp = Label::createWithTTF("ENEMY HP", "fonts/arial.ttf" , 60);
    enemyHp->setColor(Color3B::BLACK);
    enemyHp->setPosition(Vec2(600, 1160));
    this->addChild(enemyHp, 1, 0);
    
    _enemy->_hpLabelPosX = 700;
    _enemy->_hpLabelPosY = 1100;
    _enemy->_hpLabel = _enemy->createHpLabel();
    this->addChild(_enemy->_hpLabel, 1, 0);
    
    this->addChild(_enemy, 1, 7);
    
    
    
    // タップイベントを取得する
    auto listener = EventListenerTouchOneByOne::create();
    listener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this);
    listener->onTouchEnded = CC_CALLBACK_2(HelloWorld::onTouchEnded, this);
    listener->onTouchMoved = CC_CALLBACK_2(HelloWorld::onTouchMoved, this);
    this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this);
    
    
    // ドロップ5種の格納
    _dropList.push_back(dropRed);
    _dropList.push_back(dropYellow);
    _dropList.push_back(dropBlue);
    _dropList.push_back(dropGreen);
    _dropList.push_back(dropPurple);
    
    
    
    // パワーゲージ(外枠)
    _powerGaugeFrame = BaseChara::create("power_gauge_frame.png");
    _powerGaugeFrame->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 20));
    this->addChild(_powerGaugeFrame, 0, 5);
    
    // パワーゲージ
    _powerGauge = BaseChara::create("power_gauge.png");
    _powerGauge->setPosition(Vec2(visibleSize.width / 2 - _powerGauge->getContentSize().width / 2
                                  , visibleSize.height / 20 - _powerGauge->getContentSize().height / 2));
    _powerGauge->setAnchorPoint(KM_VEC2_ZERO);
    _powerGaugeOrigin = _powerGauge->getTextureRect().origin;
    _powerGaugeWidth = _powerGauge->getContentSize().width;
    this->addChild(_powerGauge, 0, 5);
    
    
    // 音楽のプリロード
    CocosDenshion::SimpleAudioEngine::getInstance()->preloadBackgroundMusic("game_maoudamashii_4_field09.mp3"); // バトル曲
    CocosDenshion::SimpleAudioEngine::getInstance()->preloadBackgroundMusic("game_maoudamashii_9_jingle05.mp3"); // 勝利曲
    CocosDenshion::SimpleAudioEngine::getInstance()->preloadBackgroundMusic("game_maoudamashii_9_jingle10.mp3"); // 敗北曲
    
    // 効果音のプリロード
    CocosDenshion::SimpleAudioEngine::getInstance()->preloadBackgroundMusic("kick1.mp3"); // 打撃音
    CocosDenshion::SimpleAudioEngine::getInstance()->preloadBackgroundMusic("reflection.mp3"); // 反射音
    
    // 音楽の再生
    CocosDenshion::SimpleAudioEngine::getInstance()->playBackgroundMusic("game_maoudamashii_4_field09.mp3", true);
    
    scheduleUpdate();
    
    
    return true;
}

 

 

HelloWorldScene.cpp HelloWorld::update

void HelloWorld::update(float dt){
    
    // プレイヤーのターン
    if(_state == TYPE_PLAYER_TURN){
        
        // パワーゲージの増加
        if(_isPowerGaugeCounting){
            if(_powerGaugeCount >= 100){
                _powerGaugeCount = 100;
                _powerGaugeStopCount++;
                if(_powerGaugeStopCount >= 6){
                    _powerGaugeCount = 0;
                    _powerGaugeStopCount = 0;
                }
            }else if(35 < _powerGaugeCount && _powerGaugeCount < 100){
                _powerGaugeCount += 10;
            }else{
                _powerGaugeCount += 5;
            }
            
            // パワーゲージ増減の表示
            viewPowerGauge();
        }
        
        if(_ball->_isBallMoving){
            float x = _ball->getPosition().x - _ball->_speed*cosf(CC_DEGREES_TO_RADIANS(_ball->_degree));
            float y = _ball->getPosition().y + _ball->_speed*sinf(CC_DEGREES_TO_RADIANS(_ball->_degree));
            _ball->setPosition(Vec2(x, y));
            
            
            // ドロップに接触した場合の跳ね返りの処理
            std::list<Sprite*>::iterator it;
            for(it = _dropList.begin(); it != _dropList.end(); it++){
                if(hitDetectionOval(_ball, (*it))){
                    // 跳ね返り角度の更新
                    _ball->_degree = getDegree(_ball->getPosition(), (*it)->getPosition());
                    
                    // 効果音
                    CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("reflection.mp3");
                    
                    // ヒットエフェクトの表示
                    Vec2 pos = Vec2(ccpMidpoint(_ball->getPosition(), (*it)->getPosition()));
                    viewHitEffect(pos);
                    
                    // 接触したドロップのカウント
                    _dropHitCountMap[((BaseChara*)(*it))->_colorType]++;
                }
            }
            
            // 画面端に接触した場合の跳ね返りの処理
            Size visibleSize = Director::getInstance()->getVisibleSize();
            // 画面下端
            if(_ball->getPosition().y - _ball->getContentSize().height / 2 <= 0){
                _ball->_degree = _ball->_degree * -1;
                y = _ball->getContentSize().height / 2;
            }
            
            // 画面上端
            if(_ball->getPosition().y + _ball->getContentSize().height / 2 >= visibleSize.height - _enemy->getContentSize().height){
                _ball->_degree = _ball->_degree * -1;
                y = visibleSize.height - _ball->getContentSize().width / 2 - _enemy->getContentSize().height;
            }
            
            // 画面左端
            if(_ball->getPosition().x - _ball->getContentSize().width / 2 <= 0){
                _ball->_degree = 180 - _ball->_degree;
                x = _ball->getContentSize().width / 2;
            }
            
            // 画面右端
            if(_ball->getPosition().x + _ball->getContentSize().width / 2 >= visibleSize.width){
                _ball->_degree = 180 - _ball->_degree;
                x = visibleSize.width - _ball->getContentSize().width / 2;
            }
            
            _ball->setPosition(Vec2(x, y));
            
            
            // 減速処理
            _ball->_speedDownCount++;
            // 一定回数をカウントしたら徐々に減速する
            if(_ball->_speedDownCount > _powerGaugeCount){
                // スピードが0になったらムーブフラグをfalseにする。減速したスピードを元に戻し、スピードダウンカウントを0に初期化する
                // 減速し終えたら攻撃エフェクト
                if(_ball->_speed <= 0){
                    
                    // 攻撃力計算
                    int attackPoint = playerAttack();
                    
                    // 敵HP減算
                    _enemy->_hp -= attackPoint;
                    
                    // 敵HP表示更新
                    _enemy->_hpLabel = _enemy->createHpLabel();
                    this->addChild(_enemy->_hpLabel);
                    
                    // 減速していたスピードを元に戻す
                    _ball->_speed = 50;
                    
                    // ボール移動中フラグを元に戻す
                    _ball->_isBallMoving = false;
                    
                    // カウンタのリセット
                    _ball->_speedDownCount = 0;
                    
                    // ターン交代
                    _state = TYPE_ENEMY_TURN;
                    
                }else{
                    // 減速処理
                    _ball->_speed -= 1;
                    
                }
            }
            
        }
        
    }
    
    
    // 敵のターン
    else if(_state == TYPE_ENEMY_TURN){
        if(!_enemy->_doAttack){
            // 攻撃フラグをtrueにして以降の処理は一回のみ実施する
            _enemy->_doAttack = true;
            
            // 攻撃力計算(50、100、150、200)のいずれか
            int attackPoint = (arc4random()%4 + 1) * 50;
            
            // プレイヤーHP減算
            _ball->_hp -= attackPoint;
            
            // プレイヤーHP表示更新
            _ball->_hpLabel = _ball->createHpLabel();
            this->addChild(_ball->_hpLabel);
            
            // 攻撃フラグを元に戻す
            _enemy->_doAttack = false;
            
            // ターン交代
            _state = TYPE_PLAYER_TURN;
            
        }
    }
    
    
    // 勝利イベント
    else if(_state == TYPE_WIN){
        
    }
    
    
    // 敗北イベント
    else if(_state == TYPE_LOSE){
        
    }

}

 

 

HelloWorldScene.cpp HelloWorld::playerAttack

int HelloWorld::playerAttack(){
    
    int totalDamage = 0;
    
    std::map<COLOR, int>::iterator it;
    for(it = _dropHitCountMap.begin(); it != _dropHitCountMap.end(); it++){
        // 赤>緑>青>赤 の3すくみ
        if((_enemy->_colorType == TYPE_GREEN && (*it).first == TYPE_RED)
           || (_enemy->_colorType == TYPE_RED && (*it).first == TYPE_BLUE)
           || (_enemy->_colorType == TYPE_BLUE && (*it).first == TYPE_GREEN)){
            totalDamage += (*it).second * _ball->_attackPoint * 1.5;
            
        }else if((_enemy->_colorType == TYPE_GREEN && (*it).first == TYPE_BLUE)
                 || (_enemy->_colorType == TYPE_RED && (*it).first == TYPE_GREEN)
                 || (_enemy->_colorType == TYPE_BLUE && (*it).first == TYPE_RED)){
            totalDamage += (*it).second * _ball->_attackPoint * 0.5;
        }
        
        // 黄色>紫 黄色<紫
        else if((_enemy->_colorType == TYPE_YELLOW && (*it).first == TYPE_PURPLE)
                || (_enemy->_colorType == TYPE_PURPLE && (*it).first == TYPE_YELLOW)){
            totalDamage += (*it).second * _ball->_attackPoint * 1.5;
        }else{
            totalDamage += (*it).second * _ball->_attackPoint * 1;
        }
        
    }
    
    // 計算が終わったので不要になったマップをクリアする
    _dropHitCountMap.clear();
    
    return totalDamage;
    
}

 

 

 

プログラム解説

プレイヤーの攻撃

HelloWorldScene.cpp HelloWorld::update

45~46行目で、ヒットしたドロップの種類ごとの回数を「_dropHitCountMap」に記録しています。

 

// 接触したドロップのカウント
_dropHitCountMap[((BaseChara*)(*it))->_colorType]++;

ですが、少し難しいと思うので補記しておきます。

 

・「((BaseChara*)(*it))」 = ドロップ(のポインタ)
・「ドロップ(のポインタ)->colorType」 = ドロップのカラータイプ
・「_dropHitcountMap[ドロップのカラータイプ]」 = 指定された「ドロップのカラータイプ」をキーとするマップの「値」

 

今回、「値」はint型なので、++でインクリメントできる。という記述です。

マップのイメージは上記std::mapを参照してください。

 

 

HelloWorldScene.cpp HelloWorld::playerAttack

「HelloWorld::update」内で「_dropHitCountMap」に記録された属性ごとのドロップヒット回数を、属性の仕様を勘案して攻撃力を算出し、ターン内で合算した最終的な攻撃力を返却しています。

 

マップのループ処理

    std::map<COLOR, int>::iterator it;
    for(it = _dropHitCountMap.begin(); it != _dropHitCountMap.end(); it++){}

 

でmapのループ処理が可能です。ループ内では「(*it).first」でマップのキー、「(*it).second」でキーに紐づく値にアクセスできます。知っていると便利な形かと思います。

 

 

敵の攻撃

・arc4random()

C++のメソッドで、乱数を生成します。「arc4random_uniform」の方が乱数の偏りが少ないようです。

 

 

ターン交代

プレイヤーのターンでは「_state = TYPE_ENEMY_TURN;」が実行された後は、次のupdate処理で「else if(_state == TYPE_ENEMY_TURN)」に引っかかるので敵のターンへの交代が実現できます。

敵のターンからプレイヤーのターンに交代するときも原理は同じです。

 

 

シミュレータの起動

それではシミュレータを起動して動作確認しましょう。

ターンが移動しているのが確認できますでしょうか?・・・へ?よくわからない?いやいや、ちゃ~んとターン交代してます。下のスクリーンショットを見てください。

 

14_sc_simu

・・・プレイヤーのHP(初期値1000)が減っていますよね。これは敵のターンでプレイヤーが攻撃を受けた証拠です。

・・・・・・・

 

はい、それではターンを交代していることがわかりやすくなるように、次回はダメージエフェクト演出を追加しましょう!!

 

 

次回

第15話 「ダメージエフェクトの演出」

 - ,

        

Comment

  1. さとよし より:

    いつもお世話になっております。
    HelloWorldScene.h の追加部分のコード、1行目にタイポがあります。
    std::maになっています。正しくは、std::map ですね。

    HelloWorldScene.cpp HelloWorld::init
    の敵パートの追加部分のハイライトが、66〜76行目となっていますが、
    実際の追加部分は70〜80行目だと思います。

    よろしくご確認ください。

  2. さとよし より:

    もう1点。

    HelloWorldScene.cpp HelloWorld::update
    の106・107行目の、敵のターンに移行する部分が、
    ハイライトになっていません。

    • aki より:

      ご連絡ありがとうございます。

      ご指摘の通りですので、誤字およびハイライト部分を修正しておきました。

Message

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

  関連記事

シミュレータ
ゲーム制作 Cocos2d-x関連 第4回 「画面タッチに反応して画像を出そう」

画面を眺めててもゲームにはならないよ。画面タッチしたいんだけど!   …

ゲーム制作 Prain プレイン Cocos2d-x
ゲーム制作 Cocos2d-x関連 第17回 「タイトル画面の作成と画面遷移」

  この回ではタイトル画面と画面遷移を実装していきます。画面遷移が実装 …

cocos2dx
Cocos2d-x カスタムイベントを削除するタイミング

こんにちは、akiです。 この記事ではカスタムイベントを削除するタイミングについ …

エフェクトサムネ
ゲーム制作 Cocos2d-x関連 第12回 「ボールヒットエフェクトの表示」

派手なヒットエフェクトが欲しい!   この回では、派手では無いですが、 …

ゲーム制作 Cocos2d-x関連 最終回 「勝利演出、敗北演出の追加」

勝利演出、敗北演出を実装していきます。今回で最終回です!   &nbs …