Quick Links: Download Gideros Studio | Gideros Documentation | Gideros community chat | DONATE
A question about tilemaps, hitboxing, and triggers. — Gideros Forum

A question about tilemaps, hitboxing, and triggers.

HarrisonHarrison Member
edited June 2014 in General questions
I am currently working on a top down rpg (imagine early legend of zelda style game mechanics). I have been dabbling with tilemaps for a little bit, but i was wondering if in a top down scenario i could set tiles to act as walls, event triggers, npc spawns, etc. Imagine how powerful this could be.. Code base game then open tile and make all of the maps without even touching a .lua file. Is this doable/realistic? I have been looking at these lines of code because they seem relevant..


mainScene:object(layer.objects[i].x + layer.objects[i].width/ 2, layer.objects[i].y + layer.objects[i].height/2,
layer.objects[i].width, layer.objects[i].height, layer.objects[i].name, layer.objects[i].type,
tilemap:getProperty(layer.objects[i], "score"))

This is from:
http://giderosmobile.com/forum/discussion/999/tiled-example-with-tile-layer-object-layer-and-box2d-collisions/p1
Thanks!
“ The first 90% of the code accounts for the first 90% of the development time. The remaining 10% of the code accounts for the other 90% of the development time. ” - Tom Cargill

Comments

  • [Update]
    Box2d with 0 gravity would indeed work i believe... But it seems like using a sledgehammer on a nail. Will have to check it out...
    “ The first 90% of the code accounts for the first 90% of the development time. The remaining 10% of the code accounts for the other 90% of the development time. ” - Tom Cargill
  • ar2rsawseenar2rsawseen Maintainer
    @Harrison since TileMaps are rectangle, I don't think you need Box2d but probably simple boundary check or something like it
  • @ar2sawseen
    by simple boundary check you mean writing a function to check if tile is.. say..#1 instead of #4? You don't mean draw bounding boxes everywhere manually right?
    “ The first 90% of the code accounts for the first 90% of the development time. The remaining 10% of the code accounts for the other 90% of the development time. ” - Tom Cargill
  • bump
    “ The first 90% of the code accounts for the first 90% of the development time. The remaining 10% of the code accounts for the other 90% of the development time. ” - Tom Cargill
  • ar2rsawseenar2rsawseen Maintainer
    Sorry, somehow missed it with all the posts :)

    So you have a TileMap and each of it's tile is the same size. For example you have a tiles that are 64x64px

    Let's first write a function that determines if tile i, j collides with your character:
    --assuming your character has Character class
    function Character:collidesWithTile(i, j)
        -- getting bounds of your character
        local x,y,w,h = self:getBounds(stage)
     
        -- tile dimensions known
        local w2, h2 = 64, 64
     
        --getting position of whole tile map
        local x2, y2 = tilemap:getBounds(stage)
     
        -- calculating position of specific tile
        x2 = x2 + (i-1)*64
        y2 = y2 + (j-1)*64
     
        --performing collision check
        return not ((y+h < y2) or (y > y2+h2) or (x > x2+w2) or (x+w < x2))
    end
    Next what you need is to determine which tiles you can walk on and which not. There are many ways to go here, depending on the size of your map
    1) if you have a small map, you can store all i, j of tile maps which you can't go on and check them in a loop every time you move
    2) if you have a larger, but your character is smaller than one single tile, then you may want to check against all tiles around him
    3) if you character is larger than one single tile, than you may want to check against all tiles on the screen, etc

    Let's go with option 2)

    First we need to determine, on which tile your character currently is:
    function Character:getTile()
        -- getting bounds of your character
        local x,y,w,h = self:getBounds(stage)
     
        --calculating center
        x = x +w/2
        y = y + h/2
     
        --getting position of whole tile map
        local x2, y2 = tilemap:getBounds(stage)
     
        --calculating tile
        return math.ceil((x2-x)/64), math.ceil((y2-y)/64)
    end
    Now we need to know on which tiles we can't step on. Tiles can be identified by the x and y number (or basically index) on the texture file. So we need to store indexes of bad tiles we can't step onto in a table.
    Something like this:
    local badTiles = {}
    badTiles[1][1] = true
    badTiles[2][1] = true
    ---etc
    Then we can write a function to check if character can move, by checking all adjacent tiles
    function Character:canMove()
        -- get current tile character is on
        local i, j = self:getTile()
        --loop through all adjacent tiles
        for k = i -1, i+1 do
            for m = j - 1, j + 1 do
                --check if we collide with the tile
                if self:collidesWithTile(k, m) then
                    --then check if it is a bad tile
                    if badTiles[k][m] then
                        --can't move, return false
                        return false
                    end
                end
            end
        end
        --can move, return true
        return true
    end
    Now I have no idea how you move your character, but lets assume you drag him, so you can do something like this:
    function Character:onMouseDown(e)
        if self:hitTestPoint(e.x, e.y) then
            self.isFocus = true
     
            --storing for dragging offset
            self.x0 = e.x
            self.y0 = e.y
     
            e:stopPropagation()
        end
    end
     
    function Character:onMouseMove(e)
        if self.isFocus then
            --storing reference to previous position
            self.lastX,self.lastY = self:getPosition()
     
            --including drag offset
            local dx = e.x - self.x0
            local dy = e.y - self.y0
     
            --moving character on new position
            self:setX(self:getX() + dx)
            self:setY(self:getY() + dy)
     
            if self:canMove() then
                --if can move, then saving new drag offset
                self.x0 = event.x
                self.y0 = event.y
            else
                --reverting to previous position
                self:setPosition(self.lastX,self.lastY)
            end
     
           e:stopPropagation()
        end
    end
     
    function Character:onMouseUp(e)
        if self.isFocus then
            self.isFocus = false
     
            e:stopPropagation()
        end
    end
    Disclaimer this is just something from the top of my head, without testing or trying it out, so maybe need to workout some quirks, but should be enough to get you started or decide to move on with other method as box2d :)

    Likes: Harrison

    +1 -1 (+1 / -0 )Share on Facebook
  • @ar2rsawseen The code you wrote for Harrison above assumes you can determine a location of a tile in a tilemap at i, j. Trying to understand TileMap and your code above, when I add print(map:getBounds(stage)) to the onMouseUp function in desert.lua, for example, I get the following error:

    main.lua:117: attempt to call method 'getBounds' (a nil value)

    Also, when I include print(allLayers:getX(), allLayers:getY()) in the onMouseUP function, I get values like -27 -61. I can imagine turning those values into x=1 and y=2, but even when I use print(map:getTile(1,1)), I get something similar:

    attempt to call method 'getTile' (a nil value)

    Maybe I should be querying a specific layer? But that doesn't work either. print(allLayers[1]:getTile(1,1)) gives me:

    attempt to index field '?' (a nil value)

    Clearly I'm confused how to query the TileMap variable to determine the tile in question.

    Any help would be appreciated. Thanks.
  • piepie Member
    @Greywine if you can share the project it will be easier to answer to your questions :) those errors seems to be related to the structure of your code:
    When you
    print(allLayers:getX(), allLayers:getY())
    you're getting the position of allLayers on stage, which is a Sprite that "includes" every layer of the tilemap, and it is not a Tilemap object (so you can't apply getTile() to it).
    allLayers[1] could be a tilemap object, but are you sure that you have allLayers[1] and that you can access it?
    If I recall correctly, in desert example the tilemap object is added as local group to allLayers, so it becomes unaccessible outside the function that creates it.
  • GreywineGreywine Member
    edited July 2015
    @pie. Based on your feedback, I'm still trying to get a simple getTile() request to work with a 2 layer map. There's an animated tiles example where they simply query from a main.lua function:

    local tilemap = TiledMap.new("platformer.lua")
    local tiles = tilemap:getTileMap()
    local function onMouseUp(event)
    currentTileX, currentTileY, currentTileFlip = tiles:getTile(13,6)

    http://giderosmobile.com/forum/discussion/2028/another-tilemap-question-animated-tiles/p1

    Here's my simplified version with two layers from the original Sewers2 project, Tilemap.lua from the TileMultiple.lua example and a simple attempt to query a tile like the animated-tiles example.

    Of course, I know it's because I'm not querying a Tilemap object. But at this point, I really don't know how to extract that information from my code. The animated-tiles example works with one layer. Maybe it's just not possible with two layers?!

    zip
    zip
    Sewer3.zip
    74K
  • piepie Member
    @Greywine it is possible, see attachment :)

    I do believe that you need to keep different tilesets on separate layers though, because of the way that setTile works (you have to use another texture from the same tileset).
    zip
    zip
    sewers_setTileMulti.zip
    74K
  • @pie Beautiful. Thanks, that's exactly what I couldn't see yesterday. The GetTileMap function needed the layer variable in order to return a Tilemap.
  • piepie Member
    @Greywine, not only that, it also needed an "external access" to the tilemaps (lines #23 and #54 of TileMap.lua)
    still I am wondering if there is a better way to achieve the same result :)
  • @pie. I'm making a lot of progress on my game thanks your sewers_setTileMulti example above. Your solution to determining the TileMap was

    function TiledMap:getTileMap(layer) --layer added
    if layer then
    return self.worldmap[layer]
    else
    return self.worldmap
    end
    end

    i.e. we index the self.worldmap table of TileMap layers. What if self.worldmap was a Sprite, instead of a table. How would you access a TileMap layer? In TileMap.lua, I changed the definition of self.worldmap from {} to

    self.worldmap = Sprite.new()

    On line 54, I changed the table.insert to

    self.worldmap:addChild(group)

    And finally I rewrote the GetTileMap function to be

    function TiledMap:getTileMap(layer) --layer added
    return self.worldmap:getChildAt(layer)
    end

    Depending on what exactly I do, I either don't see anything, or I get an index error. Like I said, I'm happy with tables, but Giderosmobile seems like it's built with Sprites in mind (like the Desert and Sewers examples). So I'm still trying to understand the world of classes instead of the being content in the world of tables.

    Thanks again.
  • Thinking about it again, maybe I don't need the self.worldmap at all? TiledMap is a Sprite class and at the end of TiledMap:init, the 'self' in "self:addChild(group)" adds the Sprite 'group' to TiledMap.

    Maybe there's a way to get a TileMap from the Sprite 'TiledMap'?
  • piepie Member
    @Greywine Maybe I am not the best person to explain since I still have a bit of confusion in mind about this :D however I'll try:

    in Lua, "Classes don't exist", and theoretically everything is a table:
    Sprite too is a table with extended functionality - like any other "class".
    http://lua-users.org/wiki/ObjectOrientationTutorial

    I think that would be possible to get a tilemap layer directly from an instance of the Sprite TiledMap, the "problem" is that you can't access locals outside their scope, and that this code is adding the tilemap object (which is what you need to retrieve later) to a local Sprite (group) before group itself is added to TiledMap Sprite.
    Unless you save a reference to each tilemap layer, I can't see how to retrieve them.
    local group = Sprite.new() --line 28
    (...)
    group:addChild(tilemap) --line 53
    (...)
    self:addChild(group) --line 67

    It's usually better to use local variables because they can be collected by garbage collector as soon as they are no longer referenced, but

    If you need to access something "later" you need to store it as a property of a global object, or as a global variable:
    self.worldmap has nothing to do with the display on screen of the tilemap saved there (in fact you can see the tilemap layer even if you don't have self.worldmap) it's just an "address" to find it later :)


Sign In or Register to comment.