Prainブログ

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

*

ゲーム制作 Cocos2d-x関連 第16回 「パズル要素を実装する」

      2016/07/14

puzzle

ちょっと待って、パズル要素はどこなの???

 

・・・ですよね・・・「パズル&ストライク」ですもんね・・・

 

それではパズル要素の仕様を決めていきましょう。

 

スポンサーリンク

 

 

仕様の決定

苦し紛れですが、パズル要素はこんな感じにしましょう。

 

仕様

指定色のドロップにボールをヒットさせなければ、敵にダメージを与えられない「ドロップマッチングルール」を定める。

・指定色は攻撃前にランダムで指定され、画面左下に表示される。

・ドロップマッチングルールはある一つの色のみ指定される。

 

 

今回はドロップマッチングルールを一つの色だけとしましたが、例えば、

・ドロップマッチングの順番(例えば青→黄の順番でヒットさせる)

・複数のドロップマッチング(順番を問わず、青、黄にヒットさせる)

などが考えられます。

ですが、あまりルールを複雑にしすぎてもゲームが面白くなくなってしまうので注意が必要です。

 

以下が完成イメージです。

 

16

 

左下のドロップマッチングルールに表示された色のドロップに、

・ボールがヒットした場合は、敵にダメージを与えて敵のターンに移行。

・ボールがヒットしていない場合は、敵へのダメージ及び演出をスキップして敵のターンに移行。

しています。

 

 

プログラムの実装

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の最初から実行されるのがポイントです。

 

 

シミュレータの起動

それではいつものようにシミュレータを起動して動作確認しましょう。うまくいきましたでしょうか?

 

次は一旦バトルから離れて、タイトル画面を作成し、バトル画面に遷移するところまでを実装します。

 

 

次回

第17回 「タイトル画面の作成と画面遷移」

 

もう一息で完成です!!

 - ,

        

Message

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

  関連記事

cocos2dx
Cocos2d-x サーバ側の処理とデータ送信(Httpレスポンス)

この記事は「Cocos2d-x セーブデータをサーバに送信して保存する」の続きに …

新しいファイル
ゲーム制作 Cocos2d-x関連 第8回 「より保守性のあるプログラムを目指して」

  何だこのタイトルは・・・ゲーム制作はどこに行ったの?   …

cocos2dx
Cocos2d-x Http通信の完了に同期して処理を実行する

  こんにちは、akiです。 この記事ではHttp通信処理の完了に同期 …

シミュレータ
ゲーム制作 Cocos2d-x関連 第3回 「画像の表示処理 解説」

今回はCocos2d-xにおける画像表示コードの解説です。   スポン …

動作確認画像
Cocos2d-x アプリ起動画面(スプラッシュ画像)の変更方法について

この記事では、アプリの起動画面(スプラッシュ画像)の変更のやり方について説明しま …