質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
COCOS2D-X

COCOS2D-Xは、 2Dゲームを手軽に開発できるフレームワークのことです。 iPhone(iOS)向け、Android等に対応しており、 実質ワンソースで開発が可能です。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

1回答

3221閲覧

Cocos2d-x addChild()でセグメンテーション違反になる

yama_da

総合スコア73

COCOS2D-X

COCOS2D-Xは、 2Dゲームを手軽に開発できるフレームワークのことです。 iPhone(iOS)向け、Android等に対応しており、 実質ワンソースで開発が可能です。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2017/01/09 16:42

編集2017/01/09 16:49

こんばんは

###前提・実現したいこと
今「cocos2d-xではじめるスマートフォンゲーム開発」という本を見ながらcocos2d-xを勉強中なのですが、ここに書いてあるサンプルコードをそのまま入力したのにも関わらずセグメンテーション違反でコアダンプしてしまいます。gdbでcoreファイルを見てみたのですが、イマイチ原因が分かりません。どなたかご教授ください。。。

###発生している問題・エラーメッセージ
以下、gdbでcoreファイルを見た時の出力です。

(gdb) bt #0 0x0000000000000000 in ?? () #1 0x0000000000866e7b in cocos2d::Node::addChild (this=0x359fac0, child=0x36133b0) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/2d/CCNode.cpp:989 #2 0x00000000008152fe in MainScene::<lambda()>::operator()(void) const (__closure=0x35d6710) at /home/daichi/dev/cocos.projects/KawazCatch/Classes/MainScene.cpp:407 #3 0x00000000008161f4 in std::_Function_handler<void(), MainScene::addReadyLabel()::<lambda()> >::_M_invoke(const std::_Any_data &) (__functor=...) at /usr/include/c++/4.9/functional:2039 #4 0x0000000000824240 in std::function<void ()>::operator()() const (this=0x360bd68) at /usr/include/c++/4.9/functional:2440 #5 0x0000000000822b9f in cocos2d::CallFunc::execute (this=0x360bd00) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/2d/CCActionInstant.cpp:414 #6 0x0000000000822b04 in cocos2d::CallFunc::update (this=0x360bd00) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/2d/CCActionInstant.cpp:403 #7 0x000000000082553c in cocos2d::Sequence::update (this=0x3614e40, t=1) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/2d/CCActionInterval.cpp:411 #8 0x0000000000825388 in cocos2d::Sequence::update (this=0x3614ec0, t=1) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/2d/CCActionInterval.cpp:385 #9 0x0000000000824923 in cocos2d::ActionInterval::step (this=0x3614ec0, dt=0.0157120004) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/2d/CCActionInterval.cpp:142 #10 0x0000000000a0195c in cocos2d::ActionManager::update (this=0x2a68cd0, dt=0.0157120004) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/2d/CCActionManager.cpp:449 #11 0x00000000009a5543 in void cocos2d::Scheduler::scheduleUpdate<cocos2d::ActionManager>(cocos2d::ActionManager*, int, bool)::{lambda(float)#1}::operator()(float) const (__closure=0x2a66350, dt=0.0157120004) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/base/CCScheduler.h:284 #12 0x00000000009a6e9a in std::_Function_handler<void (float), void cocos2d::Scheduler::scheduleUpdate<cocos2d::ActionManager>(cocos2d::ActionManager*, int, bool)::{lambda(float)#1}>::_M_invoke(std::_Any_data const&, float) (__functor=..., __args#0=0.0157120004) at /usr/include/c++/4.9/functional:2039 #13 0x00000000008302e6 in std::function<void (float)>::operator()(float) const (this=0x2a68d30, __args#0=0.0157120004) at /usr/include/c++/4.9/functional:2440 #14 0x00000000009d190c in cocos2d::Scheduler::update (this=0x2a68790, dt=0.0157120004) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/base/CCScheduler.cpp:850 #15 0x00000000009a135b in cocos2d::Director::drawScene (this=0x2a65500) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/base/CCDirector.cpp:277 #16 0x00000000009a4ef8 in cocos2d::Director::mainLoop (this=0x2a65500) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/base/CCDirector.cpp:1443 #17 0x000000000090efc3 in cocos2d::Application::run (this=0x7ffd78757d00) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/platform/linux/CCApplication-linux.cpp:86 #18 0x000000000081bbdb in main (argc=1, argv=0x7ffd78757e18) at /home/daichi/dev/cocos.projects/KawazCatch/proj.linux/main.cpp:14 (gdb) frame 1 #1 0x0000000000866e7b in cocos2d::Node::addChild (this=0x359fac0, child=0x36133b0) at /home/daichi/dev/cocos.projects/KawazCatch/cocos2d/cocos/2d/CCNode.cpp:989 989 this->addChild(child, child->getLocalZOrder(), child->_name); (gdb) frame 2 #2 0x00000000008152fe in MainScene::<lambda()>::operator()(void) const (__closure=0x35d6710) at /home/daichi/dev/cocos.projects/KawazCatch/Classes/MainScene.cpp:407 407 this->addChild(start); (gdb) p start $3 = (cocos2d::Sprite * const) 0x36133b0 (gdb) p this $4 = (MainScene * const) 0x359fac0 (gdb)

###該当のソースコード
以下、サンプルコード内の該当する関数です。サンプルコードは、落ちてくるフルーツをキャッチしてスコアを競うというゲームです。

c++

1void MainScene::addReadyLabel() 2{ 3 auto winSize = Director::getInstance()->getWinSize(); 4 auto center = Vec2(winSize.width / 2.0,winSize.height / 2.0); 5 //Readyの文字を定義する 6 auto ready = Sprite::create("ready.png"); 7 ready->setScale(0); //最初の大きさを0%にしておく 8 ready->setPosition(center); 9 this->addChild(ready); 10 11 //STARTの文字を定義する 12 auto start = Sprite::create("start.png"); 13 start->runAction(Sequence::create( 14 CCSpawn::create( 15 EaseIn::create(ScaleTo::create(0.5,0.5),0.5), 16 FadeOut::create(0.5), 17 NULL), 18 RemoveSelf::create(), 19 NULL)); 20 21 start->setPosition(center); 22 23 //READYにアニメーションを追加する 24 ready->runAction(Sequence::create( 25 ScaleTo::create(0.25,1), 26 DelayTime::create(1.0), 27 CallFunc::create([this,start] { 28 //STARTのラベルを追加する 29 this->addChild(start); 30 //ゲームの状態をPLAYINGに切り替える 31 _state = GameState::PLAYING; 32 33 CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("start.mp3"); 34 }), 35 RemoveSelf::create(), 36 NULL)); 37} 38

addReadyLabel()関数は、ゲームの開始時に「READY」と「START」の画像を順にアニメーションを行いながら表示する関数です。まず「READY」を表すSpriteをアニメーションを行いながら表示して、アニメーションが終わるとCallFuncのラムダのなかで「START」を表すSpriteをSceneに追加しています。
CallFuncのラムダ内のthis->addChild(start);の行をコメントアウトするとエラーは発生しないので、この行でエラーが発生しているのは分かるのですが、原因がわかりません。

###試したこと
初めラムダ内のthisかstartがヌルになっているんじゃないかと思ったのですが、gdbのpコマンドで確認してみるとどちらもヌルではありませんでした。もしかしたらstartにセットしたアニメーション内に原因が?と思い、一応start->runAction()の部分をコメントアウトして試して見たのですが、やはりaddChild(start)の部分でエラーになり、お手上げ状態です。どこが原因でしょうか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

私も同じ本を持っていてこれで勉強しました。
実はこのコードには根本的な誤りがあります。

C++のポインタの特性について

C++のポインタは参照先のオブジェクトのアドレスを保持しているだけで実体ではないため、
例えば、startの参照先であるSpriteが破棄されたとしても、startの値は変化しないのでNULLになりません。
なので、ポインタがNULLでないからといってオブジェクトが存在するという事にはなりません。

cocos2d-xのリファレンスカウンタについて

cocos2d-xのオブジェクト(Nodeを継承したクラス)には、リファレンスカウンタがあり、
メインループごとにオブジェクトを自動で破棄する仕組みがあります。

本書のp70-74にも記載がありますが、こちらの記事が非常に分かりやすいのでご参照ください。
cocos2d-xのリファレンスカウンタを理解してクラッシュやメモリリークを防ぐ | たそがれブランチ

ざっくりまとめると、

  • SpriteなどのNodeを継承したオブジェクトを作成すると、リファレンスカウンタは1の状態で作成される
  • addChildするか、自分でretain()を呼び出すと、リファレンスカウンタが+1される
  • リファレンスカウンタが1のオブジェクトは、メインループで自動的に破棄される
  • この仕組みがあるため、cocos2d-xではポインタがNULLでなくてもオブジェクトが既に存在しないという事が起こる

このコードの誤りについて

上記の特徴から、リファレンスカウンタが1のオブジェクトは自動的に破棄される。
明示的にretainやaddChildされていないオブジェクトは、こちらの意図しないところで破棄されてしまう。
という事が分かると思います。

もう一度、addReadyLabelのコードを見てみるとstartがaddChildされるのは、
ready->runAction()のCallFuncですが、ScaleToの0.25秒+DelayTimeの1秒で1.25秒後から開始されます。
cocos2d-xのメインループはデフォルトでは60FPSに設定されているので、は1/60秒=0.016秒ごとにメインループが実行されます。
つまり、0.016秒ごとにオブジェクトの破棄判定がされているという事です。
となると、startがaddChildされる前に、オブジェクトの自動破棄が行われる可能性があると考える事ができます。

どのように修正すべきか?

オブジェクトの作成とaddChildの時間がずれている事が問題です。
このコードでは、オブジェクトを作ってすぐにaddChildすればいいだけなので、
ready->runActionのCallFunc内でstartの作成も行えば良いはずです。

cpp

1void MainScene::addReadyLabel() 2{ 3 auto winSize = Director::getInstance()->getWinSize(); 4 auto center = Vec2(winSize.width / 2.0,winSize.height / 2.0); 5 //Readyの文字を定義する 6 auto ready = Sprite::create("ready.png"); 7 ready->setScale(0); //最初の大きさを0%にしておく 8 ready->setPosition(center); 9 this->addChild(ready); 10 11 //READYにアニメーションを追加する 12 ready->runAction(Sequence::create( 13 ScaleTo::create(0.25,1), 14 DelayTime::create(1.0), 15 CallFunc::create([this, center] { 16 17 //STARTの文字を定義する 18 auto start = Sprite::create("start.png"); 19 start->runAction(Sequence::create( 20 CCSpawn::create( 21 EaseIn::create(ScaleTo::create(0.5,0.5),0.5), 22 FadeOut::create(0.5), 23 NULL), 24 RemoveSelf::create(), 25 NULL)); 26 27 start->setPosition(center); 28 29 //STARTのラベルを追加する 30 this->addChild(start); 31 32 //ゲームの状態をPLAYINGに切り替える 33 _state = GameState::PLAYING; 34 35 CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("start.mp3"); 36 }), 37 RemoveSelf::create(), 38 NULL)); 39}

投稿2017/01/10 01:27

mingos

総合スコア4025

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yama_da

2017/01/10 06:24

詳しい回答ありがとうございます、助かりました!無事実行できるようになりました。 問題は解決したので良いのですが、まだ疑問が残っていて、 >つまり、0.016秒ごとにオブジェクトの破棄判定がされているという事です。 >となると、startがaddChildされる前に、オブジェクトの自動破棄が行われる可能性があると考える事ができ>ます。 とありますが、元のコードではCallFuncのラムダの中でstartをキャプチャしていると思うのですが、このキャプチャはオブジェクトの実体ではなくオブジェクトのポインタをコピーしているということですか?もしそうなら、キャプチャでポインタをコピーしたところでaddChild()を実行する頃にはもうすでに実体はautoreleaseによって破棄されているので、コピーしたポインタはヌルポインタになってしまっていた、ということでしょうか?
mingos

2017/01/10 06:31

autoは便利なんですが、自分が何をやっているのか分からなくなるので微妙な感じはありますが、 auto start = Sprite::create("start.png"); これは、 Sprite* start = Sprite::create("start.png"); という事です。 従って、startは単なるポインタであり、CallFuncのラムダでキャプチャで渡しているのはポインタ=アドレスです。 > もしそうなら、キャプチャでポインタをコピーしたところでaddChild()を実行する頃にはもうすでに実体はautoreleaseによって破棄されているので、コピーしたポインタはヌルポインタになってしまっていた、ということでしょうか? はい、私の理解ではそうです。 C++は参照先のオブジェクトが破棄されたらポインタを自動でNULLにしてくれないので、 あれ?ポインタがNULLになってないのにエラーで落ちたぞ?という事が頻発します。
yama_da

2017/01/10 07:26

>autoは便利なんですが、自分が何をやっているのか分からなくなるので微妙な感じはありますが、 本当にそうですね、自分もstartがポインタであることを完全に忘れていました。 納得できました、ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問