漆 Cocos2d-xで物理エンジンを使う 接触したら”何か”したい

#目次
1 はじめに
2 物体の接触設定と処理
2.1 物体の分類を設定する
2.2 物体の接触対象を設定する
2.3 接触時に呼び出される関数を記述し登録する
3 今後の予定

#はじめに
本記事は、cocos2d-xおよびCocos Code IDEを導入し、絵(スプライト)を表示し、タッチに反応して絵を動かし、BGMや効果音を鳴らし、物理エンジンchipmunkを使って物体を落下させた人で、特定の物体が接触した時に何かしたい人向けの、覚書です。まず、接触時に物体を特定するための情報を設定し、実際に接触させ、設定した情報を元に、物体を移動する例を使って説明します。

#物体の接触設定と処理
下のコードを使って説明します。実行すると、画面上から物体が落下し、地面に触れると上にまた上から落ち始めます。

![7 repeat fall.png](https://qiita-image-store.s3.amazonaws.com/0/90495/ed8cb178-f62e-2645-b780-319a050e2cbb.png)


```GameScene.lua
local WALL  = 0x01
local BALL  = 0x02 

local GameScene = class("GameScene",function()
    -- l)物理エンジンをこの場面に対し有効化
    return cc.Scene:createWithPhysics()
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()
    -- BGMを再生する
    local bgMusicPath = cc.FileUtils:getInstance():fullPathForFilename("background.mp3") 
    cc.SimpleAudioEngine:getInstance():playMusic(bgMusicPath, true)
    -- 効果音を先に読み込んでおく
    local effectPath = cc.FileUtils:getInstance():fullPathForFilename("effect1.wav")
    cc.SimpleAudioEngine:getInstance():preloadEffect(effectPath)
end

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

    -- n)ゲーム画面の中央を基点とする接続点を作る
    local node = cc.Node:create()
    node:setAnchorPoint(0.5, 0.5)
    node:setPosition(self.visibleSize.width/2, 0)
    -- l)その接続点(但し位置は画面下端)に四角の壁をつける
    local bodyWall = cc.PhysicsBody:createEdgeBox(cc.size(self.visibleSize.width, 10))
    bodyWall:setDynamic(false) 
    node:setPhysicsBody(bodyWall)
    layer:addChild(node)
    
    -- 落下物(スプライト)を追加し物体を取り付ける
    local sprite = cc.Sprite:create("land.png")
    sprite:setPosition(270,900)
    layer:addChild(sprite)
    local bodyBall = cc.PhysicsBody:createBox(sprite:getContentSize())
    sprite:setPhysicsBody(bodyBall)

    -- o)壁と落下物それぞれの分類と接触対象を設定
    bodyWall:setCategoryBitmask(WALL)
    bodyWall:setContactTestBitmask(BALL)
    bodyBall:setCategoryBitmask(BALL)
    bodyBall:setContactTestBitmask(WALL)

    -- p)接触時に呼ばれる関数
    local function onContactBegin(contact)
        local a = contact:getShapeA():getBody()
        local b = contact:getShapeB():getBody()
        
        -- q)接触した物体aとbのどちらかがボールなら、そのスプライト部を取得して、
        if a:getCategoryBitmask()== BALL then
            ball = a:getNode()
        elseif b:getCategoryBitmask()== BALL then
            ball = b:getNode()
        end
        
        -- 上に戻す
        if ball ~= nil then
            local jump = cc.JumpTo:create(1,cc.p(270,800),30,5) 
            ball:runAction(jump)
        end

        return true -- trueを返すこと
    end

    -- r)接触時に呼び出す関数を登録し、このレイヤーでの接触イベント取得を有効にする
    local contactListener = cc.EventListenerPhysicsContact:create()
    contactListener:registerScriptHandler(onContactBegin, cc.Handler.EVENT_PHYSICS_CONTACT_BEGIN)
    local eventDispatcher = layer:getEventDispatcher()
    eventDispatcher:addEventListenerWithSceneGraphPriority(contactListener, layer)

    return layer
end

return GameScene
```

重要な点のみ順番に説明します。

o)で、下壁と落下物それぞれの分類および接触対象を設定します。
例えば、ある物体Aが物体Bと接触したときにイベントが発生するようにしたいときは
物体Aの分類は1で、物体Aの接触対象は2(物体Bの分類)、
物体Bの分類は2で、物体Bの接触対象は1(物体Aの分類)、
という風に設定します。お互いの分類が接触対象に設定されていると、接触イベントが発生します。これが設定されていないと、接触イベントが発行されません。また、分類番号はビット列(1,2,4,..)で分けます。
p)が接触イベントが発生時に呼び出される関数です。
q)で、`a = contact:getShapeA():getBody()`は接触した物体を取得する命令です。これに対しさらに`getNode()`することで、その物体が取り付けられている絵の部分を取得できます。ここで、a、bどちらに落下物の情報が入るかは分からないため、両方を調べる必要があります。 
r)で、p)の関数を接触イベント開始時に呼び出す関数として登録し、このレイヤーに対する接触イベントの取得を有効化しています。

物理エンジンの重力計算にまかせて落下させ、落下物が壁に触れた瞬間、今度はジャンプアクションで開始点まで戻すようになっています。

#今後の予定
次回は、Cocos Code IDEを使ったAndroid、iPhone端末でのデバッグ実行と、Cocos Code IDEから生成されたプロジェクトファイルのEclipse、xCodeでのビルドおよび実行について説明します。