Prainブログ

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

*

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

      2016/07/08

pro

 

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

 

結論からいうと、この回ではゲームとしての進展はありません。ただ、後々のプログラムの可読性を上げるために、プログラムを少し修正していきます。

 

スポンサーリンク

 

 

ゲーム仕様の追加

この時点で、よりゲームらしくするために以下の仕様を後々追加したいと考えています。

 

【新たな仕様】

・ボールがヒットしたドロップの回数に比例して敵にダメージを与える。

・属性の概念を取り入れる。ボールがヒットしたドロップの属性に応じて敵へのダメージが変化する。

 

ただ、今のプログラムのままでは5色のドロップの内、どの色のドロップにボールが衝突したかを判別するのは困難です。

 

強引にプログラムしてできないことも無いですが、そんな事してもいいことはあまりないので、後々のためにここで新たにクラスやヘッダファイルを追加します。適切にクラスなどを追加することでプログラムの可読性、保守性は上がります。

 

 

それではconfig.hファイルとBaseCharaクラスを追加しましょう。

 

 

config.hファイルの追加

5色のドロップの持つ属性を追加します。

「Classes(右クリック)」->「New File」から「Header File」を選択して、「Next」を押下します。

8_sc_head1

 

続いて「Save As」のところに”config”と入れて「Create」を押下するとconfig.hファイルが生成されます。

8_sc_head2

 

 

それではconfig.hに以下の内容を記述してください。

 

config.h

#ifndef config_h
#define config_h

enum COLOR{
    TYPE_RED,
    TYPE_YELLOW,
    TYPE_BLUE,
    TYPE_GREEN,
    TYPE_PURPLE,
    TYPE_NONE,
};

#endif /* config_h */

 

・enum

C++の列挙型と呼ばれるものです。

 

 

BaseCharaクラスの追加

BaseCharaというクラスを追加します。

 

「Classes(右クリック)」->「New File」->「C++File」を選択して「Next」を押下します。

8_sc_cpp1

 

Nameに”BaseChara”と入力して「Also create a header file」にチェックが入っていることを確認して「Next」を押下、

8_sc_cpp2

 

そのままCreateを押下するとBaseCharaクラスが生成されます。

8_sc_cpp3

 

 

このクラスに先程までHeloWorldSceneクラスで使用していたdegree、speedなども持たせます。

以下の内容をBaseCharaクラスに記述してください。

 

BaseChara.hpp

#ifndef BaseChara_hpp
#define BaseChara_hpp

#include <stdio.h>
#include "config.h"

class BaseChara : public cocos2d::Sprite{
public:
 
    COLOR _colorType = TYPE_RED;

    int _hp = 0; // HP
    int _attackPoint = 0; // 攻撃力

    int _speed = 50; // ボールのスピード
    int _speedDownCount = 0; // ボールが減速するタイミングを数えるためのカウンタ
    bool _isBallMoving = false; // ボールが動いているかどうかを判断するフラグ
    float _degree = .0; // ボールの進行方向
 
 
    // クラス生成
    virtual bool init();
    static BaseChara* create(std::string imageFilePath);
 
};
#endif /* BaseChara_hpp */

 

 

BaseChara.cpp

#include "BaseChara.hpp"

bool BaseChara::init(){
    return true;
}

BaseChara* BaseChara::create(std::string imageFilePath){
 
    BaseChara* pRet = new BaseChara();
    if(pRet && pRet->initWithFile(imageFilePath) && pRet->init()){
        pRet->autorelease();
        return pRet;
    }else{
        CC_SAFE_DELETE(pRet);
        return nullptr;
    }
}

 

 

HelloWorldクラスの修正

HelloWorldScene.hの修正

今までHelloWorldクラスに持たせていた変数がなくなったので、削除します。また、BaseCharaクラスを加えたことによる修正箇所をハイライトします。

現在のHelloWorldScene.hは下記のとおりです。

 

HelloWorldScene.h

#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__

#include "cocos2d.h"
#include "BaseChara.hpp"

class HelloWorld : public cocos2d::Layer
{
public:
    static cocos2d::Scene* createScene();

    virtual bool init();

    // a selector callback
    void menuCloseCallback(cocos2d::Ref* pSender);

    // implement the "static create()" method manually
    CREATE_FUNC(HelloWorld);


    // touch
    virtual bool onTouchBegan(cocos2d::Touch* pTouch, cocos2d::Event* pEvent);
    virtual void onTouchEnded(cocos2d::Touch* pTouch, cocos2d::Event* pEvent);
    virtual void onTouchMoved(cocos2d::Touch* pTouch, cocos2d::Event* pEvent);


    // キャラクター関連
    BaseChara* _ball; // プレイヤーが操作するボール
    cocos2d::Sprite* _allow; // ボールの侵攻方向を表す矢印画像
    BaseChara* _enemy; // 敵キャラ


    // ドロップ接触、プレイヤー攻撃関連
    float getDegree(cocos2d::Vec2 o, cocos2d::Vec2 t); // 2点間の角度を取得する


    void update(float dt);


    std::list<cocos2d::Sprite*> _dropList; // フィールド上のドロップを格納する
    bool hitDetectionOval(cocos2d::Sprite*, cocos2d::Sprite*); // 円形の接触判定を行う

 
};

#endif // __HELLOWORLD_SCENE_H__

 

 

HelloWorldScene.cppの修正

続いてHelloWorldScene.cppを修正していきます。修正箇所をハイライトしています。

 

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));
    this->addChild(_ball, 1, 0);
    // ドロップ5種
    // 赤
    Sprite* dropRed = Sprite::create("color_drop_red.png");
    dropRed->setPosition(Vec2(100.0, 600.0));
    this->addChild(dropRed, 1, 1);
 
    // 黄
    Sprite* dropYellow = Sprite::create("color_drop_yellow.png");
    dropYellow->setPosition(Vec2(200, 700));
    this->addChild(dropYellow, 1, 2);
 
    // 青
    Sprite* dropBlue = Sprite::create("color_drop_blue.png");
    dropBlue->setPosition(Vec2(100, 1000));
    this->addChild(dropBlue, 1, 3);
 
    // 緑
    Sprite* dropGreen = Sprite::create("color_drop_green.png");
    dropGreen->setPosition(Vec2(400, 600));
    this->addChild(dropGreen, 1, 4);
 
    // 紫
    Sprite* dropPurple = Sprite::create("color_drop_purple.png");
    dropPurple->setPosition(Vec2(500, 900));
    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));
    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);
 
 
    scheduleUpdate();
 
 
    return true;
}


 

 

HelloWorldScene.cpp HelloWorld::onTouchBegan

bool HelloWorld::onTouchBegan(Touch* pTouch, Event* pEvent){
 
    // タップした座標を取得
    Vec2 touchPoint = convertTouchToNodeSpace(pTouch);
    // タップした座標がボール画像の中に入っているかどうかを判断
    if(_ball->boundingBox().containsPoint(touchPoint)){
        // タップした座標とボール画像の座標の二点間の角度の取得
        _ball->_degree = getDegree(_ball->getPosition(), touchPoint);
 
        // 矢印オブジェクトの角度を設定
        _allow = Sprite::create("allow.png");
        _allow->setPosition(Vec2(_ball->getPosition().x, _ball->getPosition().y));
        _allow->setRotation(_ball->_degree);
        this->addChild(_allow, 0, 7);
 
        return true;
    }
    return false;
}

 

 

HelloWorldScene.cpp HelloWorld::onTouchMoved

void HelloWorld::onTouchMoved(Touch* pTouch, Event* pEvent){
    Vec2 touchPoint = convertTouchToNodeSpace(pTouch);
 
    // 角度の再取得
    _ball->_degree = getDegree(_ball->getPosition(), touchPoint);
    // 矢印オブジェクトの角度を更新
    _allow->setRotation(_ball->_degree);
}

 

 

HellWoldScene.cpp HelloWorld::onTouchEnded

void HelloWorld::onTouchEnded(Touch* pTouch, Event* pEvent){
 
    if(_allow != nullptr){
        _allow->removeFromParent();
        _allow = nullptr;
    }
    _ball->_isBallMoving = true;
}

 

 

HelloWorldScene.cpp HelloWorld::update

void HelloWorld::update(float dt){
    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());
            }
        }
 
        // 画面端に接触した場合の跳ね返りの処理
        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));
 
    }
}

 

 

シミュレータの起動

シミュレータを起動して、前回までの内容と変わっていなければOKです。

余談ですが、このようにプログラムに改修を加えた後、元の機能を毀損していないかどうかを確かめることを、デグレードテストとかリグレッションテストとか言います。業界とか職場によって言葉や意味が微妙に違うかもしれません。

 

次回こそボールの減速処理、やっていきます!!

 

次回

第9回 「移動しているボールの減速」

 - ,

        

Message

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

  関連記事

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

ちょっと待って、パズル要素はどこなの???   ・・・ですよね・・・「 …

エフェクト
Cocos2d-x 加算合成を利用して、背景が黒い画像をアニメーションで使えるようにする方法

アニメーションを利用してエフェクト表示するときに、以下のようなエフェクト素材を目 …

Cocos2d-x iOS アプリ名(アプリのアイコン名)のローカライズ方法

こんにちは。akiです。 この記事ではCocos2d-xでのアプリ名(アプリのア …

アプリ 購入画面
SDKBoxを使ってアプリ内課金をするやり方 プログラム実装~実機テストまでの13の手順

この記事では、SDKBoxを利用したiOSアプリ内課金のプログラム実装から実機テ …

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

今回はプレイヤーのターン、敵のターンを交互に行うようにしていきます。また、それぞ …