Example drag, pan, zoom, multitouch, tiles, visibleRect, testlua, zOrder

Unresolved problem

I could not get this code to run if setContentScaleFactor is used.

Why this example

The current examples are most interesting but every items are independent.

What this code does

  • Note a different size map would work the code does not use arbitrary constants.
  • vRect illustrate how to support visible rect supporting setDesignResolutionSize it is disable in this example.
  • Adding decorators for a sprite and on a layer DrawAnchorPoint DrawCenterPoints DrawBoundingBox
  • DisplayOnSceen keep a layer within the visible screen
  • mapToIndex translate a location on a TMXTiledMap layer to a tile index
  • indexToMap index to map location (if the index is an integer will give you the position of the anchor point)
  • onTouches… supports dragging, panning, zooming using pinch , and inserting new sprites on the tiled map.
  • zOrder is supported without using arbitrary constant but interrogating the map parameters.
  • Illustrate how to center a sprite on a tile.

m_tamara[i]:setAnchorPoint((m_tamara[i]:getContentSize().width - rectForTile_1_inTileSet.width)/(2*m_tamara[i]:getContentSize().width), 0)

How to use this code

  1. Change controller.lua add
    line
    screenSize = designSize
    after line
    local designSize = {width = 480, height = 320}

  2. change to file TileMapTest.lua
    after the end of function TMXBug787() replace all the code by the following code

local function TMXAA()
    local vRect = {}
    function vRect:setDesignResolutionSize (scale)
        scale = scale or 1
--~    cc.Director:getInstance():getOpenGLView():setDesignResolutionSize(480 * scale, 320 * scale, cc.ResolutionPolicy.NO_BORDER);
        self.visibleOrigin = cc.Director:getInstance():getVisibleOrigin()
        self.size  = cc.Director:getInstance():getVisibleSize()
        self.x, self.y = 0, 0 -- always at 0, 0
        self.width = self.size.width
        self.height = self.size.height
        self.left = cc.p(0, self.height/2)
        self.right = cc.p(self.width, self.height/2)
        self.top = cc.p(self.width/2, self.height)
        self.bottom = cc.p(self.width/2, 0)
        self.center = cc.p(self.width/2, self.height/2)
        self.topLeft = cc.p(0, self.height)
        self.topRight = cc.p(self.width, self.height)
        self.bottomLeft = cc.p(0,0)
        self.bottomRight = cc.p(self.width, 0)
        return self
    end
    vRect:setDesignResolutionSize()
    local function CreateAPoint(p, size)
        local point = cc.DrawNode:create()
        point:drawDot(p, size or 10, cc.c4f(1.0, 0.0, 1.0, 1.0))
        return point
    end
    local function DrawAnchorPoint(sprite, size)
        sprite:addChild(CreateAPoint(cc.p(sprite:getContentSize().width * sprite:getAnchorPoint().x, sprite:getContentSize().height * sprite:getAnchorPoint().y), size));
    end
    local function DrawCenterPoints(sprite, size)
        local bb = sprite:getBoundingBox()
        sprite:addChild(CreateAPoint(cc.p(bb.width/2, 0), size))
        sprite:addChild(CreateAPoint(cc.p(0, bb.height / 2), size))
        sprite:addChild(CreateAPoint(cc.p( bb.width / 2,  bb.height), size))
        sprite:addChild(CreateAPoint(cc.p(bb.width, bb.height / 2), size))
    end
    local function DrawBoundingBox(sprite)
        local rect = cc.DrawNode:create()
        local bb = sprite:getBoundingBox()
        rect:drawRect(cc.p(0, 0), cc.p(bb.width, bb.height), cc.c4f(1.0, 0.0, 1.0, 1.0))
        sprite:addChild(rect)
    end
    local ret = createTileDemoLayer("Touch-Multitouch-Pan-Zoom-Drag", "Touch tile to add image, touch image to delete [TMXAA]")
    ret:getEventDispatcher():removeEventListenersForTarget(ret)
    local MOVE_VS_TOUCH = 3 -- Arbitrary value
    local kTagTileMap = 1
    local s_pPathSister1 = "Images/grossinis_sister1.png"
    local m_tamara = {}
    local insertAtPosition
    local hidden = cc.LayerGradient:create(); hidden:setPosition(vRect.visibleOrigin)
    local baseLayer = cc.LayerGradient:create(cc.c4b(100,255,100,255), cc.c4b(255,0,0,255), cc.p(100, 100))
    local map = cc.TMXTiledMap:create("TileMaps/iso-test-zorder.tmx")
    local bottomLayer =   map:getChildByTag(0)
    local MapTileSize = bottomLayer:getMapTileSize()
    local MapSize = map:getMapSize()
    local MapFullSize = cc.size(MapTileSize.width * MapSize.width,  MapTileSize.height * MapSize.height )
    local halfTileSize = cc.size(MapTileSize.width/2, MapTileSize.height/2)
    local tileSetInfo = bottomLayer:getTileSet()
    local rectForTile_1_inTileSet = tileSetInfo:getRectForGID(1)
    local function DisplayOnSceen()
        local boundingBox = baseLayer:getBoundingBox()
        local x, y = baseLayer:getPosition()
        if boundingBox.x > 0 then baseLayer:setPositionX(x - boundingBox.x) end
        if boundingBox.y > 0 then baseLayer:setPositionY(y - boundingBox.y) end
        if cc.rectGetMaxX(boundingBox) < vRect.width then baseLayer:setPositionX(x + (vRect.width - cc.rectGetMaxX(boundingBox))) end
        if cc.rectGetMaxY(boundingBox) < vRect.height then baseLayer:setPositionY(y + (vRect.height - cc.rectGetMaxY(boundingBox))) end
    end
    local function mapToIndex(where)
        local x = math.floor(MapFullSize.height/MapTileSize.height  -  MapFullSize.width / (2 * MapTileSize.width) - 1/2 - where.y/MapTileSize.height + where.x/MapTileSize.width + 0.5)
        local y = math.floor(MapFullSize.height/MapTileSize.height + MapFullSize.width / (2 * MapTileSize.width) - 3/2  - where.y/MapTileSize.height - where.x/MapTileSize.width + 0.5)
        if x == -1 then x = 0 elseif x == MapSize.width then x = x - 1 end
        if y == -1 then y = 0 elseif y == MapSize.height then y = y -1 end
        return x>=0 and x<MapSize.width and y>=0 and y<MapSize.height and cc.p(x,y)
    end
    local function indexToMap(pt)
        return cc.p(halfTileSize.width * (pt.x - pt.y) +  MapFullSize.width / 2 - halfTileSize.width,
            MapFullSize.height  - halfTileSize.height * (pt.x + pt.y + 2))
    end
    local function onTouchesBegan(touches, event )
        local where = cc.pSub(map:convertTouchToNodeSpace(touches[1]), cc.p(halfTileSize.width, halfTileSize.height))
        insertAtPosition = mapToIndex(where)
    end
    local function onTouchesEnded(touches, event )
        for i,v in ipairs(touches)  do
            local where = map:convertTouchToNodeSpace(v)
            for i,v in ipairs(m_tamara)  do
                if v and cc.rectContainsPoint(v:getBoundingBox(), where) then
                    v:stopAllActions()
                    v:release()
                    map:removeChild(v)
                    table.remove(m_tamara, i)
                    insertAtPosition = nil
                    break
                end
            end
        end
        if #touches == 1 and cc.pDistanceSQ(touches[1]:getStartLocation(), touches[1]:getLocation()) > MOVE_VS_TOUCH then insertAtPosition = nil end
        if insertAtPosition then
            local i = #m_tamara + 1
            m_tamara[i] = cc.Sprite:create(s_pPathSister1)
            m_tamara[i]:setAnchorPoint((m_tamara[i]:getContentSize().width - rectForTile_1_inTileSet.width)/(2*m_tamara[i]:getContentSize().width), 0) -- center sprite on tile
            DrawAnchorPoint(m_tamara[i], 5)
            DrawCenterPoints(m_tamara[i], 3)
            DrawBoundingBox(m_tamara[i], 1)
            map:addChild(m_tamara[i], i)
            m_tamara[i]:retain()
            local fromTilePos = bottomLayer:getPositionAt(cc.p(MapSize.width-1, MapSize.height-1))
            m_tamara[i]:setPosition(fromTilePos)
            local toTilePos = bottomLayer:getPositionAt(insertAtPosition)
            local  move = cc.MoveBy:create(math.random(3, 10),  cc.pSub(toTilePos, fromTilePos))
            local  back = move:reverse()
            local  seq = cc.Sequence:create(move, back)
            m_tamara[i]:runAction( cc.RepeatForever:create(seq) )
        end
    end
    local function onTouchesMoved(touches, event )
        if #touches == 1 then
            local touch = touches[1]
            local scale = baseLayer:getScale()
            local newPosition  = cc.pAdd(cc.p(baseLayer:getPosition()), cc.p(touch:getDelta().x/scale,touch:getDelta().y/scale))
            baseLayer:setPosition(newPosition)
            DisplayOnSceen()
        elseif #touches == 2 then
            local director = cc.Director:getInstance()
            local curPostouches, prevPostouches = {}, {}
            curPostouches[1] = director:convertToGL(touches[1]:getLocationInView());
            curPostouches[2] = director:convertToGL(touches[2]:getLocationInView());
            prevPostouches[1] = director:convertToGL(touches[1]:getPreviousLocationInView());
            prevPostouches[2] = director:convertToGL(touches[2]:getPreviousLocationInView());
            local curPosLayer = cc.pMidpoint(curPostouches[1], curPostouches[2]);
            local prevPosLayer = cc.pMidpoint(prevPostouches[1], prevPostouches[2]);
            local prevScale = baseLayer:getScale()
            local curScale = baseLayer:getScale() * cc.pGetDistance(curPostouches[1], curPostouches[2]) / cc.pGetDistance(prevPostouches[1], prevPostouches[2])
            curScale  = math.min(math.max(1, curScale), 4)
            if curScale ~= prevScale then
                baseLayer:setScale(curScale)
                local realCurPosLayer = baseLayer:convertToNodeSpaceAR(curPosLayer);
                local delta = cc.pMul(realCurPosLayer, (curScale - prevScale))
                baseLayer:setPosition(cc.pSub(cc.p(baseLayer:getPosition()), delta))
            end
            insertAtPosition = nil
            DisplayOnSceen()
        end
    end
    local function onTouchesCancel(touches, event )
        insertAtPosition = nil
    end
    local listener = cc.EventListenerTouchAllAtOnce:create()
    listener:registerScriptHandler(onTouchesBegan,cc.Handler.EVENT_TOUCHES_BEGAN )
    listener:registerScriptHandler(onTouchesMoved,cc.Handler.EVENT_TOUCHES_MOVED )
    listener:registerScriptHandler(onTouchesEnded,cc.Handler.EVENT_TOUCHES_ENDED )
    listener:registerScriptHandler(onTouchesCancel,cc.Handler.EVENT_TOUCH_CANCELLED )
    local eventDispatcher = baseLayer:getEventDispatcher()
    eventDispatcher:addEventListenerWithSceneGraphPriority(listener, baseLayer)
    local function TMXIsoZorder()
        local scheduler = cc.Director:getInstance():getScheduler()
--~        sceneGame = cc.Scene:create()
        ret:addChild(hidden)
        hidden:addChild(baseLayer)
        local contentSize = map:getContentSize()
        baseLayer:addChild(map, 0, kTagTileMap)
        map:setScale(math.min(vRect.width/contentSize.width, vRect.height/contentSize.height))
        map:setPositionY(cc.rectGetMidY(baseLayer:getBoundingBox()) -cc.rectGetMidY(map:getBoundingBox()))
        map:setPositionX(cc.rectGetMidX(baseLayer:getBoundingBox()) -cc.rectGetMidX(map:getBoundingBox()))
        local function repositionSprite(dt)
            local nLayer = 1
            while map:getChildByTag(nLayer - 1) do nLayer = nLayer + 1 end
            for i= 1, #m_tamara do
                map:reorderChild(m_tamara[i], math.floor(math.max(nLayer - (cc.p(m_tamara[i]:getPosition()).y / (halfTileSize.height * 3)), 0)))
            end
        end
        local schedulerEntry
        local function onNodeEvent(event)
            if event == "enter" then
                schedulerEntry = scheduler:scheduleScriptFunc(repositionSprite, 0, false)
            elseif event == "exit" then
                for k,v in pairs(m_tamara)  do
                    v:release()
                end
                scheduler:unscheduleScriptEntry(schedulerEntry)
            end
        end
        baseLayer:registerScriptHandler(onNodeEvent)
        return baseLayer
    end
    TMXIsoZorder()
    baseLayer:setScale(2)
    local tile = bottomLayer:getTileAt(cc.p(MapSize.width-1, MapSize.height-1))
    local bb = tile:getBoundingBox()
    local pt = CreateAPoint(cc.p(cc.rectGetMidX(bb), bb.y), 2)
    map:addChild(pt)
    local rect = cc.DrawNode:create()
    rect:drawRect(cc.p(bb.x, bb.y), cc.p(bb.x+ bb.width, bb.y+bb.height), cc.c4f(1.0, 0.0, 1.0, 1.0))
    map:addChild(rect)
    return ret
end
function TileMapTestMain()
    cclog("TileMapTestMain")
    Helper.index = 1
    cc.Director:getInstance():setDepthTest(true)
    local scene = cc.Scene:create()
    Helper.createFunctionTable = {
        TMXAA,
        TileMapTest,
        TileMapEditTest,
        TMXOrthoTest,
        TMXOrthoTest2,
        TMXOrthoTest3,
        TMXOrthoTest4,
        TMXReadWriteTest,
        TMXHexTest,
        TMXIsoTest,
        TMXIsoTest1,
        TMXIsoTest2,
        TMXUncompressedTest,
        TMXTilesetTest,
        TMXOrthoObjectsTest,
        TMXIsoObjectsTest,
        TMXResizeTest,
        TMXIsoZorder,
        TMXOrthoZorder,
        TMXIsoVertexZ,
        TMXOrthoVertexZ,
        TMXIsoMoveLayer,
        TMXOrthoMoveLayer,
        TMXTilePropertyTest,
        TMXOrthoFlipTest,
        TMXOrthoFlipRunTimeTest,
        TMXOrthoFromXMLTest,
        TMXBug987,
        TMXBug787
    }
    scene:addChild(TMXAA())
    scene:addChild(CreateBackMenuItem())
    return scene
end

I hope this help
Andre

@RetryAgain , sorry i can’t catch your meaning.Can you give me the screenshot to distinguish the correct and wrong displaying.

Working

The tile map is correct.
Zoom, pan, drag works
Touch works but the location is not right

Problems caused by setContentScaleFactor.

The calculation for the tile position fails.
Tamara’s scale is wrong.
Tamara is not hidden by the trees.

Debugging

I have taken a long look at the side effect of setContentScaleFactor but I cannot figure out what is really going on.

Thank you and good luck.

Andre

Teslua.zip (318.7 KB)

I have some questions as follow:
1.setContentScaleFactor wasn’t used in the codes which you supply
2.Tamara is hidden by the trees on the cocos2d-x v3.4 final.which version did you use?
3.The tamaras could exist both left and right sides.
4.For Scale is wrong, and The calculation for the tile position fails, i don’t have idea that how to check these two problems.

Thank you for feedback.

setContentScaleFactor is coded in controller.lua

If you have added the line described in my first message in controller.lua the problem will not exist.

  1. Change controller.lua add
    line
    screenSize = designSize
    after line
    local designSize = {width = 480, height = 320}

Then setContentScaleFactor is not used

I use 3.3 sorry I will update my version and tested it again.

If you click on a tile the Tamara should move exactly from the bottom tile to that tile and the Tamara should be perfectly centered on the tile horizontally and with the bottom of the tile and the bottom of the Tamara aligned.

I will try to retest, using 3.4, as soon as possible but it may take me a while.
Will post back to this message.

Thanks for looking at this problem.

Andre

Just tested on 3.4.

I am sorry but there is no change.

Included are the file to do the test.

Commenting line 28 in controller.lua make the program fail.
Uncommenting makes it works.

TestLua.zip (9.9 KB)

Thank you

Andre

if you have a minute replace the pIsSegmentIntersect in Cocos2d.lua

function cc.pIsSegmentIntersect(pt1,pt2,pt3,pt4)
local ret, s, t =cc.pIsLineIntersect(pt1, pt2, pt3, pt4, s ,t)
return ret and s >= 0.0 and s <= 1.0 and t >= 0.0 and t <= 1.0
end

Thank you for fixing class