今回はプレイヤーのターン、敵のターンを交互に行うようにしていきます。また、それぞれのターンで相手に攻撃を行い、ダメージ計算もしていきます。
ターン交代の仕様
ターンの交代のやり方は至ってシンプルで、前回追記した「_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ずつドロップにボールがヒットした場合、下図の様なイメージのマップとなるようプログラムで記述しています。
例えば、「_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)」に引っかかるので敵のターンへの交代が実現できます。
敵のターンからプレイヤーのターンに交代するときも原理は同じです。
シミュレータの起動
それではシミュレータを起動して動作確認しましょう。
ターンが移動しているのが確認できますでしょうか?・・・へ?よくわからない?いやいや、ちゃ~んとターン交代してます。下のスクリーンショットを見てください。
・・・プレイヤーのHP(初期値1000)が減っていますよね。これは敵のターンでプレイヤーが攻撃を受けた証拠です。
・・・・・・・
はい、それではターンを交代していることがわかりやすくなるように、次回はダメージエフェクト演出を追加しましょう!!
次回
いつもお世話になっております。
HelloWorldScene.h の追加部分のコード、1行目にタイポがあります。
std::maになっています。正しくは、std::map ですね。
HelloWorldScene.cpp HelloWorld::init
の敵パートの追加部分のハイライトが、66〜76行目となっていますが、
実際の追加部分は70〜80行目だと思います。
よろしくご確認ください。
もう1点。
HelloWorldScene.cpp HelloWorld::update
の106・107行目の、敵のターンに移行する部分が、
ハイライトになっていません。
ご連絡ありがとうございます。
ご指摘の通りですので、誤字およびハイライト部分を修正しておきました。