Quick Links: Download Gideros Studio | Gideros Documentation | Gideros Development Center | Gideros community chat
Collision with any object - Gideros Forum

Collision with any object

MaxMax Member
edited March 2016 in General questions
Hello,
currently I'm trying to figure out how to check collision for an object without physics. The only problem I have at the moment is that I want an object to stop moving as soon as it hits any object, no matter which one. With the code I have at the moment I have to check for every single object if it collides with it. Is there a way to check if the object collides with any other object?
Thank you in advance.

Comments

  • I can recommend Bump. I played with it for a couple of hours, and it has a number of basic collision responses, including "touch" which seems to be what you're after.

    https://github.com/kikito/bump.lua
    My Gideros games: www.totebo.com
    +1 -1 (+3 / -0 ) Share on Facebook
  • antixantix Member
    I also just discovered Splash yesterday. It's like Bump but can handle different shapes like circles too.
    https://github.com/bakpakin/Splash.lua

    But Bump is very powerful!
  • antixantix Member
    edited March 2016
    My current experiment in Gideros uses Bump for collisions. If you are going to use a tilemap with Bump then maybe you will find this example project of use. It has two classes..

    RenderLevel - Draws a tiled map. This class is based on the Desert example. Call render(map) to render a tiled map. It just draws tilemaps and in the code it has placeholder code for processing object and image layers as well. Subsequent calls to render() will attempt to dispose of any previously created resources.

    CollisionMaker - Creates collision rectangles from a tiled map and adds them to a bump collision world. The main function you will call is createRectangles(map, name). map is a loaded tiled map and name is the name of the layer you want to make rectangles from. The function its-self It makes two passes over the layer, with the 1st pass creating vertical rectangles, and the 2nd pass creating horizontal rectangles. Each rectangle is created with a isWall bool variable to make it easy to differentiate them from other collision objects in bump. NOTE that the function treats any non zero tile as solid. Finally it adds the created rectangles to the bump collision world. Subsequent calls to createRectangles() will remove any previously added collision rectangles from the bump world. You can also just call reset() if you just want to clear the generated data.

    Here is an example of how it works..
    local world = require("bump").newWorld() -- CREATE NEW BUMP COLLISION WORLD
     
    local func = loadfile("levels/test.lua") -- LOAD MAP
    local map = func()
    func = nil
     
    local RenderLevel = RenderLevel.new() -- RENDER MAP
    RenderLevel:render(map)
    stage:addChild(RenderLevel)
     
    local CollisionMaker =  CollisionMaker.new(world) -- GENERATE COLLISION RECTANGLES
    CollisionMaker:enableDebug(true)
    CollisionMaker:createRectangles(map, "walls")
    The attached picture shows the collision rectangles that are created by running the attached example project. As you can see, you could add another pass to optimize the rectangle output but it works as is just fine.

    EDIT: See further down the thread for the updated example.
    +1 -1 (+6 / -0 ) Share on Facebook
  • john26john26 Maintainer
    edited March 2016
    I don't have much to add beyond the excellent suggestions above, but just take a step back to look at the theory of it. In principle there is no way to check if object A collided with any other objects (B, C, D etc) without looping over B, C, D and checking each one. What you can do is simplify the check. Eg if A, B, C, D are all strangely shaped objects (polygons) then checking a collision involves some complex mathematics for each pair. But you can be sure they didn't collide if you consider the rectangle that encloses eg A and B. If those rectangles didn't overlap, then no collision took place and you can skip to the next pairing. If the rectangles overlapped then you have to do the detailed calculation to see if the objects overlapped.

    Another approach is to divide the game area into a grid and assign each object to one cell in the grid. Eg it might be quite coarse like 3x3 and A is in cell (1,2) and B is in cell (3,3). In this case you can be sure A and B are not colliding as they are in different cells (and not neighbouring cells). The disadvantage of this is you need to constantly update the cell index number for each object but it means many collision tests can be avoided.

    The point is you can write your own collision detection code right in Lua. It is not necessarily better to use Box2D or other libraries. If you are doing something simple, complex libs could slow things down while 2-3 lines of Lua code is often faster.

    This tutorial provides great info on collision detection.
    http://www.metanetsoftware.com/technique/tutorialA.html

    Likes: rolfpancake, antix

    +1 -1 (+2 / -0 ) Share on Facebook
  • antixantix Member
    edited March 2016
    @john26, great explanations :)
    I still wholeheartedly recommend bump for grid based games. I twrote my own collision engine and whilst it is pretty good, it doesn't come close to bump by a long way. If I had discovered bump before writing my own collision engine, I would have saved 3 weeks heheh. Good article link :)

    If you want to go down the SAT path then here are some other links.

    https://bitbucket.org/RudenkoPaint/sat-lua - I haven't looked at this closely but it's another library from the looks of it.
    http://www.phailed.me/2011/02/polygonal-collision-detection/ - This one has a simple explanation of SAT and has LUA source code too.

    There is also a rather cool library called HardonColider which uses SAT. Its hard to get working with Gideros (don't even go there) but it has some great code and there's a lot to learn from reading through it. https://vrld.github.io/HardonCollider/.

    You can find a few others collision libraries at https://love2d.org/wiki/Category:Libraries. In general it's a great place to go (check out their forums too) to find code and stuff. Love2D is LUA based and "most" of the things you will find there are fairly easy to get working with Gideros (except HardonCollider ROFLS)

    And here's a neat little blog about circle collision/resolution with LUA code too.. http://blogs.love2d.org/content/circle-collisions

    And finally this series of articles is pretty good at showing how to make a Mario style platform collision engine... https://www.raywenderlich.com/15230/how-to-make-a-platform-game-like-super-mario-brothers-part-1

    Likes: rolfpancake

    +1 -1 (+1 / -0 ) Share on Facebook
  • antixantix Member
    edited March 2016
    I took a day off from my current project to mess about and chose to dig up my HardOnCollider experiment and post it as another example of collision detection. I've tidied it up a bit and added a tiled map loader which loads tiled maps and creates collision data from objects in any objectlayer.

    If you run the attached project you can click and drag the ball around the screen, which will turn red when it collides with a shape. The ball will also be pushed out of any shape that it collides with and a little white arrow will show the direction of the collision.

    EDIT: See further down the thread for the updated Hardon Collider example.
    +1 -1 (+4 / -0 ) Share on Facebook
  • @antix
    Thanks a ton for your links and the attached project!
    (A central place for tutorial links would be great anyway)

    Likes: john26

    +1 -1 (+1 / -0 ) Share on Facebook
  • muromuro Member
    @antix thanks a lot for your great answers
  • @antix You are awesome. The project you have just upload is exactly what I ve been looking for a while now. Thanks for sharing.

    Likes: antix

    +1 -1 (+1 / -0 ) Share on Facebook
  • antixantix Member
    edited March 2016
    You're welcome :)

    @misterhup has another discussion about collisions and I posted another example project there. http://giderosmobile.com/forum/discussion/6357/bump#Item_1

    I'll re-post it here so all my examples are in the one thread....

    Okay here is a little example project that can detect collisions between rotated rectangles. The collision stuff is from this page.. https://bitbucket.org/RudenkoPaint/sat-lua. The version included here has been modified slightly to better work with Gideros. It's a bit messy but it works.

    When you run the example project you can click and drag one of the boxes around. If it collides with the other, both turn red.

    NOTE: The example is fairly basic and does not perform any collision resolution but with a bit of work it could easily do so. The "response" table in the main loop contains pretty much everything you need to resolve the collisions.

    Anyway, it should be enough to get you up and running


    EDIT: See further down the thread for the updated example..
    +1 -1 (+4 / -0 ) Share on Facebook
  • Hey @antix, how does SAT compare to Bump, performance wise? I'm using Bump in a shooter I'm working on and it's really quite fast and does what I want it to (resolves collisions).

    Likes: antix

    My Gideros games: www.totebo.com
    +1 -1 (+1 / -0 ) Share on Facebook
  • antixantix Member
    @totebo - SAT is slower because needs to use a ton of maths to project vectors and stuff when determining if collisions happened. Add to that having to rotate all those vertices when you rotate a polygon and it adds up. Once you start using large numbers of polygons, bump really outshines a SAT solution like the example in my post above.

    at the end of the day, if you aren't rotating rectangles, then use bump! It's just fantastic!

    I updated my example while I was away for the wekeend (as well as my examples in the other thread (Hardon Collider, and Bump Collision Maker). I've added some small documentation to the top of the code and also in the classes I have provided. Most of the classes have been revamped and I'm pretty happy how they ended up. I'm surprised at how much I got done without any internet to distract me heheh.

    Here is the latest incarnation of the SAT example..
    --[[
     
    SAT Rectangle Collision Example for Gideros
    by Cliff Earl
    Antix Software
    March 2016
     
    Example  project that  creates and  displays 2  rotating boxes  that can  be 
    dragged  about the  screen by  their center  points, and  detects collisions 
    between them. 
     
    Collision  detection  is a  2 part  process. First  the bounding  boxes  are 
    checked to see  if they overlap  (it is pointless  to waste CPU  time on SAT 
    calculations  unless that is the case). Once the  boxes overlap they will be 
    tinted yellow and  SAT collision detection will then  be applied to test for 
    intersection  (one rectangle partially  inside the other  rectengle). If the 
    rectangles do intersect then they will be tinted red.
     
    There is one class (Draggable) and 1 library (GSAT) included in the example. 
     
    Draggable
    An  extension  of  Bitmap  that can  be dragged  about by  its  center.  See 
    Draggable.lua for more information.
     
    GSAT
    A  simple library for  performing collision  detection (and projection-based 
    collision response) of rectangular shapes in  2 dimensions. See GSAT.lua for 
    more information.
     
    --]]
     
    --require("mobdebug").start() -- ONLY REQUIRED FOR ZEROBRANE
     
    local GSAT = require("gsat") -- THE COLLISION LIBRARY
     
    local function newBox(actor) -- CREATE A NEW COLLISION BOX
      local x, y, w, h = actor:getBounds(stage)
      local box = GSAT.Box(x + (w * 0.5), y + (h * 0.5), w, h)
      box:setRotation(actor:getRotation())
      return box
    end
     
    local function actorInActor(actor1, actor2) -- TEST 2 BOUNDING BOX FOR OVERLAP
    	local x, y, w, h = actor1:getBounds(stage)
    	local x2, y2, w2, h2 = actor2:getBounds(stage)
    	return not ((y+h < y2) or (y > y2+h2) or (x > x2+w2) or (x+w < x2))
    end
     
    local info = TextField.new(nil, "Drag the boxes by their centers to move them")
    info:setPosition(20, 20)
     
    local player = Draggable.new(Texture.new("images/box.png", true), {x = 160, y = 128}) -- CREATE PLAYER
    player.box = newBox(player)
    player.onDragged = function() player.box:setPosition(player:getPosition()) end
     
    local enemy = Draggable.new(Texture.new("images/box.png", true), {x = 160, y = 352}) -- CREATE ENEMY
    enemy.box = newBox(enemy)
    enemy.onDragged = function() enemy.box:setPosition(enemy:getPosition()) end
     
    local function onEnterFrame(event) -- MAIN LOOP
     
      local angle = enemy:getRotation() - 0.284 -- ROTATE ENEMY CCW
      enemy:setRotation(angle)
      enemy.box:setRotation(angle)
     
      angle = player:getRotation() + 0.284 -- ROTATE PLAYER CW
      player:setRotation(angle)
      player.box:setRotation(angle)
     
      if actorInActor(player.box, enemy.box) then
        stage:setColorTransform(1, 1, 0, 1) -- BOXES OVERLAP, TINT YELLOW
     
        local response = GSAT.Response() -- CHECK FOR COLLISION BETWEEN BOXES
        local collided = GSAT.checkCollision(player.box, enemy.box, response)
        if collided then
          stage:setColorTransform(1, 0, 0, 1) -- BOXES COLLIDED,  TINT BOXES RED
     
          --
          -- PERFORM COLLISION RESOLUTIONS HERE
          --
     
        end
      else
        stage:setColorTransform(1, 1, 1, 1) -- NO COLLISION, TINT BOXES BACK TO NORMAL
      end
     
    end
     
    stage:addEventListener(Event.ENTER_FRAME, onEnterFrame)
     
    stage:addChild(enemy)
    stage:addChild(player)
     
    stage:addChild(info)
    zip
    zip
    SAT Collision.zip
    19K
    SAT Collision.png
    336 x 540 - 33K
    +1 -1 (+3 / -0 ) Share on Facebook
  • antixantix Member
    Here is the updated Collision Maker example..
    --[[
     
    CollisionMaker Example for Gideros
    by Cliff Earl
    Antix Software
    March 2016
     
    Example project that  loads a tiled  map, renders it, and  creates collision 
    rectangles from one of its layers (which are then added to a bump world).
     
    bump is a collision detection and resolution library that operates on AABB's 
    (Axis Aligned Bounding  Boxes), it can  also be used as  a spatial directory 
    complete  with query  functions. You can  read more  about bump.lua  and its 
    functionality at <a href="https://github.com/kikito/bump.lua" target="_blank" rel="nofollow">https://github.com/kikito/bump.lua</a>
     
    Take the following case where a map is a 3x3 grid where 1 represents a solid 
    cell and 0  represents an empty cell. You  want your sprites to  collide and 
    not pass through any solid cells. You could create a new collision rectangle 
    for each solid  grid cell  which would result in 6 collision  rectangles. It 
    would be more efficient (and faster)  to create fewer (but bigger) collision 
    rectangles. This  is what  CollisionMaker does.  CollisionMaker first  scans 
    each  vertical column of  grid cells in the map  layer and creates collision 
    rectangles from  them. It then  makes a  second pass  and creates horizontal 
    collision  rectangles  from  the  remaining  solid  grid  cells.  So  in the 
    following example CollisionMaker would generate only 3 collision rectangles. 
    I hope you can see the benefit of CollisionMaker.
     
    1,1,1
    1,0,0
    1,1,0
     
    2 classes are provided with this example, RenderLevel, and CollisionMaker... 
     
    RenderLevel.
    Loads tiled maps and renders them. RenderLevel is an extension of the Sprite 
    class so once  you have called  render() it  can  be directly  added to  the 
    stage. When calling render() any previously created resources (sprites, etc) 
    will be cleaned up so you can reuse the RenderLevel over and over.
     
    CollisionMaker.
    Scans named  layer and creates collision  rectangles  from it. Any  generated 
    collision  rectangles are  added to a bump world.  CollisionMakers generation 
    technique is explained above. When you call createRectangles() any previously 
    created collision rectangles  will be removed from the  bump world so it also 
    can be reused over  and over. When adding a  collision rectangle to  the bump 
    world, CollisionMaker  sets its isWall bool to true, which can be used during 
    collision detection / resolution.
     
    NOTES:
     
    If you examine the  RenderLevel code you will see placeholder  code where you 
    can expand its functionality to process objectlayers and imagelayers.
     
    --]]
     
    --require("mobdebug").start() -- ONLY REQUIRED FOR ZEROBRANE STUDIO
     
    local RenderLevel = RenderLevel.new() -- CREATE RENDERLEVEL
     
    local world = require("bump").newWorld() -- CREATE BUMP COLLISION WORLD
     
    local func = loadfile("levels/level0000.lua") -- LOAD MAP
    local map = func()
    func = nil
     
    RenderLevel:render(map)  -- RENDER MAP
    stage:addChild(RenderLevel)
     
    local CollisionMaker =  CollisionMaker.new(world) -- GENERATE COLLISION RECTANGLES
    CollisionMaker:enableDebug(true)
    CollisionMaker:createRectangles(map, "walls")

    Likes: pie, jdbc

    CollisionMaker.png
    336 x 540 - 14K
    zip
    zip
    CollisionMaker.zip
    19K
    +1 -1 (+2 / -0 ) Share on Facebook
  • antixantix Member
    And finally here is the updated Hardon Collider example..
    --[[
     
    Hardon Collider Example for Gideros
    by Cliff Earl
    Antix Software
    March 2016
     
    Example project that  loads a tiled  map, renders it, and  creates collision 
    shapes from one of its layers and adds  those to a Hardon Collider world. It 
    also updates the Collision world which detects collisions and performs basic 
    collision resolution  by pushing  the draggable  ball shape out of  any wall 
    shape that it collides with. 
     
    Hardon Collider is a Love2d library that detects collisions between circles, 
    polygons (convex and concave), and points. You just  add collision shapes to 
    the collider, update it every frame, and  then respond to  collisions via  a 
    callback fundtion. The version of Hardon Collider provided with this example 
    is a version that has  been modified to work with Gideros. You can read more 
    about Hardon Collider at <a href="https://vrld.github.io/HardonCollider/" target="_blank" rel="nofollow">https://vrld.github.io/HardonCollider/</a> 
     
    Besides  the Hardon Collider library  (and its many associated  lua files) 1 
    class is provided, Level...
     
    Level
    A Sprite extension that can load tiled maps. It creates collision shapes for 
    objects in objectlayers of the type "solid". These collision shapes are then 
    added to the Hardon Collioder  world. It can also creates  a graphical shape 
    (Gideros Shape) for each collision shape it creates. See  Level.lua for more 
    information.
     
    --]]
     
    --require("mobdebug").start() -- ONLY REQUIRED FOR ZEROBRANE
     
    require("json")
     
    local currentTimer = 0
     
    local COLLIDER = HC.new(100) -- CREATE NEW COLLISION WORLD
     
    local Level = Level.new({collider = COLLIDER, createShapes = true}) -- LOAD A MAP
    Level:loadMap("level0000")
    Level:toFront()
     
    local x, y = Level:getPlayerStart() -- CREATE A BALL OBJECT
    local ball = Bitmap.new(Texture.new("images/ball.png", true))
    ball:setAnchorPoint(0.5, 0.5)
    ball.shape = COLLIDER:addCircle(x, y, ball:getWidth() * 0.5)
    ball.position = ({x = 0, y = 0})
    ball:setPosition(x, y)
    stage:addChild(ball)
     
    local arrow1 = Bitmap.new(Texture.new("images/arrow.png", true)) -- SOME RANDOM ARROW
    arrow1:setAnchorPoint(0.5, 0.5)
    arrow1:setRotation(0)
    arrow1:setPosition(x, y)
    arrow1:setVisible(false)
    stage:addChild(arrow1)
     
    local DEBUG = TextField.new(nil,"") -- JUST TO SOW A FEW VARS
    DEBUG:setPosition(0, 64)
    stage:addChild(DEBUG)
     
    local function onCollision(dt, shape_a, shape_b, mtv_x, mtv_y) -- CALLED WHEN COLLISION DETECTED
        local floor, radToDeg, atan2 = math.floor, 180/ math.pi, math.atan2
     
        local ball_shape, tileshape -- sort out which one our ball shape is
        if shape_a == ball.shape and shape_b.type == "tile" then
          ball_shape = shape_a
        elseif shape_b == ball.shape and shape_a.type == "tile" then
          ball_shape = shape_b
        else
          return -- NONE OF THE TWO SHAPES IS A TILE <img class="emoji" src="/resources/emoji/frowning.png" title=":(" alt=":(" height="20" />
        end
     
        local angle = ( ( atan2(mtv_y, mtv_x) * radToDeg ) -90) % 360 -- ANGLE OF COLLISION
     
        ball_shape:move(mtv_x, 0) -- CORRECT COLLISION SHAPE X, Y
        ball_shape:move(0, mtv_y)
     
        local fx, fy = ball_shape:center() -- CORRECT BALL X, Y
        ball.position.x = fx
        ball.position.y = fy
        ball:setPosition(fx, fy)
        ball:setColorTransform(1, 0, 0, 1)
     
        arrow1:setVisible(true)
        arrow1:setPosition(fx, fy)
        arrow1:setRotation(angle)
     
        DEBUG:setText("  angle="..floor(angle)..", mtv_x="..floor(mtv_x)..", mtv_y="..floor(mtv_y))
    end
     
    local function screenTouched(e) -- TELEPORT THE BALL TO WHERE THE SCREEN IS TOUCHED
      local x, y = e.touch.x, e.touch.y
      ball.shape:moveTo(x, y)
      ball:setPosition(x, y)
      ball:setColorTransform(1, 1, 1, 1)
     
      arrow1:setVisible(false)
      DEBUG:setText("")
    end
     
    local function onEnterFrame(event) -- MAIN GAME LOOP
    	local timer = os.timer()
    	local dt = timer - currentTimer
     
      COLLIDER:update(dt) -- UPDATE COLLISION WORLD
    end
     
    COLLIDER:setCallbacks(onCollision) --SET COLLISION WORLD CALLBACK
     
    stage:addEventListener(Event.ENTER_FRAME, onEnterFrame)
    stage:addEventListener(Event.TOUCHES_BEGIN, screenTouched)
    stage:addEventListener(Event.TOUCHES_MOVE, screenTouched)

    Likes: misterhup, pie

    HardonCollider.png
    336 x 540 - 9K
    zip
    zip
    HardonCollider.zip
    213K
    +1 -1 (+2 / -0 ) Share on Facebook
  • Cool, thanks! I think I have to use Hardon or SAT, because I have an isometric world. Unless I can rotate ALL rectangles 45 degrees somehow!

    Out of SAT and Hardon, which is more efficient when it comes to rectangle collisions?
    My Gideros games: www.totebo.com
  • antixantix Member
    edited March 2016
    @totebo - It's hard to say. I didn't write either of those systems, just the examples that show their basic use.

    Hardon Collider actually uses GJK to detect rectangle collisions. It also utilizes a spatial hash like bump to increase it's performance. In my own tests I found Hardon Collider kind of slow but I was using 640 polygons for my walls in that experiment.

    the SAT example has no spatial hash so if you get to larger numbers of polygons the performance will degrade unless you implement some spatial hash or other algorithm to reduce collision checks.

    For an isometric game, bump should be all you require. I would think all collisions get handled by bump and then world objects are translated when drawn to appear to be in the correct perspective.

    It's an interesting question (about isometric collision) and one I could easily get side-tracked by [-X

    @jdbc shares some code for translation in his thread http://giderosmobile.com/forum/discussion/6054/isometric-endless-racing#Item_1 Check it out :)
  • Keeping the collisions non-rotated and separated from the gfx is a great idea. And worth side tracking I reckon. ;) Although isometric is probably a little tricky, because the perspective is usually a bit "squashed", which means the actual collisions would be diamonds rather than right-angled rectangles. Unless there is some " right-angle-ification" going on.
    My Gideros games: www.totebo.com
  • @antix I have to say, is really impressive what SAT can do, but isn't there a way to play with the object's anchor point? It seems that SAT doesn't allow scaling or changing the anchor point of a box. It seems that that's the only thing it is missing. I could be wrong tho, it won't be the first time :))
  • antixantix Member
    Perspective is the key word here. It doesn't matter what perspective you draw objects in, the underlying square grid system remains the same, and valid.

    Likes: jdbc

    +1 -1 (+1 / -0 ) Share on Facebook
  • piepie Member
    thanks for sharing @antix, I still don't need collisions but I am sure that this post will come in handy... :D

    I think it deserves to be moved to the Code Snippet section of the forum (if not included in a resource list inside the docs) to be easily found later.
  • antixantix Member
    Okay so I really am trying to avoid the stuff I should be doing (like exciting old scene management code *yawn*) so I have bashed together a little demonstration of Bump collision working with Isometric rendering...
    --[[
     
    ISO Collision Utilizing Bump.lua Example
    by Cliff Earl
    Antix Software
    March 2016
     
    This example serves to illustrate that you do not need to rotate or 
    perform any other hanky  operations to perform collision  detection 
    in an isometric game.
     
    The key point is that you don't ever need any isometric to cartesian 
    calculations  since  everything  takes  place  in a  grid that  uses 
    cartesian  coordinates. You  only need to  convert from cartesian to 
    isometric to draw your game world and objects.
     
    This  example draws a small isometric tile map to the screen. As it 
    draws the  map it creates a  collision rectangle for any  tile that 
    is solid (1 in this example) and adds it to a bump collision world. 
    It then creates 5-12 colored squares that move at a random speed at 
    a  random  angle. In  the main  loop  the  squares  are  moved  and 
    collisions checked  in the bump world. Any square that  hits a wall 
    will be bounced off the wall.
     
    As you will see, it does not matter how  you want to draw your game 
    objects. They can be drawn in isometric perspective,  topdown, side 
    on, etc. As long  as the world you want to  use is based  on a grid 
    filled with  AABB's (Axis Aligned Bounding Boxes),  bump will  work
    perfectly fine for collision detection and resolution.
     
    NOTES:
    This example uses the twoDToIso() code posted by jdbc in his thread 
    about his Endless Isometric Racing Game...
     
    <a href="http://giderosmobile.com/forum/discussion/6054/isometric-endless-racing#Item_2" target="_blank" rel="nofollow">http://giderosmobile.com/forum/discussion/6054/isometric-endless-racing#Item_2</a>
     
    --]]
     
    --require("mobdebug").start() -- ONLY REQUIRED FOR ZEROBRANE STUDIO
     
    stage:setOrientation(Stage.LANDSCAPE_LEFT)
     
    local random, sin, cos, rad = math.random, math.sin, math.cos, math.rad
     
    math.randomseed(os.time()) for i = 1, 4 do random() end -- INIT PRNG
     
    local function vector2(x, y) -- CREATE A NEW 2D VECTOR
      return {x = x, y = y}
    end
     
    WIDTH       = application:getContentWidth() -- SOME NASTY CONSTANTS
    HEIGHT      = application:getContentHeight()
    MIDX        = WIDTH * 0.5 - 32
    MIDY        = HEIGHT * 0.5
     
    TILESIZE    = 32
     
    ACTORSIZE  = 16
     
    MAPWIDTH    = 7
    MAPHEIGHT   = 7
     
    local bumpWorld = require("bump").newWorld() -- CREATE BUMP COLLISION WORLD
     
    local textureRegions = {
      {  0,  0, 64, 32}, -- 1 WALL TILE
      { 64,  0, 64, 32}, -- 2 DIRT TILE
      {128,  0, 64, 32}, -- 3 GRASS BUTTON
      {192,  0, 32, 16}, -- 4 BLUE ACTOR
      {192, 16, 32, 16}, -- 5 RED ACTOR
      {224,  0, 32, 16}, -- 6 CYAN ACTOR
      {224, 16, 32, 16}, -- 7 YELLOW ACTOR
    }
     
    local textureAtlas = Texture.new("images/isoblocks.png", true)
     
    local function newRegion(r) -- RETURNS A NEW TEXTURE REGION
    	local a = textureRegions[r]
    	return TextureRegion.new(textureAtlas, a[1], a[2], a[3], a[4])
    end
     
    local function setRegion(bitmap, r) -- SETS A BITMAPS TEXTURE REGION
      local region = bitmap.region
    	local a = textureRegions[r]
      region:setRegion(a[1], a[2], a[3], a[4])
      bitmap:setTextureRegion(region)
    end
     
    function newBitmap(r) -- CREATE NEW BITMAP
    	local a = textureRegions[r]
    	local region = TextureRegion.new(textureAtlas, a[1], a[2], a[3], a[4])
      local bitmap = Bitmap.new(region)
      bitmap.region = region
    --  bitmap:setAnchorPoint(0.5, 0.5)
      return bitmap
    end
     
    local function twoDToIso(x, y) -- FROM <a href="http://giderosmobile.com/forum/discussion/6054/isometric-endless-racing#Item_2" target="_blank" rel="nofollow">http://giderosmobile.com/forum/discussion/6054/isometric-endless-racing#Item_2</a>
    	local isoX = x -y
    	local isoY = (x + y) * 0.5
    	return isoX, isoY
    end
     
    local function setISOPosition(actor, x, y, w) -- SET BITMAPS X, Y POSITION IN ISOMETRIC SPACE
    	local isoX = (x + (w)) - y
    	local isoY = (x + y) * 0.5
     
      actor:setPosition(isoX + MIDX, isoY)
    end
     
    local function initMap(group, mapData) -- CREATE GRAPHIC OBJECTS AND COLLISION RECTS FOR TILE MAP
      local n = 1
      for r = 0, MAPHEIGHT - 1 do
        for c = 0, MAPWIDTH - 1 do
          local tile = mapData[n] -- GET NEXT TILE
          if tile == 1 then -- IF ITS A SOLID TILE, CREATE A COLLISION RECT AND ADD IT TO THE BUMP WORLD
            local rect = {isWall = true, x = c * TILESIZE, y = r * TILESIZE, w = TILESIZE, h = TILESIZE}
            bumpWorld:add(rect, rect.x, rect.y, rect.w, rect.h)
          end
          local bitmap = newBitmap(tile)
          local x, y = twoDToIso(c * TILESIZE, r * TILESIZE) -- CONVERT FROM CART TO ISO
          bitmap:setPosition(x + MIDX, y) -- SET ACCORDING TO ISO POSITION
          group:addChild(bitmap)
          n = n + 1 -- NEXT TILE TO FETCH
        end
      end
    end
     
    local function redrawMap(group, mapData) -- REDRAW THE TILE MAP (NOT USED IN EXAMPLE)
      local n = 1
      for r = 1, MAPHEIGHT do
        for c = 1, MAPWIDTH do
          local tile = mapData[n]
          local bitmap = group:getChildAt(n)
          setRegion(bitmap, tile) -- SET BITMAPS NEW REGION
          n = n + 1
        end
      end
    end
     
    local mapData = { -- MOST AWESOME TILE MAP EVER
      1,1,1,1,1,1,1,
      1,2,1,2,2,2,1,
      1,2,2,2,1,2,1,
      1,2,2,2,2,2,1,
      1,1,2,3,2,2,1,
      1,2,2,2,2,2,1,
      1,1,1,1,1,1,1,
    }
     
    local mapGroup = Sprite.new()
    stage:addChild(mapGroup)
     
    initMap(mapGroup, mapData)
     
    local actors = {}
     
    local function createActor()
      local actor = newBitmap(random(4, 7))
      stage:addChild(actor)
      local x = (TILESIZE * 3) + random(0, 16)
      local y = (TILESIZE * 4) + random(0, 16)
     
      bumpWorld:add(actor, x, y, ACTORSIZE, ACTORSIZE)
     
      local speed = random(12, 20)
      local angle = random(0, 360)
      actor.velocity = vector2(speed * cos(rad(angle - 90)), speed * sin(rad(angle - 90)))
      actor.position = vector2(x, y)
     
      setISOPosition(actor, x , y, ACTORSIZE)
      table.insert(actors, actor)
    end
     
    for i = 1, random(5, 12) do -- CREATE A RANDOM NUMBER OF ACTORS
      createActor()
    end
     
    local currentTimer = 0
    local function onEnterFrame(event) -- MAIN LOOP
    	local timer = os.timer()
    	local dt = timer - currentTimer
    	currentTimer = timer
     
      local function updateActor(actor, dt) -- UPDATES ACTOR
        local p, v = actor.position, actor.velocity -- GET POSITION AND VELOCITY
     
        p.x = p.x + v.x * dt -- MOVE ACTOR TO ITS DESIRED POSITION
        p.y = p.y + v.y * dt
     
        local filter = function(item, other)
          if other.isWall then return "bounce" end -- ONLY CHECK FOR WALLS IN EXAMPLE
        end
     
        local actualX, actualY, cols, len = bumpWorld:move(actor, p.x, p.y, filter)
        p.x, p.y = actualX, actualY
     
        for i=1,len do -- BOUNCE ACTOR OFF WALLS
          local col = cols[1]
          local bounciness = 1
          local nx, ny = col.normal.x, col.normal.y
          local vx, vy = v.x, v.y
          if (nx < 0 and vx > 0) or (nx > 0 and vx < 0) then
            vx = -vx * bounciness
          end
          if (ny < 0 and vy > 0) or (ny > 0 and vy < 0) then
            vy = -vy * bounciness
          end
          v.x, v.y = vx, vy
        end
        setISOPosition(actor, p.x, p.y, ACTORSIZE)
      end
     
      for i = #actors, 1, -1 do -- MOVE ACTORS ABOUT MAP AND BOUNCE THEM OFF WALLS
        updateActor(actors[i], dt)
      end
     
    end
     
    stage:addEventListener(Event.ENTER_FRAME, onEnterFrame)
    IsoBump.png
    496 x 380 - 13K
    zip
    zip
    IsoBump.zip
    12K
    +1 -1 (+2 / -0 ) Share on Facebook
  • Wow! Look forward to giving this a spin after the holidays. You really ARE trying to avoid doing something else aren't you? :) Awesome.

    Likes: antix

    My Gideros games: www.totebo.com
    +1 -1 (+1 / -0 ) Share on Facebook
  • antixantix Member
    Sure am, but now I ran out of excuses so I'll get cracking on scene management now :)
  • totebototebo Member
    edited April 2016
    Hey @antix, I've downloaded all these, really cool stuff.

    I now need to do something slightly different; skewing the perspective. With your Legendary Antix Hat on, how would I even start doing something like this? See illustration.
    Artboard 1.png
    653 x 387 - 16K
    My Gideros games: www.totebo.com
  • antixantix Member
    @totebo, having everything conform to a grid makes things a lot easier. Bump can of course handle any shapes you like. Does your isometric game have different sized tiles?
  • The game I'm building has an isometric perspective, but it's not divided into rigid tiles. The objects requiring collisions are of varying sizes, like the mockup above. I've started creating a test for this, but at the moment I feel like a blind man in a dark room. :)
    My Gideros games: www.totebo.com
  • antixantix Member
    edited April 2016
    @totebo, oops didn't see your pictures (cleans glasses) :)

    The actual collisions can still take place in the bump world and you can do some mathsy translation to determine where the objects should be drawn onscreen I think.

    So I took a break and made an example. There are some actors which move about the screen and bounce off the walls and each other. They are drawn in a skewed perspective.
    --[[
     
    Skewed Perspective Collision utilizing Bump.lua Example
    by Cliff Earl
    Antix Software
    April 2016
     
    Here is another  wee example  to show you  can have  many different 
    sized  rectangles colliding  in a bump  world and draw  them in any 
    crazy way  you like.  At the end of  the day  you just need  to use 
    some  maths to  translate the  coordinates  from bump to  draw your 
    objects on screen how you want them.
     
    The  function of  note in this  example is  setSkewedPosition(). It 
    transforms cartesian coordinates  to the desired skewed coordinates 
    for positioning sprites on screen.
     
    It is really that simple I think <img class="emoji" src="/resources/emoji/smile.png" title=":)" alt=":)" height="20" />
     
    --]]
     
    --require("mobdebug").start() -- ONLY REQUIRED FOR ZEROBRANE STUDIO
     
    stage:setOrientation(Stage.PORTRAIT)
    local random, insert, sin, cos, rad = math.random, table.insert, math.sin, math.cos, math.rad
    math.randomseed(os.time()) for i = 1, 4 do random() end -- INIT PRNG
     
    local function vector2(x, y) -- CREATE A NEW 2D VECTOR
      return {x = x, y = y}
    end
     
    local bumpWorld = require("bump").newWorld() -- CREATE BUMP COLLISION WORLD
     
    local shapes = {
      {texture = Texture.new("images/big.png", false), w = 32, h = 32},
      {texture = Texture.new("images/med.png", false), w = 16, h = 16},
      {texture = Texture.new("images/sml.png", false), w =  8, h =  8},
    }
     
    local function setSkewedPosition(actor, x, y) -- SET BITMAPS X, Y POSITION IN SKEWED ISOMETRIC SPACE
    	local skewedX = x + (y / 2)
      actor:setPosition(skewedX, y)
    end
     
    local walls = {
      {isWall = true, x =   0, y =   0, w = 192, h =   8}, -- TOP
      {isWall = true, x =   0, y = 184, w = 192, h =   8}, -- BOTTOM
      {isWall = true, x =   0, y =   8, w =   8, h = 176}, -- LEFT
      {isWall = true, x = 184, y =   8, w =   8, h = 176}, -- RIGHT
    }
    for i = 1, #walls do
      bumpWorld:add(walls[i], walls[i].x, walls[i].y, walls[i].w, walls[i].h) -- ADD WALLS TO BUMP AS COLLISION RECTANGLES
    end
    stage:addChild(Bitmap.new(Texture.new("images/walls.png", false))) -- LETS JUST SHOW AN IMAGE INSTEAD OF DRAWING <img class="emoji" src="/resources/emoji/wink.png" title=";)" alt=";)" height="20" />
     
    local actors = {}
     
    local function createActor(x, y)
      local shape = shapes[random(#shapes)] -- GET A RANDOM SHAPE
     
      local actor = Bitmap.new(shape.texture)
      stage:addChild(actor)
      actor:setColorTransform(random(), random(), random(), 1) -- RANDOMIZE ITS COLOR
      bumpWorld:add(actor, x, y, shape.w, shape.h)
     
      local speed = random(12, 20) -- SET ACTOR IN MOTION
      local angle = random(0, 360)
      actor.velocity = vector2(speed * cos(rad(angle - 90)), speed * sin(rad(angle - 90)))
      actor.position = vector2(x, y)
      actor.isShape = true
     
      setSkewedPosition(actor, x , y) -- MAGIC
      insert(actors, actor)
    end
     
    createActor(  24, 24) -- CREATE 9 ACTORS OF RANDOM SIZES
    createActor(  80, 24)
    createActor( 136, 24)
    createActor(  24, 80)
    createActor(  80, 80)
    createActor( 136, 80)
    createActor(  24, 136)
    createActor(  80, 136)
    createActor( 136, 136)
     
    local currentTimer = 0
    local function onEnterFrame(event) -- MAIN LOOP
    	local timer = os.timer()
    	local dt = timer - currentTimer
    	currentTimer = timer
     
      local function updateActor(actor, dt) -- UPDATES ACTOR
        local p, v = actor.position, actor.velocity -- GET POSITION AND VELOCITY
     
        p.x = p.x + v.x * dt -- MOVE ACTOR TO ITS DESIRED POSITION
        p.y = p.y + v.y * dt
     
        local filter = function(item, other)
          if other.isWall or other.isShape then return "bounce" end -- ONLY CHECK FOR WALLS IN EXAMPLE
        end
     
        local actualX, actualY, cols, len = bumpWorld:move(actor, p.x, p.y, filter)
        p.x, p.y = actualX, actualY
     
        for i=1,len do -- BOUNCE ACTOR OFF WALLS
          local col = cols[1]
          local bounciness = 1
          local nx, ny = col.normal.x, col.normal.y
          local vx, vy = v.x, v.y
          if (nx < 0 and vx > 0) or (nx > 0 and vx < 0) then
            vx = -vx * bounciness
          end
          if (ny < 0 and vy > 0) or (ny > 0 and vy < 0) then
            vy = -vy * bounciness
          end
          v.x, v.y = vx, vy
        end
        setSkewedPosition(actor, p.x, p.y)
      end
     
      for i = #actors, 1, -1 do -- MOVE ACTORS ABOUT MAP AND BOUNCE THEM OFF WALLS
        updateActor(actors[i], dt)
      end
    end
     
    stage:addEventListener(Event.ENTER_FRAME, onEnterFrame)
    The sprites in the example are predrawn skewed as I think I read in one of your other threads that skewing with matrix is bugged right now (like my beloved RenderTarget:clear() :(
    SkewedBump.png
    336 x 540 - 8K
    zip
    zip
    SkewedBump.zip
    37K
  • Amazing! Worked something similar up myself, but this is great for reference. The magic for me was the x=x+y. Thanks a lot @antix, you're too easy to distract!
    My Gideros games: www.totebo.com
  • antixantix Member
    @totebo, I had programmers block on my current experiment so it was useful to take some time off and make some examples yesterday to help other users with their coding problems. Taking a wee rest has me back on track now though ~:>
Sign In or Register to comment.