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

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

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.

@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.

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

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

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

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

@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?

@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()

@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?

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

This is the code I tried for destroying bodies:


if c1.x > 800 then
        c1:destroy()
        c1 = nil
        Scene.Change("Word flash")
    end

@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.

@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

@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