Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Physics switching off in tilting a ball through a maze when switching scenes.

edited January 2015 in General Posts: 18

Hello all, I am working on a game that involves tilting a ball through a randomly generated maze. A while ago, I posted a discussion question on how to apply physics to a randomly generated maze so that the ball doesn't pass through the walls of the maze, which the Codea community helped me solve. I'm also using a scene manager system (from BrainFox, off the Codea forums) and a button class together in a collection of classes in my program. When I tilt the ball to the end of the maze, the scene is supposed to switch to another scene, which works, but the problem is that when I switch back to the maze scene from another scene, the ball passes through all the walls in the maze. Does anyone know how to solve this problem either with the code I am currently using or with any other code? Any help would be greatly appreciated.

Thanks

Comments

  • Posts: 1,976

    Have you made sure that you're not deleting the physics bodies, or if you are, you're creating them again? (Applies to both the walls and the ball) it's a little hard to tell without some code.

  • dave1707dave1707 Mod
    Posts: 7,656

    @ChrisKarpinski Without knowing everything you're doing, if you're switching things, you need to destroy all the old physics objects and then create all the new physics objects.

  • edited January 2015 Posts: 18

    Here's a program that demonstrates the problem in my game:

    Main:


    function setup() Scene("Maze", MazeScene) Scene("Screen", Screen) Scene.Change("Maze") end function draw() Scene.Draw() end function touched(touch) Scene.Touched(touch) end function pb(x,y,x1,y1) Scene.Pb(x,y,x1,y1) end function createmaze(n, sx, sy, ex, ey) Scene.CreateMaze(n, sx, sy, ex, ey) end function push(a, b) Scene.Push(a, b) end function pop() Scene.Pop() end function getneighbor(x, y, d) Scene.GetNeighbor(x, y, d) end function notvisited(x, y) Scene.NotVisited(x, y) end function setvisited(x, y) Scene.SetVisited(x, y) end function getunvisitedneighbor(x, y) Scene.GetUnvisitedNeighbor() end function getdirection(x, y, nx, ny) Scene.GetDirection(x,y,nx,ny) end function erasewalls(x, y, nx, ny) Scene.EraseWalls(x, y, nx, ny) end
  • edited January 2015 Posts: 18

    Maze scene, originally retrieved from dave1707:


    MazeScene = class() function MazeScene:init(x) displayMode(FULLSCREEN) supportedOrientations(LANDSCAPE_ANY) c1=physics.body(CIRCLE, 15) c1.x=200 c1.y=20 c1.gravityScale=0 c1.sleepingAllowed=false tab1={} tab2={} EAST = 0 NORTH = 1 WEST = 2 SOUTH = 3 EWALL = 1 NWALL = 2 WWALL = 4 SWALL = 8 XOFF = 30 YOFF = 70 d=0 ALLWALLS = NWALL + EWALL + SWALL + WWALL VISITED = 16 NMAX = 15 N = 10 pN = 10 M = WIDTH XYOFF = XOFF if HEIGHT < WIDTH then M = HEIGHT XYOFF = YOFF end stack = {} startx = 1; starty = 1 endx = N; endy = N createmaze(N, startx, starty, endx, endy) --solvemaze() end -- This function gets called once every frame function MazeScene:draw() -- This sets a dark background color background(0,0,0) translate(0,100) strokeWidth(2) fill(255, 0, 0, 255) ellipse(c1.x, c1.y, 30) c1.x = c1.x+Gravity.x*15 c1.y = c1.y+Gravity.y*15 fill(255, 255, 255, 255) stroke(255, 255, 255, 255) for i=1, N do x = XOFF + (i-1)*S for j=1, N do y = YOFF + (j-1)*S c = maze[i][j] w = c%VISITED we = w%2 wn = math.floor(w/NWALL)%2 ww = math.floor(w/WWALL)%2 ws = math.floor(w/SWALL) if we == 1 then line(x+S+150, y-50, x+S+150, y-S-50) pb(x+S+150,y-50,x+S+150,y-S-50) end if wn == 1 then line(x+150, y-50, x+S+150, y-50) pb(x+150, y-50, x+S+150, y-50) end if ww == 1 then line(x+150, y-50, x+150, y-S-50) pb(x+150, y-50, x+150, y-S-50) end if ws == 1 then line(x+150, y-S-50, x+S+150, y-S-50) pb(x+150, y-S-50, x+S+150, y-S-50) end end end done=true if c1.x > 800 then Scene.Change("Screen") end end --print("x: "..CurrentTouch.x.." y: "..CurrentTouch.y) -- print("x: "..c1.x.." y: "..c1.y) function MazeScene:pb(x,y,x1,y1) local z if not done then for z=1,#tab1 do if tab1[z].x==x and tab1[z].y==y and tab1[z].z==x1 and tab1[z].w==y1 then return end end table.insert(tab1,vec4(x,y,x1,y1)) table.insert(tab2,physics.body(EDGE,vec2(x,y),vec2(x1,y1))) tab2[#tab2].sleepingAllowed=false end end function MazeScene:createmaze(n, sx, sy, ex, ey) if ex > N then ex = N; ey = N end S = math.floor((M - 2*XYOFF)/N) maze = {} for i=0, NMAX+1 do maze[i] = {} for j = 0, NMAX+1 do maze[i][j] = ALLWALLS if i == 0 or i == N+1 then maze[i][j] = maze[i][j] + VISITED elseif j == 0 or j == N+1 then maze[i][j] = maze[i][j] + VISITED end end end x = ex; y = ey sp = 1 setvisited(ex, ey) erasewalls(sx-1, sy, sx, sy) erasewalls(ex, ey, ex+1, ey) while true do nx, ny = getunvisitedneighbor(x, y) if nx < 0 then x, y = pop() if sp == 1 then break end else setvisited(nx, ny) push(x, y) erasewalls(x, y, nx, ny) x = nx; y = ny end end end function MazeScene:push(a, b) stack[sp] = a stack[sp+1] = b sp = sp + 2 end function pop() sp = sp - 1 b = stack[sp] a = stack[sp-1] sp = sp - 1 return a, b end function getneighbor(x, y, d) --print("x ", x, "y ", y, "d ", d) if d == EAST then x = x + 1 elseif d == NORTH then y = y + 1 elseif d == WEST then x = x -1 else y = y - 1 end return x, y end function notvisited(x, y) if x < 1 or x > N then return false end if y < 1 or y > N then return false end v = math.floor(maze[x][y] / VISITED) if v >= 1 then return false else return true end end function MazeScene:setvisited(x, y) maze[x][y] = maze[x][y] + VISITED end function getunvisitedneighbor(x, y) nu = 0 uds = {} for i=EAST, SOUTH do nx, ny = getneighbor(x, y, i) if notvisited(nx, ny)then nu = nu + 1 uds[nu] = i end end if nu == 0 then return -1, -1 end td = math.random(1, nu) nx, ny = getneighbor(x, y, uds[td]) return nx, ny end function getdirection(x, y, nx, ny) if nx > x then d = EAST elseif ny > y then d = NORTH elseif nx < x then d = WEST else d = SOUTH end return d end function MazeScene:erasewalls(x, y, nx, ny) d = getdirection(x, y, nx, ny) if d == EAST then maze[x][y] = maze[x][y] - EWALL maze[nx][ny] = maze[nx][ny] - WWALL elseif d == NORTH then maze[x][y] = maze[x][y] - NWALL maze[nx][ny] = maze[nx][ny] - SWALL elseif d == WEST then maze[x][y] = maze[x][y] - WWALL maze[nx][ny] = maze[nx][ny] - EWALL else maze[x][y] = maze[x][y] - SWALL maze[nx][ny] = maze[nx][ny] - NWALL end end
  • edited January 2015 Posts: 18

    Screen scene:


    Screen = class() local backButton function Screen:init(x) -- you can accept and set parameters here backButton = Button("Cargo Bot:Command Left", vec2(512, 384)) end function Screen:draw() -- Codea does not automatically call this method background(0, 255, 59, 255) fontSize(30) fill(0, 2, 255, 255) text("This button goes back to the maze scene.", 512, 430) backButton:draw() end function Screen:touched(touch) -- Codea does not automatically call this method backButton:touched(touch) if backButton.selected then Scene.Change("Maze") end end
  • edited January 2015 Posts: 18

    Collection of classes (as a blank file):

    -- HelperClass


    Button = class() function Button:init(buttonImage, buttonPosition) -- accepts the button image and location to draw it self.buttonImage = buttonImage self.buttonLocation = buttonPosition self.buttonTouchScale = 1.15 self.buttonImageSize = vec2(spriteSize(self.buttonImage)) self.currentButtonImage = self.buttonImage self.buttonTouchedImage = resizeImage(self.buttonImage, (self.buttonImageSize.x*self.buttonTouchScale), (self.buttonImageSize.y*self.buttonTouchScale)) self.selected = false end function Button:draw() -- Codea does not automatically call this method pushStyle() pushMatrix() noFill() noSmooth() noStroke() sprite(self.currentButtonImage, self.buttonLocation.x, self.buttonLocation.y) popMatrix() popStyle() end function Button:touched(touch) -- local varaibles local currentTouchPosition = vec2(touch.x, touch.y) -- reset touching variable to false self.selected = false if (touch.state == BEGAN) then if( (self.buttonLocation.x - self.buttonImageSize.x/2) < currentTouchPosition.x and (self.buttonLocation.x + self.buttonImageSize.x/2) > currentTouchPosition.x and (self.buttonLocation.y - self.buttonImageSize.y/2) < currentTouchPosition.y and (self.buttonLocation.y + self.buttonImageSize.y/2) > currentTouchPosition.y ) then self.currentButtonImage = self.buttonTouchedImage --print("Now touching! - began") else self.currentButtonImage = self.buttonImage --print("Not touching - began") end end if (touch.state == MOVING) then if( (self.buttonLocation.x - self.buttonImageSize.x/2) < currentTouchPosition.x and (self.buttonLocation.x + self.buttonImageSize.x/2) > currentTouchPosition.x and (self.buttonLocation.y - self.buttonImageSize.y/2) < currentTouchPosition.y and (self.buttonLocation.y + self.buttonImageSize.y/2) > currentTouchPosition.y ) then self.currentButtonImage = self.buttonTouchedImage --print("Now touching! - moving") else self.currentButtonImage = self.buttonImage --print("Not touching - moving") end end if (touch.state == ENDED) then if( (self.buttonLocation.x - self.buttonImageSize.x/2) < currentTouchPosition.x and (self.buttonLocation.x + self.buttonImageSize.x/2) > currentTouchPosition.x and (self.buttonLocation.y - self.buttonImageSize.y/2) < currentTouchPosition.y and (self.buttonLocation.y + self.buttonImageSize.y/2) > currentTouchPosition.y ) then self.selected = true --print("Activated button") end self.currentButtonImage = self.buttonImage end end function resizeImage(img, width, height) -- function from -- http://codea.io/talk/discussion/3490/importing-pics-from-dropbox/p1 local newImg = image(width,height) setContext(newImg) sprite( img, width/2, height/2, width, height ) setContext() return newImg end -- SceneManager -- -- This file lets you easily manage different scenes -- Original code from Brainfox, off the Codea forums Scene = {} local scenes = {} local sceneNames = {} local currentScene = nil setmetatable(Scene,{__call = function(_,name,cls) if (not currentScene) then currentScene = 1 end table.insert(scenes,cls) sceneNames[name] = #scenes Scene_Select = nil end}) --Change scene Scene.Change = function(name) currentScene = sceneNames[name] scenes[currentScene]:init() if (Scene_Select) then Scene_Select = currentScene end collectgarbage() end Scene.Draw = function() pushStyle() pushMatrix() scenes[currentScene]:draw() popMatrix() popStyle() end Scene.Touched = function(t) if (scenes[currentScene].touched) then scenes[currentScene]:touched(t) end end Scene.Keyboard = function() if (scenes[currentScene].keyboard) then scenes[currentScene]:keyboard(key) end end Scene.OrientationChanged = function() if (scenes[currentScene].orientationChanged) then scenes[currentScene]:orientationChanged() end end Scene.CreateMaze = function(n, sx, sy, ex, ey) if (scenes[currentScene].createmaze) then scenes[currentScene]:createmaze(n, sx, sy, ex, ey) end end Scene.Push = function(a, b) if (scenes[currentScene].push) then scenes[currentScene]:push(a, b) end end Scene.Pop = function() if (scenes[currentScene].pop) then scenes[currentScene]:pop() end end Scene.GetNeighbor = function(x,y,d) if (scenes[currentScene].getneighbor) then scenes[currentScene]:getneighbor(x,y,d) end end Scene.NotVisited = function(x,y) if (scenes[currentScene].notvisited) then scenes[currentScene]:notvisited(x, y) end end Scene.SetVisited = function(x, y) if (scenes[currentScene].setvisited) then scenes[currentScene]:setvisited(x, y) end end Scene.GetUnvisitedNeighbor = function(x, y) if (scenes[currentScene].getunvisitedneighbor) then scenes[currentScene]:getunvisitedneighbor(x, y) end end Scene.Pb = function(x, y, x1, y1) if (scenes[currentScene].pb) then scenes[currentScene]:pb(x, y, x1, y1) end end Scene.GetDirection = function(x,y,nx,ny) if (scenes[currentScene].getdirection) then scenes[currentScene]:getdirection(x,y,nx,ny) end end Scene.EraseWalls = function(x, y, nx, ny) if (scenes[currentScene].erasewalls) then scenes[currentScene]:erasewalls(x, y, nx, ny) end end
  • dave1707dave1707 Mod
    Posts: 7,656

    @ChrisKarpinski I added the 3 ~'s to your code so it's formatted on the forum correctly.

  • @dave1707 Thanks Dave. Sorry for the formatting error, I wasn't sure how to format it properly in the forum.

  • @dave1707 Also how do you delete and create new physics bodies?

  • Posts: 1,976

    @ChrisKarpinski Can you copy all of your code and put it into one big post, or put it on GitHub or something? Long press on your project's icon in the project viewer and choose "copy," it'll copy all the tabs and then we can long-press the "new project" button and paste it with all the tabs pre-made.

    Also, you already know how to create new physics bodies, just use physics.body() with all your parameters, set its values, etc. For deleting them, just use body:destroy()

  • dave1707dave1707 Mod
    edited January 2015 Posts: 7,656

    @ChrisKarpinski See the build in documentation under physics, body:destroy. Creating new physics bodies is just the standard code.

  • @SkyTheCoder Yes, here's the link to the file with my code in Github: https://github.com/ChrisKarpinski/physics-maze-/blob/master/README.md

  • @dave1707 To destroy physics bodies that are edges, would you just assign a variable to them and destroy them as shown in documentation?

  • Posts: 1,976

    @ChrisKarpinski You could have a table of all the physics bodies, and destroy() each one of them before you change the scene.

  • edited January 2015 Posts: 18

    This is the code I tried for destroying bodies:


    if c1.x > 800 then c1:destroy() c1 = nil Scene.Change("Word flash") end
  • dave1707dave1707 Mod
    Posts: 7,656

    @ChrisKarpinski When you switch from one maze to the next, you need to destroy the ball and the edges of the old maze, then create the new ball and edges of the new maze. Any physics.body needs to be destroyed. c1 for the ball and all the edges created in tab2.

  • IgnatzIgnatz Mod
    edited January 2015 Posts: 5,396

    @ChrisKarpinski - there are a couple of gotchas.

    A physics body continues to exist until it is deleted with the destroy command, or until no variables refer to it (ie you no longer have a way to "talk" to it).

    So if we take this example

    p = physics.body(....)
    -- much later you get rid of it
    p:destroy() --A
    p=nil          --B
    

    Either of the lines marked A and B will get rid of the physics object. However, there is a big timing difference. Method A will get rid of it straight away, but method B will only destroy it when "garbage is collected", something that can take up to a minute. During that time, you will have an invisible (because you will think it has gone, and stop drawing it) zombie object on your screen, causing invisible collisions.

    So you need both A to destroy it immediately, and B so your code doesn't try to draw it once it's destroyed.

    If you define a physics object in a temporary local variable, or not in a variable at all, it will exist for a little while, but only until the garbage truck comes round. So yes, physics objects need to be stored in global variables.

  • @Ignatz I tried that and the ball got destroyed as well as each element in tab2, which contains the physics body edges in the maze. I tested with text to show the value of the number of items in tab2 before and after the ball goes through the maze and the scene switches. I noticed that when the the ball gets to the position at end of the maze, the number of items (physics edges) in tab2 is 0. But when I go back to the maze, for some reason new edges are not created and the text showing the number of items in tab2 (#tab2) is still 0, even though I tested the pb function that makes the physics bodies and it seemed to work. So I'm not sure why the process of creating the new physics edges doesn't seem to work.

    Does my code below of destroying the physics bodies in my maze look right? The counter variable is just a variable I created to go through each item of the tab2 table to destroy the physics edges.

    if c1.x > 800 then
            c1:destroy()
            c1=nil
            while counter <= #tab2 do
                tab2[counter]:destroy()
                tab2[counter]=nil
                counter = counter + 1
            end
            if counter > #tab2 then
            Scene.Change("Screen")
        end
    
            end
    
  • dave1707dave1707 Mod
    Posts: 7,656

    @ChrisKarpinski Try destroying the tab2 contents in the reverse order. You might be altering the table contents and not destroying everything.


    counter=#tab2 while counter>0 then tab2[counter]:destroy() tab2[counter]=nil counter=counter-1 end
  • dave1707dave1707 Mod
    Posts: 7,656

    @ChrisKarpinski I just tried some code similar you yours above and when you set tab2[counter]=nil, that entry is now the end of the table. So you've been destroying only the first entry of the table and leaving all the other physics bodies as live objects.

  • Posts: 1,976

    @Ignatz Actually, the bodies will hang around, even if there is no value to store them...just setting them to nil doesn't do anything, because the physics engine has its own cache of bodies in the simulator (and that list can only be affected by the functions physics.body() and body:destroy())

  • IgnatzIgnatz Mod
    Posts: 5,396

    @SkyTheCoder - Not true, as this code proves. The floor variable is set to nil after 2 seconds. The floor disappears as soon as garbage is collected, either by pressing the button, or by waiting for Codea to do it itself. (It used to collect garbage every 60 seconds or so, but now it seems to only do so every five minutes or so, so be patient).

    function setup()
        a=physics.body(EDGE,vec2(0,0),vec2(WIDTH,0)) --floor
        a.restitution=1
        b=physics.body(CIRCLE,50)
        b.x=WIDTH/2
        b.y=HEIGHT/2
        parameter.text("Time","")
        parameter.action("CollectGarbage",function() collectgarbage() end)
    end
    
    function draw()
        background(50)
        fill(255,0,0)
        ellipse(b.x,b.y,100)
        if ElapsedTime>2 then a=nil end
        Time=math.floor(ElapsedTime)
    end
    
  • @dave1707 I just tried starting from the end of tab2 and doing what you showed and it worked, I tested it using some text and once the ball reached the end, #tab2 was 0. But when I went back to the maze after switching scenes, the text of #tab2 still showed 0, so for some reason new edges are not being created.

  • dave1707dave1707 Mod
    Posts: 7,656

    @ChrisKarpinski Are the edges being created correctly when the program first starts. If so, now the fun begins. Start putting a print statements in your code and try to see if your code is executing how you think it's supposed to. Start where you build tab2 with the edges.

  • @dave1707 I tested the way that the edges are being created and it seems correct to me, the edges are created through a pb function with this code:


    function MazeScene:pb(x,y,x1,y1) local z if not done then for z=1,#tab1 do if tab1[z].x==x and tab1[z].y==y and tab1[z].z==x1 and tab1[z].w==y1 then return end end table.insert(tab1,vec4(x,y,x1,y1)) table.insert(tab2,physics.body(EDGE,vec2(x,y),vec2(x1,y1))) tab2[#tab2].sleepingAllowed=false end end

    But the pb function only seems to work at the first time you go through the maze, then it does not make any more edges in tab2 and #tab2 still remains 0 at all times in the program after all of the original edges and ball are destroyed. I used print statements to check how many edges were in tab2 and I'm pretty sure that the problem lies in the pb function because even if I try to make new edges in setup, it creates them and adds them to tab2 at first but they are not created after the original edges are destroyed and after I first change scenes and go back to the Maze scene. Do you know why the pb function only seems to create the edges before the original set of edges is destroyed, or is something else wrong?

  • IgnatzIgnatz Mod
    Posts: 5,396

    The most obvious thing I can think of is that you are destroying the edges in table 2 but not clearing table 1, but maybe that is too obvious

  • @Ignatz Yeah I tried that, tab1 contains just the walls of the maze, no physics bodies, and new walls of the maze were being created anyway, so it didn't change much.

  • dave1707dave1707 Mod
    Posts: 7,656

    @ChrisKarpinski I don't know why, but I enjoy debugging programs. It's like detective work. Add the line I indicate below to your code.

        if c1.x > 800 then
            Scene.Change("Screen")
            done=false          -- add this line to your code.
        end
    
  • @dave1707 Wow it works, you are a genius! I can't believe how just that one simple line of code was causing the whole problem. It's amazing how just one line of code can change so much. Thanks so much Dave and to everyone that helped!

  • dave1707dave1707 Mod
    Posts: 7,656

    @ChrisKarpinski After I loaded your code, it took all of 5 minutes to find the problem. The last couple of years before I retired, I was in support and did nothing but debug programs. I developed a pretty good routine for finding problems and correcting them.

Sign In or Register to comment.