拾壱 Cocos2d-xで絵をパラパラ切り替える 定周期で何かしたい たくさんの絵から探して消す たくさんの絵を消す

#目次
1 はじめに
2 絵をパラパラする
3 定周期で関数を呼び出す
4 たくさんの絵から探して消す
5 たくさんの絵を消す
6 今後の予定

#はじめに
本記事は、cocos2d-xおよびCocos Code IDEを導入し、プロジェクトを作成し絵(スプライト)を表示した人で、絵をパラパラと切り替えたい、定周期で何かしたい、たくさんの絵から探して消した人向けの、覚書です。絵をパラパラと切り替える機能はアニメーションと呼ばれます。定周期で処理を行うにはスケジューラーの機能を使います。そして、たくさんの絵から対象を探すにはテーブルと呼ばれる万能行列を使った例を示します。

#絵をパラパラ切り替える
下のコードを実行すると、パラパラとリスの絵が切り替わり、踊っているように見えます。また、タッチした位置に絵が切り替わりながら走ってきます。

```GameScene.lua

local GameScene = class("GameScene",function()
    return cc.Scene:create()
end)

function GameScene.create()
    local scene = GameScene.new()

    scene:addChild(scene:createLayer())

    return scene
end


function GameScene:ctor()
    self.visibleSize = cc.Director:getInstance():getVisibleSize()
    self.origin = cc.Director:getInstance():getVisibleOrigin()
    self.schedulerID = nil
end

function GameScene:playBgMusic()

end

function GameScene:createLayer()
    local layer = cc.Layer:create()

    -- 背景
    local sprite_background = cc.Sprite:create("farm.jpg")
    sprite_background:setPosition(self.visibleSize.width/2, self.visibleSize.height/2)
    sprite_background:setScale(1.2)
    layer:addChild(sprite_background)
    
    -- a)絵をパラパラ切り替えて動いているように見せる
    local texture = cc.Director:getInstance():getTextureCache():addImage("dog.png")
    local frame1 = cc.SpriteFrame:createWithTexture(texture, cc.rect(0, 0, 105, 95))
    local frame2 = cc.SpriteFrame:createWithTexture(texture, cc.rect(105, 0, 105, 95))
    local animation = cc.Animation:createWithSpriteFrames({frame1,frame2}, 0.3)
    local repeatAnim = cc.RepeatForever:create(cc.Animate:create(animation))
    local sprite = cc.Sprite:createWithSpriteFrame(frame2)
    sprite:setPosition(100,100)
    sprite:runAction(repeatAnim)
    layer:addChild(sprite)
    
    -- タッチ開始時に呼ばれる
    local function onTouchBegan(touch, event)
        local location = touch:getLocation()
        
        -- 絵をタッチ位置まで走らせる
        sprite:runAction(cc.MoveTo:create(0.3,location))
        
        return true
    end

    -- タッチイベントで呼ばれる関数を登録し、このレイヤーでのタッチイベント取得を有効化
    local listener = cc.EventListenerTouchOneByOne:create()
    listener:registerScriptHandler(onTouchBegan,cc.Handler.EVENT_TOUCH_BEGAN )
    local eventDispatcher = layer:getEventDispatcher()
    eventDispatcher:addEventListenerWithSceneGraphPriority(listener, layer)
    
    return layer
end

return GameScene
```

a)の部分にて、プロジェクト作成時に自動追加されるdog.pngファイル内、座標(0,0)から横105×縦95の範囲をframe1、座標(105,0)から横105×縦95の範囲をframe2として取り出し、0.3秒でframe1→frame2と切り替えるようにし、それをRepeatForeverアクション命令でずっと繰り返すようにしています。

![11-1.png](https://qiita-image-store.s3.amazonaws.com/0/90495/a2d8a860-7a87-9151-c687-32206504ff38.png)


#定周期で関数を呼び出す
次に下記コードを使って、定周期での関数の呼び出し方について説明します。b1),b2),b3),b4)により定周期で関数が呼び出されるようになります。また、b3),b4)がない場合デバッグ実行時に意図しないエラー出力が発生します。

```GameScene.lua

local GameScene = class("GameScene",function()
    return cc.Scene:create()
end)

function GameScene.create()
    local scene = GameScene.new()

    scene:addChild(scene:createLayer())

    return scene
end


function GameScene:ctor()
    self.visibleSize = cc.Director:getInstance():getVisibleSize()
    self.origin = cc.Director:getInstance():getVisibleOrigin()
    self.schedulerID = nil
end

function GameScene:playBgMusic()

end

function GameScene:createLayer()
    local layer = cc.Layer:create()

    -- 背景
    local sprite_background = cc.Sprite:create("farm.jpg")
    sprite_background:setPosition(self.visibleSize.width/2, self.visibleSize.height/2)
    sprite_background:setScale(1.2)
    layer:addChild(sprite_background)

    -- d1)ボタンの絵
    local sprite_button = cc.Sprite:create("menu1.png")
    sprite_button:setPosition(50,900)
    layer:addChild(sprite_button)
    
    -- よく使う絵のデータを読み込んでおく
    local texture = cc.Director:getInstance():getTextureCache():addImage("dog.png")

    -- c1)絵を指し示すオブジェクト(ポインタみたいなもの)をたくさん保存するためのテーブル(万能行列)
    local sprites = {}

    -- b1)定周期に呼び出される関数
    local function func(dt)

        -- a)絵をパラパラ切り替えて動いているように見せる
        local frame1 = cc.SpriteFrame:createWithTexture(texture, cc.rect(0, 0, 105, 95))
        local frame2 = cc.SpriteFrame:createWithTexture(texture, cc.rect(105, 0, 105, 95))
        local animation = cc.Animation:createWithSpriteFrames({frame1,frame2}, 0.3)
        local repeatAnim = cc.RepeatForever:create(cc.Animate:create(animation))
        local sprite = cc.Sprite:createWithSpriteFrame(frame2)
        sprite:setPosition(math.random(100,400),math.random(100,800))
        sprite:runAction(repeatAnim)
        layer:addChild(sprite)

        -- c2)今追加された絵のオブジェクトをテーブルの一番後ろに追加
        table.insert(sprites,sprite)
    end

    -- b2)定周期で呼び出す関数を登録する 関数名fun 周期1秒　また　そのIDをself.schedulerIDに保存しておく
    self.schedulerID = layer:getScheduler():scheduleScriptFunc(func, 1, false)

    -- b3)他画面への遷移時に上記IDの定周期イベントを解除
    local function onNodeEvent(event)
        if "exit" == event then
            if self.schedulerID then
                cc.Director:getInstance():getScheduler():unscheduleScriptEntry(self.schedulerID)
            end
        end
    end

    -- b4)他画面への遷移イベントを有効化
    layer:registerScriptHandler(onNodeEvent)

    -- タッチ開始時に呼ばれる
    local function onTouchBegan(touch, event)
        local location = touch:getLocation()

        -- d2)タッチ点にボタンの絵があるか確認
        if cc.rectContainsPoint(sprite_button:getBoundingBox(), location) then
            -- 絵の数だけ繰り返す
            for i=1,table.maxn(sprites) do
                -- d3)テーブルの先頭のオブジェクトを削除
                sprites[1]:removeFromParent()
                table.remove(sprites,1)
            end

            return true -- リターンする
        end
            
        -- c3)定期的に追加された絵のオブジェクトの数だけ繰り返す
        for i=1,table.maxn(sprites) do
            
            -- タッチ点に絵があるか確認
            if cc.rectContainsPoint(sprites[i]:getBoundingBox(), location) then
                -- タッチされた絵をジャンプさせて消す
                local jump = cc.JumpBy:create(0.4,cc.p(0,0),30,2)
                local remove = cc.RemoveSelf:create()
                sprites[i]:runAction(cc.Sequence:create(jump,remove))
                -- c4)対象の絵のオブジェクトをテーブルから削除
                table.remove(sprites,i)

                break  -- ループを抜ける　これがないと１タッチで複数消える
            end
        end

        return true
    end

    -- タッチイベントで呼ばれる関数を登録し、このレイヤーでのタッチイベント取得を有効化
    local listener = cc.EventListenerTouchOneByOne:create()
    listener:registerScriptHandler(onTouchBegan,cc.Handler.EVENT_TOUCH_BEGAN )
    local eventDispatcher = layer:getEventDispatcher()
    eventDispatcher:addEventListenerWithSceneGraphPriority(listener, layer)

    return layer
end

return GameScene
```

実行すると、１秒ごとに踊るリスがランダムな座標に追加されていきます。リスをタッチするとジャンプして消えます。

![11-22.png](https://qiita-image-store.s3.amazonaws.com/0/90495/e7effa63-93aa-f535-f2c1-51673d024289.png)

#たくさんの絵から探して消す
上記c1)にて、テーブル型（万能行列）変数spritesを準備しておきます。これには、スプライトを指し示すオブジェクト等をたくさん入れることができます。複数のポインタを格納する行列に似ているかもしれません。

上記c2)にて、定周期で追加した絵のオブジェクトを、テーブル型変数spritesの最後尾に追加していきます。

>開始してN秒後つまりN回table.insert(sprites,sprite)実行後のspritesの中身
sprite(t=1), sprite(t=2), sprite(t=3), sprite(t=4), ... sprite(t=N)
サイズ N

タッチイベント発生時、`table.maxn(sprites)`でこのテーブルのサイズを取得し、先頭の絵のオブジェクトから順番に位置を確認し、タッチ位置に絵があった場合は、絵をジャンプさせた後、描画対象レイヤーから削除し、対象の絵のオブジェクトをspritesから削除しています。

>例 タップしたところにt=3のspriteがあったでtable.remove(sprites,3)した後のspritesの中身
sprite(t=1), sprite(t=2),　　　　　sprite(t=4), ... sprite(t=N)
サイズ N-1

#たくさんの絵を消す
画面左上の絵をタッチすると、定期的に追加されたリスの絵は、画面と行列spritesから削除されます。

![11-3.png](https://qiita-image-store.s3.amazonaws.com/0/90495/0fb0170e-df7a-e4d7-8cbf-20cea3518074.png)

これはd1),d2),d3)で実現しています。
特にd3)で、万能行列spritesの大きさ分、先頭のオブジェクトを繰り返し削除します。

>sprites[1]:removeFromParent()

で描画レイヤーからspritesの先頭の絵のオブジェクトが指し示す絵を削除し、

>table.remove(sprites,1)

でspritesの先頭の絵のオブジェクトを削除しています。

細かい話ですが、spritesは定周期関数からもアクセスされます。つまり、定周期関数がspritesに追加している最中に、タッチイベントからspritesの中身を削除するタイミングが発生するかもしれません。実際のゲームでは、たくさんの絵を削除している時に絵のオブジェクトを追加しないようにするなどの一定の配慮が必要です。

ちなみに、cocos2d-xにはボタンクラスが用意されていますが、それを使わなくても、簡単なボタン動作であればスプライトとタッチイベントで実現できます。

#今後の予定
データの保存と読み出しについて説明します。