Howdy, Stranger!

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

Collision box problem (not built in)

edited October 2016 in Questions Posts: 13

So I decided to make my own button/collision function which pretty much just tests if a position (x, y)are in a certain area (x,y,w,h):

function button(actX,actY,x,y,w,h)
    --test if actX and actY are in the area defined by x,y,w,h
    if (x + w/2) > actX
    and actX > (x - w/2)
    and (y + h/2) > actY
    and actY > (y - h/2) then
        --Special instruction for if actX or actY are the CurrentTouch
        if actX == CurrentTouch.x
        or actY == CurrentTouch.y then
            if CurrentTouch.state == ENDED then
                return false
            end
        end
        -- if in the area, return true, else return false
        return true
    else
        return false
    end
end

Then I made a table consisting of the spawn position, and all boxes that make up a level in the game, and a variable that states the level that loads:

levels = {
    l0 = {
        -- Spawn location for charactor
        playerspawn = {x = WIDTH/2,y = HEIGHT/2},
        -- collision is a table consisting of tables that define a collision area
        collision = {
            -- Collision box 1 (the one the charactor isn't colliding with)
            {p = false, r = 0, x = WIDTH/2, y = 6, w = WIDTH, h = 12},
            -- Collision box 2 (this box works)
            {p = true, r = 0, x = 250, y = 200, w = 200, h = 12}
        }
    },
    l1 = {
        -- this is for another level but is not used yet
        playerspawn = {x = 10,y = HEIGHT/10}
    }
}
-- this is the level that is being loaded
currentlevel = levels.l0

Then I made my charactor able to collide with all rectangles stated in currentlevel.collision (levels.l0.collision) using the button function and a for loop and drew them on screen with rect():

for k,v in pairs(currentlevel.collision) do
        -- for all the collision boxes, make a button to detect somthing
        if button(self.x,self.y,v.x,v.y,v.w,v.h) == true then
-- self.collided tells the charactor whether somthing has collided with one of the boxes or not.
            self.collided = true
        else
            self.collided = false
        end
        -- this colors the wall black or white based on a bool.
        if v.p == true then
            fill(255, 255, 255, 255)
        else
            fill(0, 0, 0, 255)
        end
        -- this draws a rectangle for each of the areas so i can see them on screen
        pushMatrix()
        rect(v.x,v.y,v.w,v.h)
        popMatrix()
    end

However, the charactor only collides with the second rectangle in the table, and not the first rectangle. self.collided doesn't seem to get set to true when the charactor touches the first collision area. Can someone help me find where, and what the problem(s) are that are causing this?

Tagged:

Comments

  • edited October 2016 Posts: 28

    There appears to be no trouble with the code you post.

  • dave1707dave1707 Mod
    Posts: 7,555

    @PlatniumFrog I'm not really following your code, so I thought I would modify something I already have that I think might help you. Hold the ipad flat and tilt it to move the green dot over the rectangles.

    displayMode(FULLSCREEN)
    
    function setup()
        rectMode(CENTER)
        boxTab={}
        for z=1,20 do
            table.insert(boxTab,box(math.random(80,WIDTH-80),
                    math.random(40,HEIGHT-40),80,40))       
        end
        player=vec2(math.random(WIDTH),math.random(HEIGHT))
    end
    
    function draw()
        background(40, 40, 50)
        for a,b in pairs(boxTab) do
            b:collide(player.x,player.y)
            fill(255)
            if b.inBox then
                fill(255,0,0)
            end
            b:draw()
        end
        fill(0, 255, 59, 255)
        ellipse(player.x,player.y,10)
        player.x=player.x+Gravity.x*10
        player.y=player.y+Gravity.y*10
    end
    
    box=class()
    
    function box:init(x,y,w,h)
        self.x=x
        self.y=y
        self.w=w
        self.h=h
        self.inBox=false
    end
    
    function box:draw()
        rect(self.x,self.y,self.w,self.h)
    end
    
    function box:collide(x,y)
        self.inBox=false
        if x>self.x-self.w/2 and x<self.x+self.w/2 and
                y>self.y-self.h/2 and y<self.y+self.h/2 then
            self.inBox=true            
        end
    end
    
  • I added some comments above so hopefully it is easier to read.

  • edited October 2016 Posts: 13

    Btw, I am making a 2D portal game, an I need the levels in that format so I can easily just go in and add a box when needed.

  • dave1707dave1707 Mod
    Posts: 7,555

    @PlatniumFrog Just to get your code to execute, I have to make a lot of assumptions as to what code needs to be added. You have self variables, but you didn't include any class as to what or how those self variables are used or defined. It's hard to try to figure out why someone's code doesn't work when a lot of code has to be added to get it to execute. What I add might not be anything close to what you have, so what works for me might not be what you have. But anyways, what I see wrong so far is in the function button. Your if statements looks like they are checking if a rect is drawn using the CENTER values, but the default rect command draws a rect using CORNER values. Maybe you have rectMode(CENTER) set, but I don't know.

  • edited October 2016 Posts: 13

    Yeah, I do have rect mode center activated. Also, here is the code:
    Main:

    -- Portal 2D
    
    -- Use this function to perform your initial setup
    function setup()
        print("Oh! It's you! It's been a long time!")
        touches = 0
        fa = {}
        g = 24
        Player:init()
        rectMode(CENTER)
    end
    function touched(touch)
        if touch.state == BEGAN then
            touches = touches + 1
        end
        if touch.state == ENDED then
            touches = touches - 1 
        end
        if touch.state == ENDED then
            fa[touch.id] = nil
        else
            fa[touch.id] = touch
        end
    end
    function averagetouch(axis)
        -- this function will be used to get the average position of every finger tounching the screen
            local xa = 0
            local ya = 0
            local fc = 0
        for k,v in pairs(fa) do
            fc = fc + 1
            xa = xa + v.x
            ya = ya + v.y
        end
        local xb = 0
        local yb = 0
    
        if CurrentTouch.state == BEGAN 
        or CurrentTouch.state == MOVING then
            xb = xa/fc
            yb = ya/fc
        end
        if axis == "x" then
            return xb
        end
        if axis == "y" then
            return yb
        end
    end
    function button(actX,actY,x,y,w,h)
        if (x + w/2) > actX
        and actX > (x - w/2)
        and (y + h/2) > actY
        and actY > (y - h/2) then
            if actX == CurrentTouch.x
            or actY == CurrentTouch.y then
                if CurrentTouch.state == ENDED then
                    return false
                end
            end
            return true
        else
            return false
        end
    end
    -- This function gets called once every frame
    function draw()
        background(127, 127, 127, 255)
    
        Player:draw()
    end
    

    Level:

    Level = class()
    levels = {
        l0 = {
            playerspawn = {x = WIDTH/2,y = HEIGHT/2},
            collision = {
                {p = false, r = 0, x = WIDTH/2, y = 45, w = 100, h = 50},
                {p = true, r = 0, x = WIDTH/2, y = 10, w = WIDTH, h = 20}
            }
        },
        l1 = {
            playerspawn = {x = 10,y = HEIGHT/10}
        }
    }
    function Level:draw()
    
    end
    currentlevel = levels.l0
    

    Player:

    Player = class()
    function Player:init()
        self.x = currentlevel.playerspawn.x
        self.y = currentlevel.playerspawn.y
        self.r = 0
        self.s = 1
        self.d = "left"
        self.armx = self.y
        self.army = self.x
        self.armr = 0
        self.vx = 0
        self.vy = 1
        self.collided = false
        self.av = 0
        parameter.watch("Player.collided")
        parameter.watch("Player.av")
    end
    function Player:move()
    
    end
    function Player:arm()
    
    end
    function Player:draw()
        self.av = averagetouch("x")
        for k,v in pairs(currentlevel.collision) do
            if button(self.x,self.y,v.x,v.y,v.w,v.h) == true then
                if touches == 1 then
                    if CurrentTouch.deltaY >= 20 then
                        self.vy = self.vy + CurrentTouch.deltaY 
                    else
                        if self.x > (v.x+(v.w/2))+1 and self.x < (v.x-(v.w/2))-1 then
                            self.y = (v.y+(v.h/2))-1
                        end
                    end
    
                    if self.x < (v.x+(v.w/2)+5) and self.y < v.y+(v.h/2) then
                        self.vx = -(CurrentTouch.deltaX/2)
                    else
                        self.vx = CurrentTouch.deltaX/2
                    end
                    if self.x < (v.x-(v.w/2)-5) and self.y < v.y+(v.h/2) then
                        self.vx = -(CurrentTouch.deltaX/2)
                    else
                        self.vx = CurrentTouch.deltaX/2
                    end
                    if CurrentTouch.deltaX < 0 then
                        self.d = "right"
                    end
                    if CurrentTouch.deltaX > 0 then
                        self.d = "left"
                    end
                end
                self.vx = self.vx - (self.vx/10)
                self.y = self.y + self.vy
                self.vy = 0
                self.collided = true
            else
                if self.vy == 0 then
                    self.vy = 1
                end
                self.vy = self.vy + (self.vy/g)
                self.y = self.y - self.vy
                self.vx = self.vx - (self.vx/20)
                self.collided = false
            end
            if v.p == true then
                fill(255, 255, 255, 255)
            else
                fill(0, 0, 0, 255)
            end
            pushMatrix()
            rect(v.x,v.y,v.w,v.h)
            popMatrix()
        end
    
        self.x = self.x + self.vx
        if touches == 2 then
            if button(averagetouch("x"),averagetouch("y"),self.x - (self.s*-0.2),self.y +(self.s*46),250,250) == false then
            self.armx = (self.x -(self.s*-0.2)) + (math.cos(self.armr)*(self.s*22))
            self.army = (self.y +(self.s*46)) + (math.sin(self.armr)*(self.s*22))
            self.armr = math.atan(averagetouch("y")-self.army,averagetouch("x")-self.armx)
            if averagetouch("x") < self.x then
                self.d = "right"
            end
            if averagetouch("x") > self.x then
                self.d = "left"
            end
            end
        else
            self.armr = math.atan(-1,0)
            self.armx = (self.x -(self.s*-0.2)) + (math.cos(self.armr)*(self.s*22))
            self.army = (self.y +(self.s*46)) + (math.sin(self.armr)*(self.s*22))
        end
        ellipse()
        noSmooth()
        pushStyle()
        pushMatrix()
        translate(self.x,self.y+(32*self.s))
        rotate(self.r)
        scale(self.s)
        if self.d == "left" then
            sprite("Dropbox:ChellR")
        end
        if self.d == "right" then
            sprite("Dropbox:ChellL")
        end
        popMatrix()
        if touches == 2 then
            Player:arm()
        end
        pushMatrix()
        translate(self.armx,self.army)
        rotate(math.deg(self.armr))
        scale(self.s)
        sprite("Dropbox:ArmBR")
        popMatrix()
        popStyle()
    
    end
    

    Assets:
    for "Dropbox:ChellR" in script "Player"
    https://www.dropbox.com/s/pydm11kvlhdske2/ChellR.png?dl=0
    For "Dropbox:ChellL" in script "Player"
    https://www.dropbox.com/s/fww5mkqo7kdh3zo/ChellL.png?dl=0
    For "Dropbox:ArmBR" in script "Player"
    https://www.dropbox.com/s/m33byfalydtqn3b/ArmOR.png?dl=0

    Controls:
    Swipe left to move left
    Swipe left to move left
    Swipe up to jump
    Use two fingers to aim the portal gun
    (Portal gun will aim at the center point of the two fingers)

    Notes:
    Also I do need to clarify that I deleted the collided variable and just put everything that the charactor does when colliding directly into the statement since my last post. This "fixed" the problem, but it also didn't. The charactor collides with both rectangles, but if I add more, it breaks the collision for the boxes that are already there. I was considering changing self.collided to a table that contains the boxes that the charactor is currently colliding with instead of a bool. Instead of querying every box in the level, I could pull from self.collided to see how the charactor can move.

  • dave1707dave1707 Mod
    Posts: 7,555

    @PlatniumFrog What are you doing with self.collided. You set it to false in Player:init then you set it to true or false in Player:draw but you don't do anything else with it.

  • dave1707dave1707 Mod
    Posts: 7,555

    @PlatniumFrog I think your problem is with self.vy . In Player.draw, you loop thru currentlevel.collision and you set self.vy to 0 when there's a collision, but you set it back to 1 when there isn't a collision. If you have a lot of collisions defined in the table and the collision isn't the last one, you'll set self.vy from 0 back to 1.

  • I explained what happened to self.collided in the notes section in my post but I'll explain it again, I originally had it set to true when a player collided with something, and false when the charactor wasn't colliding. Then I had an if else statement saying what to do if the charactor had collided.

    self.vy, when the charactor isn't colliding with something, has to be set to 1 or else the gravity equation I put it throught each frame when not colliding doesn't work.

  • I'll experiment with it though. Thank you for the help.

  • edited October 2016 Posts: 13

    Replace player:draw() with the code below. Tap the screen to move the charactor to your finger. This method does not use self.vy. Notice how when the charactor is touching the black collision box, self.collided doesn't get set to true:

    function Player:draw()
        self.av = averagetouch("x")
        for k,v in pairs(currentlevel.collision) do
            if button(self.x,self.y,v.x,v.y,v.w,v.h) == true then
                self.collided = true
            else
                self.collided = false
            end
            if v.p == true then
                fill(255, 255, 255, 255)
            else
                fill(0, 0, 0, 255)
            end
            pushMatrix()
            rect(v.x,v.y,v.w,v.h)
            popMatrix()
        end
        self.x = CurrentTouch.x
        self.y = CurrentTouch.y
        if touches == 2 then
            if button(averagetouch("x"),averagetouch("y"),self.x - (self.s*-0.2),self.y +(self.s*46),250,250) == false then
            self.armx = (self.x -(self.s*-0.2)) + (math.cos(self.armr)*(self.s*22))
            self.army = (self.y +(self.s*46)) + (math.sin(self.armr)*(self.s*22))
            self.armr = math.atan(averagetouch("y")-self.army,averagetouch("x")-self.armx)
            if averagetouch("x") < self.x then
                self.d = "right"
            end
            if averagetouch("x") > self.x then
                self.d = "left"
            end
            end
        else
            self.armr = math.atan(-1,0)
            self.armx = (self.x -(self.s*-0.2)) + (math.cos(self.armr)*(self.s*22))
            self.army = (self.y +(self.s*46)) + (math.sin(self.armr)*(self.s*22))
        end
        ellipse()
        noSmooth()
        pushStyle()
        pushMatrix()
        translate(self.x,self.y+(32*self.s))
        rotate(self.r)
        scale(self.s)
        if self.d == "left" then
            sprite("Dropbox:ChellR")
        end
        if self.d == "right" then
            sprite("Dropbox:ChellL")
        end
        popMatrix()
        if touches == 2 then
            Player:arm()
        end
        pushMatrix()
        translate(self.armx,self.army)
        rotate(math.deg(self.armr))
        scale(self.s)
        sprite("Dropbox:ArmBR")
        popMatrix()
        popStyle()
    
    end
    
  • dave1707dave1707 Mod
    edited October 2016 Posts: 7,555

    @PlatniumFrog Actually it does get set to true, but the false of the second compare shows. Flip the lines in collision so the second line comes first and you'll see the black box is true, but the white one is false. self.collided should be set to false at the start of the compare and not set to false in the loop if it was set to true.

  • dave1707dave1707 Mod
    Posts: 7,555

    @PlatniumFrog Try this. Make these changes to the beginning of player:draw().

    function Player:draw()
        self.av = averagetouch("x")
        self.collided=false                 -- change here
        for k,v in pairs(currentlevel.collision) do
            if button(self.x,self.y,v.x,v.y,v.w,v.h) == true then
                self.collided = true
            end                                   -- change here
            if v.p == true then
                fill(255, 255, 255, 255)
            else
                fill(0, 0, 0, 255)
            end
            pushMatrix()
            rect(v.x,v.y,v.w,v.h)
            popMatrix()
        end
    
  • I see, maybe you found the solution...

  • I tried it. It didn't work, But I have another solution. By making self.collision a table, adding everything the charactor is touching to the table, checking if there are values in the table, and if so executing code, and then clearing the table at the end of each frame. This way since self.collided is not a bool, it wont be set to false when ever there are two or more collision boxes to evaluate.

  • dave1707dave1707 Mod
    edited October 2016 Posts: 7,555

    I added more entries to the collision table and each one set self.collided to true when I was in the rectangle. If it's not working for you, then you're still doing something wrong. By making self.collided a table, you're just adding more unnecessary code.

    EDIT: I'm talking about the second player:draw routine you posted. If you're trying to use self.collided in your original player:draw function, it won't work because you're changing the self.vx, vy, y variables each time in the loop. You need to take those updates out of the loop. Outside of the loop check self.collided and do your calculations based on if self.collided is true or false.

Sign In or Register to comment.