SelectiveScrollとは株式会社ONE-UPのエンジニアさんが作ったScrollViewです。
こちらの記事になります     → こちら 
githubにも公開されております → github

どのようなものか説明する文章を引用させてもらうと、
CCScrollViewは動きがかなりチープなので、もっとiOSライクに気持ちよくスクロールできるScrollView
となります。


SelectiveScrollを使えばパズドラのようなモンスターBOXの画面も簡単に作ることができますし、UITableViewみたいな触り心地になるのでかなり便利だと思います。

ただ、使い方が難しい印象を受けました。
というのも、自分の使いやすいような設定をするためにはうまく調整する必要があるためです。

今回はプロジェクトへの追加からサンプルの起動までは省きます。
重要な部分は以下となります。

for (int i = 0; i < count; i++) {
        CCSize size = CCSizeMake(WINSIZE.width / count * 0.7, WINSIZE.height * 0.7);
        SelectiveScroll* scroll = SelectiveScroll::create();
        scroll->setPosition(CCSizeMake(WINSIZE.width / (count + 1) * (i + 1), WINCENTER.y));
        scroll->setBoundingEffectKind((BoundingEffect)i);
        scroll->setContentSize(size);
        scroll->setDelegate(this);
        scroll->clipToBounds(true);
        scroll->retain();
        
        float lastY = 0.0;
        int rowCount = 50;
        for (int ii = 0; ii < rowCount; ii++) {
            CCLabelTTF* label = CCLabelTTF::create("", "Helvetica", 44);
            
            // position
            float margin = 100;
            float y = margin + (label->getScaleY() + margin) * ii;
            label->setPosition(CCPointMake(size.width * 0.5, y));
            scroll->getScrollLayer()->addChild(label);
            
            CCPoint p = label->getPosition();
            string title = to_string(rowCount - ii) + " (" + to_string((int)p.y) + ")";
            label->setString(title.c_str());
            
            lastY = p.y + margin;
        }
        scroll->setScrollSize(CCSizeMake(size.width, lastY));
        scroll->scrollToTop();
        this->addChild(scroll);
}


実行した画面をもとに見ていきます。

selectivescroll



// position
float margin = 100;
float y = margin + (label->getScaleY() + margin) * ii;
label->setPosition(CCPointMake(size.width * 0.5, y));
scroll->getScrollLayer()->addChild(label);


このコードを見ると、marginとyによって貼り付けられているラベルの座標が指定されていることが分かります。yの値はforが回るたびに増加していきますので、どんどんラベルの座標は上昇していきます。ですので、ラベルは一番下から(50から)貼り付けられていることになります。

そして、最後に

lastY = p.y + margin;


とあります。これは以下の画像を見ると分かると思います。

selectivescroll解説



という仕掛けになっているので、lastYの行は一番最後に一度だけ実行すればいいのです。
仕組みが分かったのでいろいろカスタマイズすると以下のようなものも作れました。

popila


しっかりぬるぬる動いてくれるので助かりました。
ただ、メモリに注意してみるとどうやらリークしているようでした。

scroll->retain();

この部分っているのかなーと思い削除してみると、メモリリークしなくなりました。
また、動作も変わりませんでした。

前回の記事からかなり日が空いてしまいましたが、続きみたいなものを書いておきます。

今回は、「case中に数行コードがあり、caseが大量に必要な場合」という事で、
いろいろ考えてみましたが、あまりいいアイデアが思いつかなかったですね。。。
とはいえ、caseが50個も書かれているswitchはさすがに引くかと思うので、これを減らして見やすくするという方法を日本を例にやってみます。

日本には都道府県が全部で47個あります。
そこで、各県毎に処理を変えたいという場合を考えます。
手っ取り早く書くと下記のようになりますね。


switch (currentPrefecture)
{
  case okinawa:
   処理1
   break;
  case fukuoka:
   処理2
   break;
  case saga:
   処理3
   break;
  ・・・
  case hokkaido:
   処理47
   break;
}


caseが47個もあると見難い?、間違えが見つけにくい?、シンプルじゃない?気がする
ので、
せめて10個以下にしたいと思うわけです(私は)。(caseの数を減らす方法があればご教授頂きたいです

前回の記事とやろうとすることは変わらないのですが、
まずcontextを作ります。


#include "cocos2d.h"
#include "TagConfig.h"

USING_NS_CC;

class PrefectureContext : public CCNode
{
public:
  virtual void getNearPrefecture(kTagPrefecture currentPrefecture) = 0;
};


次に、ステートによる切り替え処理を作ります。
caseを分けるために日本の地方を使っていきます。


#include "cocos2d.h"
#include "PrefectureContext.h"
#include "TagConfig.h"

USING_NS_CC;
class PrefectureState
{
private:
  PrefectureContext* contextState;
public:
  void setOkinawa();
  void setKyusyu();
  void setShikoku();
  void setChugoku();
  void setKinki();
  void setChubu();
  void setKanto();
  void setTohoku();
  void setHokkaido();
  static PrefectureState* getCurrentRegionState(kTagPrefecture currentPrefecture);
  void getNearPrefecture(kTagPrefecture currentPrefecture);
};

class Hokkaido : public PrefectureContext
{
    void getNearPrefecture(kTagPrefecture currentPrefecture);
};

class Tohoku : public PrefectureContext
{
    void getNearPrefecture(kTagPrefecture currentPrefecture);
};

class Kanto : public PrefectureContext
{
    void getNearPrefecture(kTagPrefecture currentPrefecture);
};

class Chubu : public PrefectureContext
{
    void getNearPrefecture(kTagPrefecture currentPrefecture);
};

class Kinki : public PrefectureContext
{
    void getNearPrefecture(kTagPrefecture currentPrefecture);
};

class Chugoku : public PrefectureContext
{
    void getNearPrefecture(kTagPrefecture currentPrefecture);
};

class Shikoku : public PrefectureContext
{
    void getNearPrefecture(kTagPrefecture currentPrefecture);
};

class Kyushu : public PrefectureContext
{
    void getNearPrefecture(kTagPrefecture currentPrefecture);
};

class Okinawa : public PrefectureContext
{
    void getNearPrefecture(kTagPrefecture currentPrefecture);
};



※PrefectureState.cppのシンタックスハイライター設定に挫折したので黒字で勘弁

#include "PrefectureState.h"

void PrefectureState::setHokkaido()
{
    this->contextState = new Hokkaido();
}

void PrefectureState::setTohoku()
{
    this->contextState = new Tohoku();
}

void PrefectureState::setKanto()
{
    this->contextState = new Kanto();
}

void PrefectureState::setChubu()
{
    this->contextState = new Chubu();
}

void PrefectureState::setKinki()
{
    this->contextState = new Kinki();
}

void PrefectureState::setChugoku()
{
    this->contextState = new Chugoku();
}

void PrefectureState::setShikoku()
{
    this->contextState = new Shikoku();
}

void PrefectureState::setKyusyu()
{
    this->contextState = new Kyushu();
}

void PrefectureState::setOkinawa()
{
    this->contextState = new Okinawa();
}

void PrefectureState::getNearPrefecture(kTagPrefecture currentPrefecture)
{
   this->contextState->getNearPrefecture(currentPrefecture);
}

void Hokkaido::getNearPrefecture(kTagPrefecture currentPrefecture)
{
  処理1
}

void Tohoku::getNearPrefecture(kTagPrefecture currentPrefecture)
{
  switch (currentPrefecture)
  {
    case aomori:
     処理2
     break;
    case Iwate:
     処理3
     break;
    ・・・
  }
}

void Kanto::getNearPrefecture(kTagPrefecture currentPrefecture)
{
  switch (currentPrefecture)
  {
    case ibaraki:
     処理8
     break;
    case Tochigi:
     処理9
     break;
    ・・・
  }
}

void Chubu::getNearPrefecture(kTagPrefecture currentPrefecture)
{
  switch (currentPrefecture)
  {
    case niigata:
     処理15
     break;
    case toyama:
     処理16
     break;
    ・・・
  }
}

void Kinki::getNearPrefecture(kTagPrefecture currentPrefecture)
{
   switch (currentPrefecture)
  {
    case mie:
     処理24
     break;
    case siga:
     処理25
     break;
    ・・・
  }
}

void Chugoku::getNearPrefecture(kTagPrefecture currentPrefecture)
{
   switch (currentPrefecture)
  {
    case tottori:
     処理31
     break;
    case simane:
     処理32
     break;
    ・・・
  }
}

void Shikoku::getNearPrefecture(kTagPrefecture currentPrefecture)
{
  switch (currentPrefecture)
  {
    case tokushima:
     処理36
     break;
    case kagawa:
     処理37
     break;
    ・・・
  }
}

void Kyushu::getNearPrefecture(kTagPrefecture currentPrefecture)
{
     switch (currentPrefecture)
     {
         case kTagPrefecture_Fukuoka:
    処理40
             break;
         case kTagPrefecture_Saga:
     処理41
             break;
   ・・・
        }
}

void Okinawa::getNearPrefecture(kTagPrefecture currentPrefecture)
{
  処理47
}



実行してみる

PrefectureState* state = PrefectureState::getCurrentRegionState(currentPrefecture); // 現在の県が属する地方のstateを取得
state->getNearPrefecture(currentPrefecture);

実行はこれだけ


という感じで終わりになります。
結果的にcaseの数は減らないのですが見やすい気はします。(゚Д゚)
ゲームを作っているとswitchを使いたくなると思います。私はswitchを使うのは処理が少ない場合(一行とか二行とか)に限定しています。もし、case中に何行も書くことになってしまったり、またはcaseを何十個も書こうとしている状況はできるだけ回避した方がいいと思われます。

例えば以下のような例があります。

・プレイヤーが選択できる行動
 →1.家に帰る
 →2.店に行く
 →3.バトルする

・さらに1,2,3の行動をした時間による文章変更機能
例1:店に行く時間が6時だったとき
例2:バトルする時間が12時だったとき

”プレイヤーが選択できる行動”だけであれば素直にswitch-caseを使ってもいいと思います。ところが、その行動をした時間によって何かが変わるといった場合や、他の機能をつけたいといった場合は管理が面倒になります。


switch (arc4random()%3)
{
  case GO_HOME:
    switch (arc4random()%3)
    {
      case SIX_OCLOCK:
       CCLog("朝ごはん");
       break;
      case TWELVE_OCLOCK:
       CCLog("昼ごはん");
       break;
      case EIGHTEEN_OCLOCK:
       CCLog("夜ごはん");
       break;
      case default:
       break;
    }
   break;
  case GO_SHOP:
   ・・・
   break;
  case GO_BATTLE:
   ・・・
   break;
  default:
   break;
}

この書き方だと新しく「case Go_***」を追加するとさらにswitch-caseが長くなってしまいます。もちろんその中の「case ***_OCLOCK」についても同様です。
従って「case」の中身をクラス化してアクセスすることにします。
PlayerContextState* state = new PlayerContextState();
switch (arc4random()%3)
{
  case GO_HOME:
   state->setContextState(new GoHome());
   break;
  case GO_SHOP:
   state->setContextState(new GoShop());
   break;
  case GO_BATTLE:
   state->setContextState(new GoBattle());
   break;
  default:
   ・・・
   break;
}
state->doSixOclock();

上記のように書きなおすとスッキリしたように見えると思います。
何をしているのかというと、例えば家に帰る時(case GO_HOME)は「GoHome」クラスのインスタンスを実行するためにセットしています。これにより、「doSixOclock」を実行するだけで家に帰る(6時の場合の動作:CCLog("朝ごはん"))処理を行うことができます。

では、それぞれ作っていきます。
#include <iostream>
#include "State.h"

class PlayerContextState
{
private:
 State* contextState;
public:
 PlayerContextState();
 void setContextState(State* state);
 void doSixOclock();
 void doTwelveOclock();
 void doEighteenOclock();
};

#include "PlayerContextState.h"
#include "GoHome.h"

PlayerContextState
{
 this->contextState = new GoHome();
}

void PlayerContextState::setContextState(State *state)
{
 this->contextState = state;
}

void PlayerContextState::doSixOclock()
{
 this->contextState->sixOclock();
}

void PlayerContextState::doTwelveOclock()
{
 this->contextState->twelveOclock();
}

void PlayerContextState::doEighteenOclock()
{
 this->contextState->eighteenOclock();
}

#include <iostream>

class State
{
public:
 virtual ~State(){}
 virtual void sixOclock() = 0;
 virtual void twelveOclock() = 0;
 virtual void eighteenOclock() = 0;
};

以上でコアが完成しました。あとは実行したい処理を書いていくだけです。

#include <iostream>
#include "State.h"
#include "cocos2d.h"

class GoHome : public State
{
public:
 void sixOclock();
 void twelveOclock();
 void eighteenOclock();
};

#include "GoHome.h"

USING_NS_CC;

void GoHome::sixOclock()
{
 CCLog("朝ごはん");
}

void GoHome::twelveOclock()
{
 CCLog("昼ごはん");
}

void GoHome::eighteenOclock()
{
 CCLog("夜ごはん");
}

「GoHome」クラス以外も同様に追加することができるので、拡張性に優れていると思います。 GoHome.h、GoHome.cppを作成していますが、必ずしも作成する必要はありません。個人的にファイルを分けた方が見やすいのでそのようにしています。

今回は、2重にswitch-caseがある例を試してみました。
次回は、case中に数行コードがあり、caseが大量に必要な場合を試してみたいと思います。