ちょっと待って、パズル要素はどこなの???
・・・ですよね・・・「パズル&ストライク」ですもんね・・・
それではパズル要素の仕様を決めていきましょう。
仕様の決定
苦し紛れですが、パズル要素はこんな感じにしましょう。
仕様
指定色のドロップにボールをヒットさせなければ、敵にダメージを与えられない「ドロップマッチングルール」を定める。
・指定色は攻撃前にランダムで指定され、画面左下に表示される。
・ドロップマッチングルールはある一つの色のみ指定される。
今回はドロップマッチングルールを一つの色だけとしましたが、例えば、
・ドロップマッチングの順番(例えば青→黄の順番でヒットさせる)
・複数のドロップマッチング(順番を問わず、青、黄にヒットさせる)
などが考えられます。
ですが、あまりルールを複雑にしすぎてもゲームが面白くなくなってしまうので注意が必要です。
以下が完成イメージです。
左下のドロップマッチングルールに表示された色のドロップに、
・ボールがヒットした場合は、敵にダメージを与えて敵のターンに移行。
・ボールがヒットしていない場合は、敵へのダメージ及び演出をスキップして敵のターンに移行。
しています。
プログラムの実装
HelloWorldScene.h
// ドロップルール関連 bool _didCreateDropRule = false; // ターンの最初にドロップルールを生成するためのフラグ bool _didMatchDropRule = false; // ドロップルールに適合したかどうかを確認するためのフラグ std::map<COLOR, bool> _dropRuleMap; // ドロップルールを保持するためのマップ void createDropRule(); // ドロップルールをランダムで生成する
HelloWorldScene.cpp HelloWorld::update
void HelloWorld::update(float dt){ // プレイヤーのターン if(_state == TYPE_PLAYER_TURN){ // ドロップマッチングルールの生成 if(!_didCreateDropRule){ _didCreateDropRule = true; createDropRule(); } // パワーゲージの増加 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]++; // コンボカウント _comboCount++; // ドロップマッチングルール検証 std::map<COLOR, bool>::iterator rIt; for(rIt = _dropRuleMap.begin(); rIt != _dropRuleMap.end(); rIt++){ if((*rIt).first == ((BaseChara*)(*it))->_colorType && (*rIt).second == true){ _didMatchDropRule = true; } } } } // 画面端に接触した場合の跳ね返りの処理 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){ // ボールが停止してから少しの間待つ _ball->_attackWaitCount++; if(_ball->_attackWaitCount <= 30){ return; } // 以降の処理は一度しか行わない if(_ball->_doAttack){ return; }else{ _ball->_doAttack = true; } // ドロップルールをクリアしていなければ攻撃演出へ if(_didMatchDropRule){ resetParams(); _state = TYPE_ENEMY_TURN; return; } // 攻撃力計算 int attackPoint = playerAttack(); // 敵HP減算 _enemy->_hp -= attackPoint; // ボールが一度もドロップにヒットしていなかったらパラメータ類をリセットして敵のターンにする if(_comboCount == 0){ resetParams(); _state = TYPE_ENEMY_TURN; return; } // 攻撃演出 コンボカウントの数だけヒットエフェクトを表示 for(int i = 0; i < _comboCount; i++){ auto view = CallFunc::create([this](){ // ヒットエフェクトの表示 int x = arc4random()%10 * 5; int y = arc4random()%10 * 5; int rand = arc4random()%2; if(rand % 2 == 0){ x *= -1; y *= -1; } Vec2 vec = Vec2(x, y); viewHitEffect(_enemy->getPosition() + vec); // 攻撃音 CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("kick1.mp3"); }); auto delay = DelayTime::create(.08 * i); auto seq = Sequence::create(delay, view, nullptr); this->runAction(seq); // ヒットエフェクトの最後にダメージ表示 if(i == _comboCount - 1){ // ダメージ表示 Label* damage = Label::createWithTTF(std::to_string(attackPoint), "fonts/arial.ttf", 100); damage->setColor(Color3B::RED); damage->setPosition(_enemy->getPosition()); damage->setOpacity(0); this->addChild(damage,2,0); // ヒットエフェクトの最後まで待つためのdelay auto delay = DelayTime::create(.08 * i); // delay後にダメージを表示 auto add = CallFunc::create([this, damage](){ damage->setOpacity(255); }); auto seq = Sequence::create(delay, add, nullptr); // 表示したダメージを徐々に大きくしながらフェードアウトするアニメーション auto scaleTo = ScaleTo::create(1.0, 2.0); auto fadeTo = FadeTo::create(1.0, 0); auto spawn = Spawn::create(scaleTo, fadeTo, nullptr); // アニメーションが終わったらダメージ量ラベルを削除してHP表示を更新 auto remove = CallFunc::create([this, damage](){ // ダメージ量ラベル削除 damage->removeFromParent(); // 敵HP表示更新 _enemy->_hpLabel = _enemy->createHpLabel(); this->addChild(_enemy->_hpLabel, 1, 0); }); // HP表示を更新完了タイミングで勝敗判定(ゲーム終了判定) auto isPlayerWin = CallFunc::create([this](){ if(_enemy->_hp <= 0){ // 敵のHPが0以下なら勝利イベントへ _state = TYPE_WIN; }else{ // ターン交代 _state = TYPE_ENEMY_TURN; } // パラメータ類をリセットする resetParams(); }); // アニメーション及び処理をつなげて実行 auto spawn2 = Spawn::create(remove, isPlayerWin, nullptr); auto seq2 = Sequence::create(seq, spawn, spawn2, nullptr); damage->runAction(seq2); } } }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; // ヒットエフェクト表示 viewHitEffect(_ball->getPosition()); // 攻撃音 CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("kick1.mp3"); // ダメージ表示 Label* damage = Label::createWithTTF(std::to_string(attackPoint), "fonts/arial.ttf", attackPoint); damage->setColor(Color3B::RED); damage->setPosition(_ball->getPosition()); damage->setOpacity(0); this->addChild(damage,2,0); // ヒットエフェクトの最後まで待つためのdelay auto delay = DelayTime::create(.08); // delay後にダメージを表示 auto add = CallFunc::create([this, damage](){ damage->setOpacity(255); }); auto seq = Sequence::create(delay, add, nullptr); // 表示したダメージを徐々に大きくしながらフェードアウトするアニメーション auto scaleTo = ScaleTo::create(1.0, 2.0); auto fadeTo = FadeTo::create(1.0, 0); auto spawn = Spawn::create(scaleTo, fadeTo, nullptr); // アニメーションが終わったらダメージ量ラベルを削除してHP表示を更新 auto remove = CallFunc::create([this, damage](){ // ダメージ量ラベル削除 damage->removeFromParent(); // プレイヤーHP表示更新 _ball->_hpLabel = _ball->createHpLabel(); this->addChild(_ball->_hpLabel, 1, 0); }); // ダメージ表示完了タイミングで勝敗判定(ゲーム終了判定) auto isEnemyWin = CallFunc::create([this](){ if(_ball->_hp <= 0){ // プレイヤーのHPが0以下なら敗北イベントへ _state = TYPE_LOSE; }else{ // 攻撃フラグを元に戻す _enemy->_doAttack = false; // ターン交代 _state = TYPE_PLAYER_TURN; } }); // アニメーション及び処理をつなげて実行 auto spawn2 = Spawn::create(remove, isEnemyWin, nullptr); auto seq2 = Sequence::create(seq, spawn, spawn2, nullptr); damage->runAction(seq2); } } // 勝利イベント else if(_state == TYPE_WIN){ } // 敗北イベント else if(_state == TYPE_LOSE){ } }
HelloWorldScene.cpp HelloWorld::createDropRule
/* ドロップマッチングルール生成 */ void HelloWorld::createDropRule(){ std::string str; int i = arc4random_uniform(5); CCLOG("dropRule %d", i); switch (i) { case 0: _dropRuleMap[TYPE_RED] = true; str = "color_drop_red.png"; break; case 1: _dropRuleMap[TYPE_YELLOW] = true; str = "color_drop_yellow.png"; break; case 2: _dropRuleMap[TYPE_BLUE] = true; str = "color_drop_blue.png"; break; case 3: _dropRuleMap[TYPE_GREEN] = true; str = "color_drop_green.png"; break; case 4: _dropRuleMap[TYPE_PURPLE] = true; str = "color_drop_purple.png"; break; default: break; } this->removeChildByTag(10000); BaseChara* drop = BaseChara::create(str); drop->setPosition(Vec2(50,150)); drop->setScale(.5); this->addChild(drop,10000); }
HelloWorldScene.cpp HelloWorld::resetParams
void HelloWorld::resetParams(){ // 減速していたスピードを元に戻す _ball->_speed = 50; // ボール移動中フラグを元に戻す _ball->_isBallMoving = false; // カウンタのリセット _ball->_speedDownCount = 0; _ball->_attackWaitCount = 0; // コンボカウントのリセット _comboCount = 0; // 攻撃フラグのリセット _ball->_doAttack = false; // ドロップヒットカウントマップのクリア _dropHitCountMap.clear(); // ドロップルールクリエイトフラグのリセット _didCreateDropRule = false; // ドロップルールクリアフラグのリセット _didMatchDropRule = false; // ドロップルールマップのクリア _dropRuleMap.clear(); }
プログラム解説
ドロップマッチングルール生成
「arc4random_uniform」で0~4の乱数を生成し、「switch」で生成する画像の名前を取得し画像を生成します。また、ドロップの色に応じて「_dropRuleMap」にフラグを立てることでルールも同時に生成しています。この処理はプレイヤーのターンで最初に一回のみ行われます。
ドロップッチングルール検証
ドロップルールで生成したドロップルール(マップ)をループさせ、ボールがヒットしたドロップが存在するかどうかを検証します。ループ文中の(*rIt).firstはドロップカラーの種類、(rIt*).secondはフラグを表しています。
ドロップマッチングルール検証で、ボールがヒットしたドロップが存在しなかった場合
ドロップマッチングルール検証で、ボールがヒットしたドロップが存在しなかった場合は、全てのパラメータ類をリセットし、敵のターンを設定して以降の処理をスキップします。「return」を記述することで、以降の処理をスキップします。その後、次フレームからupdateの最初から実行されるのがポイントです。
シミュレータの起動
それではいつものようにシミュレータを起動して動作確認しましょう。うまくいきましたでしょうか?
次は一旦バトルから離れて、タイトル画面を作成し、バトル画面に遷移するところまでを実装します。
次回
もう一息で完成です!!