忍者ブログ
  • 2024.10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 2024.12
[PR]
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

【2024/11/23 05:21 】 |
cocos2d-xでスクリーンショット
cocos2d-xでスクリーンショットを撮影する方法。

ios標準の機能だと、こんな感じになる。

UIWindow *window = [[UIApplication sharedApplication] keyWindow];

// キャプチャ画像を描画する対象を生成します。
UIGraphicsBeginImageContextWithOptions(window.bounds.size, NO, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();

// Windowの現在の表示内容を1つずつ描画して行きます。
for (UIWindow *aWindow in [[UIApplication sharedApplication] windows]) {
[aWindow.layer renderInContext:context];
}

// 描画した内容をUIImageとして受け取ります。
UIImage *capturedImage = UIGraphicsGetImageFromCurrentImageContext();

// 描画を終了します。
UIGraphicsEndImageContext();



参考

しかし、この方法だとcocos2d-xで描画している画面は映らない。(上記コードで言うと、[aWindow.layer renderInContext:context]; の部分)



cocos2d-xの3.2以降だと、便利な機能がついている。
cocos2d::utils::captureScreen() がそれで、
以下コード例。


#include "base/ccUtils.h"
cocos2d::utils::captureScreen([&](bool succeed, const std::string &filePath){
if (succeed) {
//成功時
}
else{
//失敗時
}
}, "screenshot.png");




captureScreenの第1引数が完了時に実行するラムダ式。第2引数がファイル名。
キャプチャーが取れるとラムダ式の中が呼び出される。
ラムダ式の第2引数のfilePathは、保存したファイルの完全な絶対パスが入っている。

このパスを使って、スプライトをゲーム中に表示したり、Twitterにシェアしたりすることができる。
スプライトの場合は、通常通り
Sprite::create(filePath);



とすればよい。
ただし、そのままだと2回目以降、スクリーンショット画像が変化しない(1回目のスクリーンショットが表示されてしまう)
これは、Sprite::create()の中で自動的にテクスチャがキャッシュされているからだ。

bool Sprite::initWithFile(const std::string& filename)
{
:
Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename);
:
}




そこで、毎回テクスチャキャッシュを削除すればよい。

Director::getInstance()->getTextureCache()->removeTextureForKey(filePath);



上記コードをSprite::create()の前に実行すればOK。


TwitterやFacebookにシェアする場合、UIImageインスタンスを作成する。

UIImage* image = [UIImage imageWithContentsOfFile:[NSString stringWithUTF8String:filePath]];



アセットではなくファイルパスなので、以下の書き方ではUIImageを作成できないので注意。
UIImage* image = [UIImage imageNamed:[NSString stringWithUTF8String:filePath]];



総合すると、スクリーンショットを撮ってTwitterに投稿するのは以下のようなコードになる。


XXX.cpp

cocos2d::utils::captureScreen([&](bool succeed, const std::string &filePath){
if (succeed) {
Director::getInstance()->getTextureCache()->removeTextureForKey(filePath);
twitterShare("投稿するテキスト", filePath.c_str(), "");
}
else{
}
}, "screenshot.png");


YYY.mm

void twitterShare(const char *text, const char *imageName, const char *url, ShareCallback_t successCallback= nullptr, ShareCallback_t failureCallback= nullptr, ShareCallback_t cancelCallback= nullptr){

if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter]) {

AppController *appController = [[UIApplication sharedApplication] delegate];
RootViewController *viewController = appController.viewController;

SLComposeViewController *composeViewController = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];

if (text) {
[composeViewController setInitialText: [NSString stringWithUTF8String:text]];
}
if (url) {
[composeViewController addURL: [NSURL URLWithString:[NSString stringWithUTF8String:url]]];
}
if (imageName) {
UIImage* image = [UIImage imageNamed:[NSString stringWithUTF8String:imageName]];
if (!image) {
image = [UIImage imageWithContentsOfFile:[NSString stringWithUTF8String:imageName]];
}
if (image) {
[composeViewController addImage: image];
}
}

// 完了後のハンドラを設定する
[composeViewController setCompletionHandler:^(SLComposeViewControllerResult result) {

switch (result) {
case SLComposeViewControllerResultDone:
if (successCallback) {
successCallback();
}
break;
case SLComposeViewControllerResultCancelled:
if (cancelCallback) {
cancelCallback();
}
break;
default:
if (failureCallback) {
failureCallback();
}
break;
}
[viewController dismissViewControllerAnimated:YES completion: nil];
}];

// ポップアップを起動する
[viewController presentViewController:composeViewController animated:YES completion:nil];
}
}

PR
【2015/03/21 12:04 】 | cocos2d-x | 有り難いご意見(0)
cocos2d-xで文字送り
RPGとかノベルゲームでよくある、1文字ずつ表示する仕組み。


◯cocos2d-xの内部
文字を表示するのはLabelクラス。
これはSpriteBatchNodeの一種で、1文字を1つのSpriteで保持している。
文字数分だけ、子ノードにSpriteを持っているという構造になっている。
で、◯文字めのSpriteを返す getLetter() というメソッドがある。これを使う。


Label* label = Label::createWithTTF(text, FONT_NAME, fontSize);
label->setPosition(pos);
label->setAlignment(TextHAlignment::LEFT);
label->setVisible(false);
parent->addChild(label);

int textLength = label->getStringLength();
for (int letterIndex = 0; letterIndex < textLength; ++letterIndex) {
Sprite *sp = label->getLetter(letterIndex);
if (sp) {
sp->runAction(Sequence::create(
DelayTime::create(0.1 * letterIndex), //1文字0.1秒
CallFunc::create([sp](){
sp->setVisible(true); //ここで表示
SimpleAudioEngine::getInstance()->playEffect("se_name"); //文字表示の音を鳴らしたり
}),
NULL));
}
}



という感じ。


◯注意点
labelは使い回しできない。
一度runActionして1文字ずつ表示した後、
label->setString("another string");
して、再度runActionすると、文字の表示位置がずれまくり、そして落ちるという謎現象が起きた。
その場合、label->removeFromParent(); してから再度 Label::create() すれば正しく動いた。

◯注意点その2
int textLength = label->getStringLength();
の文字数が合っていない。textLengthの文字数だけでwhileをまわすと、たまに末尾の1〜2文字が表示されないことがあった。
どうやらcocos2d-x内部での文字の数え方に差異があるようだ。
面倒なので、textLength + 2 でループをまわしたりした。
ちゃんと調べたい



【2015/02/24 21:07 】 | cocos2d-x | 有り難いご意見(0)
cocos2dでスプライトが白くフェードアウトする
白くフェードアウトする場合、スプライトに


sprite->setOpacityModifyRGB(false);



と記述してある可能性がある。
SpriteBatchNodeを使用しているかどうかでも変わる(使用していると白くはならない)


void Sprite::updateColor(void)
{
Color4B color4( _displayedColor.r, _displayedColor.g, _displayedColor.b, _displayedOpacity );

// special opacity for premultiplied textures
if (_opacityModifyRGB)
{
color4.r *= _displayedOpacity/255.0f;
color4.g *= _displayedOpacity/255.0f;
color4.b *= _displayedOpacity/255.0f;
}

_quad.bl.colors = color4;
_quad.br.colors = color4;
_quad.tl.colors = color4;
_quad.tr.colors = color4;

// renders using batch node
if (_batchNode)
{
if (_atlasIndex != INDEX_NOT_INITIALIZED)
{
_textureAtlas->updateQuad(&_quad, _atlasIndex);
}
else
{
// no need to set it recursively
// update dirty_, don't update recursiveDirty_
setDirty(true);
}
}

// self render
// do nothing
}

void Sprite::setOpacityModifyRGB(bool modify)
{
if (_opacityModifyRGB != modify)
{
_opacityModifyRGB = modify;
updateColor();
}
}



【2015/02/24 16:13 】 | cocos2d-x | 有り難いご意見(0)
cocos2d-x 3.2 android化
Xcodeで作った物を、Eclipseで動かすための必要な作業


・コンパイルファイル設定
Android.mk

CPP_FILES := $(shell find $(LOCAL_PATH)/../../Classes -name *.cpp)
LOCAL_SRC_FILES := hellocpp/main.cpp
LOCAL_SRC_FILES += $(CPP_FILES:$(LOCAL_PATH)/%=%)
LOCAL_C_INCLUDES := $(shell find $(LOCAL_PATH)/../../Classes -type d)





cocos2d/extensionsの機能を使う場合は、以下の2行をコメントアウト。

LOCAL_WHOLE_STATIC_LIBRARIES += cocos_extension_static
$(call import-module,extensions)





・パスの変更

#include "cocos-ext.h"
#include "CCVector.h"

#include <extensions/cocos-ext.h>
#include <base/CCVector.h>




その他にも、
#include
など、Xcodeでは不要だがEclipseだと明示的にインクルードが必要なものなど出てくるので注意。



◯ライブラリプロジェクト使用時の注意


外部ライブラリが必要になる場合がある。
単体のjarファイル1つあればよいときは、libsディレクトリにjarファイルを配置するだけでよいので話が早い。
git addすれば、他の人もそのまま使えるので便利。
android-support-v4.jar や、各種広告SDKはそういう形になっている。


しかしそうではなく、Eclipse上でライブラリプロジェクトとして登録し、それへのリンクを保持する必要がある場合もある。
例えばlibcocos2dもそうだし、google-play-servicesなどもそれにあたる。



このプロジェクトをEclipseに登録する。
File -> New -> Other -> Android Project from Existing Code


Eclipseに表示される


で、これを自分のプロジェクトからリンクする。


これで完了……ではない。

ライブラリプロジェクトの中身を見ると、
libs/google-play-services.jar
というファイルがあるが、実際に使うのはこれではない。

ビルドしてみると分かるが、エラーになる。
bin/google-play-services-lib.jar
という名前のjarファイルが存在しない、というエラーになる。

このjarは、ライブラリプロジェクトをビルドすると生成される。

ので、ビルドしよう。
ライブラリプロジェクトを右クリック→Build Projectをする。

しかし、このときエラーが出ることがある。
プロジェクトのプロパティを見ると、Project Build Target が選択されていないことがある。

正しいバージョンを選択し、再度ビルド。

これで成功し、bin/ 以下にjarファイルが生成される。

これで、自分のプロジェクトもビルドに成功する。



◯cocos2d-xのライブラリ
また、
couldn't load cocos2dcpp



というエラーが出ることがある。

これは、proj.androidで
$ ./build_native.py
または
$ ./build_native.sh
を実行し、cocos2dcppを作成することで治る。

libs/armeabi/libcocos2dcpp.so
obj/local/armeabi/libcocos2dcpp.so

などが生成される。


【2015/01/20 12:59 】 | cocos2d-x | 有り難いご意見(0)
androidでフォント追加
ttfファイルをどこかからダウンロード。

PROD_DIR/proj.android/assets/fonts/XXX.ttf
に配置。

EclipseのPackageExplorerで F5 を押してrefreshすると、表示される。

Label::createで
"fonts/XXX.ttf"
を指定。ファイル名やフォント名でなく、assets以下のフルパスで指定することに注意。

これで表示されるようになる。
簡単。


なお、iOSではフォント名での指定なので、OSによって変える必要がある。
イケてないね。
【2015/01/19 12:16 】 | cocos2d-x | 有り難いご意見(0)
libcurlが悪さする


Xcodeで作っていたcocosプロジェクトを、androidで動かそうとしたときにエラー。


relocation overflow in R_ARM_THM_CALL



とか言われる。
調べてみると
参考
libcurlというライブラリが問題らしい。

コンパイルオプションをつけると解決できるとか、NDKのバージョンを戻すと治るとか治らないとか情報が交錯しているが、
cocos2d-x 3.3rc2
以降だとlibcurlの問題が解決しているらしく、
最新のcocos2d-xをダウンロードして、
cocos2d-x-3.3rc2/external/curl/
ディレクトリをまるっとコピペしたら治った。
【2015/01/16 17:57 】 | cocos2d-x | 有り難いご意見(0)
cocos2d-xの3.xで、スクロールビューとメニューを同時に使う
cocos2d-xで、スクロールビューの中身にメニューを配置したい。

簡単なことのようだが、実は問題がある。

メニューのアイテムをタッチすると、画面がスクロールできないのである。
つまり、アイテムの配置されていない箇所に指を触れてからスワイプしないと、画面がスクロールさせられないのである。
これは不便だしかっこわるい。


どうしてこうなっているのかというと…
メニュー(Menu)にtouchBeganイベントが処理されると、イベントが飲み込まれ、ScrollViewのtouchBeganイベントが処理されなくなるのである。


◯優先度について

cocos2d-xでは、EventDispatcherクラスにタッチイベントを登録する際(タッチ以外にも、マウスやキーボードイベントなども登録できる)、優先度を指定して登録する。

2種類の方法がある。

getEventDispatcher()->addEventListenerWithSceneGraphPriority(_touchListener, this);
getEventDispatcher()->addEventListenerWithFixedPriority(_touchListener, -100);






1つめの方法は、シーングラフの優先度と同じ優先度になる。
つまり、描画順序と同じく、シーン上の親から子への順序でイベントが処理される。

2つめの方法は、第2引数で指定した固定優先度で実行される。値が小さいほど優先度が高い。マイナス値も使えるので、-INT_MAXが一番優先度が高くなる。

混ぜて使う場合、

固定優先度がマイナス値のもの。-INT_MAX→-1

シーングラフ優先度が高いものから低いもの

固定優先度が0またはプラス値のもの。0→INT_MAX

という順番で処理される。
実装はEventDispatcher::dispatchEventToListeners()にある。


さて、メニューとスクロールビュー、どちらのタッチイベントが先に処理されるかというと、この優先度順序に依る訳である。

実装部分を見てみると、


void ScrollView::setTouchEnabled(bool enabled)
{
// :
//略
// :
_touchListener = EventListenerTouchOneByOne::create();
_touchListener->onTouchBegan = CC_CALLBACK_2(ScrollView::onTouchBegan, this);
_touchListener->onTouchMoved = CC_CALLBACK_2(ScrollView::onTouchMoved, this);
_touchListener->onTouchEnded = CC_CALLBACK_2(ScrollView::onTouchEnded, this);
_touchListener->onTouchCancelled = CC_CALLBACK_2(ScrollView::onTouchCancelled, this);

_eventDispatcher->addEventListenerWithSceneGraphPriority(_touchListener, this);
// :
//略
// :
}

bool Menu::initWithArray(const Vector<MenuItem*>& arrayOfItems)
{
// :
//略
// :
auto touchListener = EventListenerTouchOneByOne::create();
touchListener->setSwallowTouches(true);

touchListener->onTouchBegan = CC_CALLBACK_2(Menu::onTouchBegan, this);
touchListener->onTouchMoved = CC_CALLBACK_2(Menu::onTouchMoved, this);
touchListener->onTouchEnded = CC_CALLBACK_2(Menu::onTouchEnded, this);
touchListener->onTouchCancelled = CC_CALLBACK_2(Menu::onTouchCancelled, this);

_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, this);
// :
//略
// :
}






という風に、どちらもシーングラフの優先度通りに実行されるようになっている。

当然といえば当然なのだが、これだと困る。

どんなときでも常にメニューが優先されるからだ(スクロールビューよりメニューが手前に表示されるはずなので)。
だからメニューのアイテム上をタッチしたときは、touchBeganイベントはスクロールビューに回ってこない。

メニューをタップしたいときはこれでいいが、スクロールしたいときは困る。

「メニューをタップしてそのまま離したときは、メニューのtouchBegan,touchEndedを処理。
メニューをタップして指を滑らしたときは、スクロールビューのtouchBegan,touchMoved,touchEndedを処理」
というふうにしたいのだ。

ということは、touchBeganの時点では、スクロールビューとメニューの両方で処理を走らせる必要がある。


◯swallow設定

cocos2d-xのイベントには、スワローというメンバ変数がある。
スワローの意味は「飲み込む」。
複数のイベントリスナーが登録されているとき、1つのイベントは上記で見た優先度順に処理されていく訳だが、
その途中でswallowがtrueになっているイベントリスナーがあると、そこで処理が終了してしまう。それ以降のイベントリスナーには処理がわたらないようになっている(これが飲み込む、の意)

で、もう一度さっきの実装コードを見てみると、メニューは
touchListener->setSwallowTouches(true);
と描いている。
つまり、タッチイベントはメニューに処理されると、それ以降他のノードには渡されなくなるのだ。もちろんスクロールビューにも、である。

なるほど、だからメニュー上をタップするとスクロールしないのか。

逆に、スクロールビューではスワロー設定をしていないので、デフォルトのfalseのままである。


ということは、
「メニューより先にスクロールビューでタッチイベントを処理すればいい」
のである。
スクロールビューで処理しても、優先度が低いイベントリスナーにも処理を渡すからだ。

ということで、スクロールビューのイベント登録部分を改造する。

void ScrollView::setTouchEnabled(bool enabled)
{
// :
//略
// :
_touchListener = EventListenerTouchOneByOne::create();
_touchListener->onTouchBegan = CC_CALLBACK_2(ScrollView::onTouchBegan, this);
_touchListener->onTouchMoved = CC_CALLBACK_2(ScrollView::onTouchMoved, this);
_touchListener->onTouchEnded = CC_CALLBACK_2(ScrollView::onTouchEnded, this);
_touchListener->onTouchCancelled = CC_CALLBACK_2(ScrollView::onTouchCancelled, this);

//_eventDispatcher->addEventListenerWithSceneGraphPriority(_touchListener, this);
_eventDispatcher->addEventListenerWithFixedPriority(_touchListener, -100); //<- br=""> // :
//略
// :
}





優先度値はマイナス値ならとりあえず何でもいい。

これで試してみると、メニューをタップしてもちゃんとスクロールするようになる!!


◯メニュータップのタッチ処理を止める

しかし、ちょっと動かしてみると少し違和感がある。

確かにメニュー上をタップしてもスクロールするようになるのだが、
スクロールしたあと指を離したときに、メニューアイテムが押されるのである。
指をメニューアイテムの無い場所で離せば何も起きないが、
要するにメニューのタッチイベントがまだ生きているのである。
よく見てみれば、タップしたメニューアイテムはselected状態のスプライトが表示されたままに鳴っている。

メニューのtouchBeganで始まった処理が有効で、指を離したときにtouchEndedが発生すると、その通りメニューアイテムを選択してしまうのである。


これはちょっといやだ。
スクロールした時点で、メニューのタッチイベントは終わらせたい。

そういう処理はなかったか?
もちろんある。

onTouchCancelledだ。
これはタッチイベントが中断されたときに呼び出されるメソッドだ。
本来はタップ中に電話がかかってきてアプリが中断されたときとかに呼ばれる想定だが、今回の目的はまさにこのメソッドを呼ぶことにある。


つまり、「スクロールビューのスクロールが発生した時点で、登録されている他のすべてのイベントリスナーのonTouchCancelledを呼び出す」
ということである。
onTouchMovedやonTouchEndedは呼び出させないのだ。


さてどうするか。
そもそもonTouch***系を呼び出している部分のコードを見てみる。


void EventDispatcher::dispatchTouchEvent(EventTouch* event)
{
//略
auto oneByOneListeners = getListeners(EventListenerTouchOneByOne::LISTENER_ID);
//略
auto onTouchEvent = [&](EventListener* l) -> bool { // Return true to break
EventListenerTouchOneByOne* listener = static_cast<EventListenerTouchOneByOne*>(l);

//略

if (eventCode == EventTouch::EventCode::BEGAN)
{
if (listener->onTouchBegan)
{
isClaimed = listener->onTouchBegan(*touchesIter, event);
//略
}
}
else if (listener->_claimedTouches.size() > 0
&& ((removedIter = std::find(listener->_claimedTouches.begin(), listener->_claimedTouches.end(), *touchesIter)) != listener->_claimedTouches.end()))
{
isClaimed = true;

switch (eventCode)
{
case EventTouch::EventCode::MOVED:
if (listener->onTouchMoved)
{
listener->onTouchMoved(*touchesIter, event);
}
break;
case EventTouch::EventCode::ENDED:
if (listener->onTouchEnded)
{
listener->onTouchEnded(*touchesIter, event);
}
//略
break;
case EventTouch::EventCode::CANCELLED:
if (listener->onTouchCancelled)
{
listener->onTouchCancelled(*touchesIter, event);
}
//略
break;
//略
}
}
//略
dispatchEventToListeners(oneByOneListeners, onTouchEvent);
//略
}

void EventDispatcher::dispatchEventToListeners(EventListenerVector* listeners, const std::function<bool(EventListener*)>& onEvent)
{
//略
for (; i < listeners->getGt0Index(); ++i)
{
auto l = fixedPriorityListeners->at(i);
if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l))
{
shouldStopPropagation = true;
break;
}
}
//略
for (auto& l : *sceneGraphPriorityListeners)
{
if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l))
{
shouldStopPropagation = true;
break;
}
}
//略
for (; i < size; ++i)
{
auto l = fixedPriorityListeners->at(i);

if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l))
{
shouldStopPropagation = true;
break;
}
}
//略
}





dispatchEventToListeners()の中で、優先度順のループが3つあり(固定優先度がマイナス値のもの、シーングラフ優先度のもの、固定優先度がプラス値のもの)、順番にonEventメソッドを呼んでいる。
onEventメソッドの実体はdispatchTouchEventの中のonTouchEvent というラムダ関数で、
この中でイベントリスナーを処理し、それぞれonTouchXXX関数を呼び出している。


スクロールビューのonTouchMovedの中でスクロールが発生したと判断したときに、
それ以降のイベントリスナーについてはすべてonTouchCancelledを呼び出すようにすればいい。

ということで、EventDispatcherクラスに_touchEventForceCancelled というフラグを用意し、以下のように改造する。


CCEventDispatcher.h

class EventDispatcher : public Ref
{
void setTouchEventForceCancelled(){_touchEventForceCancelled = true;} //<-追加
bool _touchEventForceCancelled; //<- br="">};


CCEventDispatcher.cpp

void EventDispatcher::dispatchTouchEvent(EventTouch* event)
{
_touchEventForceCancelled = false; //<-追加

//略
else if (listener->_claimedTouches.size() > 0
&& ((removedIter = std::find(listener->_claimedTouches.begin(), listener->_claimedTouches.end(), *touchesIter)) != listener->_claimedTouches.end()))
{
isClaimed = true;

if (_touchEventForceCancelled){ //<-追加
eventCode = EventTouch::EventCode::CANCELLED; //<-追加
} //<-追加
switch (eventCode)
//略
}


CCScrollView.cpp

void ScrollView::onTouchMoved(Touch* touch, Event* event)
{
//略
if (std::find(_touches.begin(), _touches.end(), touch) != _touches.end())
{
if (_touches.size() == 1 && _dragging)
//略
if (_dragging)
{
//略
_eventDispatcher->setTouchEventForceCancelled(); //<-追加
}
//略
}






とする。
解説。ScrollViewのonTouchMovedで、スクロール発生と判断されたタイミングで、EventDispatcherに、「以降のタッチイベントをすべてキャンセルさせろ」というフラグを立てる。

すると、それ以降のEventDispatcher::dispatchTouchEvent()内の処理で、タッチイベントはすべてEventTouch::EventCode::CANCELLEDに強制的に書き換える、ということである。



これで、メニュー上をタップしても、指を滑らせてスクロールすると、メニューのタッチイベントはキャンセルされる。
スプライトは選択状態のものから通常状態のものに戻るし、指を離してもアイテムは選択されない。


◯スクロールビューの無効化

スクロールビューにはもう一つバグがある。それは
「不可視状態でもタッチイベントが処理され、スクロールしている」
ということである。

正確なことを言うと、
スクロールビュー自体にsetVisible(false) を呼び出していれば処理されないのだが、
スクロールビューの祖先ノードでsetVisible(false)をしていて、スクロールビュー自体の_isVisibleがtrueのままの場合、処理されてしまう。

普通、他の種類のクラス(メニューとか)だと、親が_isVisible==falseなら処理しないようになっているので(普通に考えてそれが正しい)、スクロールビューがバグっている。
スクロールビューはextension扱いだし、どうもこういう不具合がまだ多い。

これに対処するのは割と簡単で、
onTouchXXX()系のすべての関数の先頭に以下のコードを書き足せば良い。

for (Node *c = this->_parent; c != nullptr; c = c->getParent())
{
if (c->isVisible() == false)
{
return;
}
}


このコードは、Menu.cppからコピペしたものである。

【2015/01/15 17:23 】 | cocos2d-x | 有り難いご意見(0)
spriteBatchNodeを使う
cocos2d-xにあるSpriteBatchNodeクラス。

複数のスプライトを効率よく描画したいときに使う。

例えば同じ絵のキャラを大量にわらわらと表示させたいとき。

SpriteBatchNodeクラスのインスタンスのノードを作り、このクラスにテクスチャを登録する。
その子ノードに同じテクスチャを貼ったスプライトを大量に付ける。

すると、一度の描画命令で子ノードが全部描画できる。


ポイントは、SpriteBatchNodeクラスに登録したテクスチャと
「同じテクスチャ」を持つノードしか子ノードにできないこと。
テクスチャでなく、SpriteFrameでも良い。アニメーションする場合や、複数の絵を子ノードに並べたいときはSpriteFrameを使うことになるだろう。


実際に描画回数が減っているかどうかは、
画面左下に出ている。
GL calls が描画回数、
GL verts が頂点数。

GL callsがテクスチャ数より少なくなっていれば、効いている…はず

詳しいところはもう少しコードを追いかけてみる。




ちなみにLabelクラスはSpriteBatchNodeの子クラスである。



…といったところで、
v3.xでは標準でドローをまとめる機能がついたらしく、SpriteBatchNodeは非推奨になったとかなんとか。
参考
【2015/01/08 19:30 】 | cocos2d-x | 有り難いご意見(0)
cocos2d-xのクラスを継承する
cocos2d-xで、LayerとかSpriteを継承して新しいクラスを作る場合がある。

たとえば画面を作りたいなら、Layerを継承すればいい。cocosプロジェクトを作ったときもコード中にHelloWorldSceneがそうして作られているし、それを真似すればいい。

基本的にゲーム中のオブジェクトなら、Layerを継承して作るのが良さそうだ。
前の記事も参照。

その他にも、たとえばキャラクターなら、Spriteを継承して作るなど、内容に応じていろいろなクラスを継承して作るのが良い。

以下はLayerを継承して作った例

class MyClass : public cocos2d::Layer{
//create() メソッド作成
CREATE_FUNC(MyClass);

//init()作成
bool init() override{
cocos2d::Layer::init(); //親クラスのinitを呼び出す。忘れずに!
//自前の初期化処理
//
}

//その他の定義
//
};

//生成
auto obj = MyClass::create(); //参照カウンタが1になる

//破棄
obj->release(); //参照カウンタを1減らす。0になるので後で自動的に破棄される。
obj = nullptr; //これはC++の作法通りしておいたほうがよい






こうすると、コード中にnewもdeleteも現れなくなる。
cocos2d-xの作法にあった形になる。

static関数のcreate() は、CREATE_FUNCマクロによって生成されている。
CREATE_FUNCマクロを使うためには、
・引数無しコンストラクタがあること
・引数無しで戻り値がboolのinit()という関数を持っていること
・そのクラスがRefを継承していること(より正確には、autorelease()という関数を持っていること)
が必要。


#define CREATE_FUNC(__TYPE__) \
static __TYPE__* create() \
{ \
__TYPE__ *pRet = new __TYPE__(); \
if (pRet && pRet->init()) \
{ \
pRet->autorelease(); \
return pRet; \
} \
else \
{ \
delete pRet; \
pRet = NULL; \
return NULL; \
} \
}







純粋なデータ構造を扱うクラスだから、SceneもNodeも継承する必要が無い、と思っても
「メモリ管理の方法は統一しておくべき」という観点から、最低でもRefは継承するのがよい。
Refは自動でメモリ管理をしてくれるクラス。使用されなくなったらreleaseすれば、裏でPoolManagerが自動的に破棄する。


<<注意!>>

init()関数はNodeクラスに仮想関数として定義がある。
オーバーライドして、さらに親クラスのinit()を呼ぶのを忘れないように
Refクラスを直接継承する場合は、オーバーライドではなく新規の定義になる(Refクラスにinit()がないので)


Spriteクラスを継承して、親クラスのinit()の呼び出しを忘れた場合は、大抵バクる。
(glProgramStateがセットされないというアサートが出る。)





もうひとつ。
create()メソッドは継承はしないが、
元々create()メソッドを持っているクラスから継承した場合、自クラスで定義しなくても呼び出せるわけだ。
例えばSpriteクラスを継承してMySpriteクラスを作った場合

auto sprite = MySprite::create();
とすると、
Sprite::create()が呼び出されることになる。

ただしその場合、戻り値型はSprite*なので、

MySprite* sprite = MySprite::create();
はコンパイルエラーになる。
ちゃんとMySpriteでcreate()を定義する必要がある(CREATE_FUNC(MySprite); と書くだけ)


Spriteの場合、create()だけでなく
create(const std::string& filename)
create(const std::string& filename, const Rect& rect)
createWithTexture(Texture2D *texture)
createWithTexture(Texture2D *texture, const Rect& rect, bool rotated=false)
createWithSpriteFrame(SpriteFrame *spriteFrame)
createWithSpriteFrameName(const std::string& spriteFrameName)
と言う具合に多くのcreate()系のstaticメソッドがある。

本来であれば、MySpriteクラスにすべての同名関数を用意すべきである。
ただし、戻り値がMySprite*でなくSprite*なので、コンパイルエラーで気付けるといえば気付ける。
【2014/12/24 11:37 】 | cocos2d-x | 有り難いご意見(0)
cocos2d-x 3.0 の主なクラス

Ref 参照カウンタを持つ
+-- Node   親子関係を持つ
+-- Scene  Nodeとほぼ同じ。違いはアンカーポイントのデフォルト位置が画面中心になっていることだけ(Nodeは左下がデフォルト位置)
+-- Layer   タッチイベントを扱う
+-- LayerColor 背景に色を塗れる
+-- LayerGradient 背景にグラデーションできる
+-- ScrollView  スクロールビュー
+-- TableView   テーブルビュー
+-- Sprite   スプライトを表示する
+-- SpriteBatchNode  スプライトをまとめて表示するためのクラス(別記事に書く)
+-- Label  文字列表示用クラス。zyngaが作った?らしい
+-- LabelTTF  コンフィグ設定付きのラベル
【2014/12/09 19:14 】 | cocos2d-x | 有り難いご意見(0)
<<前ページ | ホーム | 次ページ>>