Prainブログ

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

*

ゲーム制作 Cocos2d-x関連 第15回 「ダメージエフェクトの演出」

      2016/08/17

おそらくここが一番大変なところですが、ゲーム完成までもう一歩です!

 

第12回 「ボールヒットエフェクトの表示」でヒットエフェクトを表示しました。その時とやり方はほぼ同じですが、エフェクトを表示するに当たり、ゲームとしてのテンポを考慮し、かつダメージエフェクトアニメーション、ダメージ表示アニメーションを連携して表示してみましょう。

 

8/17追記

複数の画像とアニメーション機能を利用する、よりリッチなエフェクト演出のやり方記事を追加しました。

この記事では、Cocos2d-xのアニメーションを利用したエフェクトの表示方法について紹介します。  また、少し実践的なアニメーションの利用方法をシリーズ記事として全5回で予定しています。 ・第1回 : アニメーションを利用した回復エフェクトの表...

 

 

 

スポンサーリンク

 

 

ダメージエフェクト表示~ダメージ表示アニメーションの仕様

 

こんな感じにしたいと思います!

15

 

以下、一応上図アニメーションの仕様を文字に起こしましたが、わかりづらいので、プログラム実装まで飛ばして頂いて構いません。

ダメージエフェクト表示

【プレイヤーの場合】

プレイヤー移動が終了したら、少し間を置いてから、ヒットエフェクトを表示する。

プレイヤーの場合、ドロップにヒットした回数分ヒットエフェクトを表示する。ヒットエフェクトの表示位置はキャラクター位置を基準として±ランダムの座標を加える。

 

【敵の場合】

敵のターン開始から、少し間をおいてからヒットエフェクトを表示する。

 

 

ダメージ表示

【プレイヤーの場合、敵の場合】

ダメージエフェクトの最後の表示のタイミングで表示する

 

 

それではやっていきましょう!!

プログラム実装

 

BaseChara.hpp

int _attackWaitCount = 0; // 攻撃するタイミングを調整するための待ちカウンター

 

 

HelloWorldScene.h

int _comboCount = 0; // ヒットした回数を記録する

void resetParams(); // 各種パラメータをリセットする

 

 

 

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]++;

                    // コンボカウント
                    _comboCount++;
                }
            }
            
            // 画面端に接触した場合の跳ね返りの処理
            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;
                    }
 	
                    // 攻撃力計算
                    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){
        
    }

}

 

・Label

cocos2dxのクラスで、文字や数字を表示したい時に使用します。

 

・DelayTime

cocos2dxのメソッドで、アクションに待ち時間を設定できます。

 

 

void HelloWorld::resetParams

void HelloWorld::resetParams(){
    // 減速していたスピードを元に戻す
    _ball->_speed = 50;
                    
    // ボール移動中フラグを元に戻す
    _ball->_isBallMoving = false;
                    
    // カウンタのリセット
    _ball->_speedDownCount = 0;
    _ball->_attackWaitCount = 0;
                         	
    // コンボカウントのリセット
    _comboCount = 0;

    // 攻撃フラグのリセット
    _ball->_doAttack = false;

    // ドロップヒットカウントマップをクリア
    _dropHitCountMap.clear();
}

 

 

HelloWorldScene.cpp HelloWorld::playerAttack 以下の文を削除

    // 計算が終わったので不要になったマップをクリアする
    _dropHitCountMap.clear();

 

 

プログラム解説

HelloWorld::update

今回の一番の肝である、プレイヤーターンのダメージエフェクト表示~ダメージ表示について説明します。

ダメージエフェクト表示~ダメージ表示の部分はHelloWorld::updateの118行目~197行目になります。このプログラム部分をタイムチャートに表すと下図の様になります。

 

time

 

ポイントはdelayに設定する時間です。for文の中でループ回数に比例してdelayの時間を長く設定しています。delayの時間がずれていくことで、複数のヒットエフェクトが一度に表示されること無く、バラバラのタイミングで表示されます。

また、for文の最後ではダメージ表示のための処理を行っています。この時、delayの時間は最後に表示するダメージエフェクトと同じとなるため、最後のダメージエフェクト表示と同時にダメージ表示アニメーションが開始します。

ダメージ表示が終わると同時に、敵のターンに交代するか、勝利イベントとなるかの判定を行います。

 

※敵のターンの場合は、プレイヤーのターンに交代するか、敗北イベントとなるかの判定を行います。この処理は敵のターン時のダメージエフェクト表示~ダメージ表示処理内に記述しています。

※タイムチャートの図ではダメージ表示のdelayの前にダメージ表示オブジェクトを生成している図を入れているため、delayがずれているように見えますが、実際にはアニメーションの表示時間およびタイミングには影響しません。

 

シミュレータの起動

それではいつものようにシミュレータを起動しましょう。いかがでしょうか。この記事の冒頭のようなアニメーションになったと思います。

 

ところで・・・今のところパズル部分の欠片も見当たらないですね・・・( ^ω^)

というわけで次回!

 

 

次回

第16回 「パズル要素を実装する」

 - ,

        

Message

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

  関連記事

Cocos2d-x モーダルレイヤーの作り方

この記事ではCocos2d-xによるモーダルレイヤー実装のやり方について説明しま …

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

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

シミュレータ
ゲーム制作 Cocos2d-x関連 第5回 「ボールを動かそう」

今回はCocos2d-xのupdateメソッドでボールを動かしていきます。 &n …

cocos2dx
ゲーム制作 Cocos2d-x関連 第1回 「ゲームを作ろう!!」

ゲーム制作をやってみたい!   誰もが一度はちらっと考えることなのでは …

cocos2dx
Cocos2d-x カスタムイベントを利用してソースコードの可読性をあげる

こんにちは、akiです。 この記事ではカスタムイベントの使い方について説明します …