Vertical scrolling rectangles

Okay I’m back to working on the game I used the swipe functions for(school and work took my focus) I’ve been OG looking at the flappy bird example but there’s so much going on I’m not sure what I need and what I can leave. My goal is to create the flappy bird pipes but vertical instead of horizontal. They scroll down and your trying to get the ball go through the gaps and keep going with getting caught and forced to the floor of the phone screen and die. Pipes offscreen unload while new ones load at the top. …. I’m feeling reaaaaaally sucky at this right.

There’s more than one way to skin a cat. I’m sure Dave is about to post a code solution for you any moment now. But I would advise you into thinking more about your goal. Here’s two thoughts that i had: pipes are an image that moves across the screen, so I could setup a bunch of pipes at different x,y positions and then move the camera, or I could setup a few pipes offscreen and then move the pipes themselves across the screen and swap them back one they reach the edge. All a pipe is an image and possibly a collision body, but you can also just do AABB testing on the raw image boxes.

@Dezmo Will this work for you. You’ll have to change the values of squaresAcross and dist to fit whatever device you’re running this on. It will continue to scroll down creating a new pipe.

PS. Made a correction. Tried this on my iPhone, it’s going to need more work.

viewer.mode=FULLSCREEN

function setup()
    squaresAcross=10    -- number of squares across the screen
    dist=300    -- distance between rows of pipes
    tab={}
    l=math.random(squaresAcross)   -- random left squares
    r=squaresAcross-l  -- total squares minus number of left squares
    table.insert(tab,vec3(l,r,HEIGHT))
end

function draw()
    background(0)
    -- draw pipe. when down dist, add another pipe to table
    for a,b in pairs(tab) do
        pipe(b.x,b.y,b.z)
        b.z=b.z-1
        if b.z==HEIGHT-dist then
            l=math.random(squaresAcross)   -- random left squares
            r=squaresAcross-l  -- total squares minus number of left squares
            table.insert(tab,vec3(l,r,HEIGHT))
        end
    end
    -- pipe off bottom of screen, remove from table 
    for a,b in pairs(tab) do
        if b.z<-100 then
            table.remove(tab,a)
        end
    end
end

function pipe(left,right,pos)
    for x=0,left-1 do
        sprite(asset.builtin.Platformer_Art.Block_Brick,35+x*70,pos)
    end
    for x=0,right-1 do
        sprite(asset.builtin.Platformer_Art.Block_Brick,WIDTH-35-x*70,pos)
    end
end

Here’s another version. Change the size variable to change the size of the squares. Change the squaresAcross to get the correct number of squares. Change dist for the distance between the rows.

On my iPhone 8, values of 17 squaresAcross, dist 200, and size 20 seemed OK.

viewer.mode=FULLSCREEN

function setup()
    squaresAcross=25   -- number of squares across the screen
    dist=300    -- distance between rows of pipes
    size=30
    tab={}
    l=math.random(squaresAcross)   -- random left squares
    r=squaresAcross-l  -- total squares minus number of left squares
    table.insert(tab,vec3(l,r,HEIGHT))
end

function draw()
    background(0)
    -- draw pipe. when down dist, add another pipe to table
    for a,b in pairs(tab) do
        pipe(b.x,b.y,b.z)
        b.z=b.z-1
        if b.z==HEIGHT-dist then
            l=math.random(squaresAcross)   -- random left squares
            r=squaresAcross-l  -- total squares minus number of left squares
            table.insert(tab,vec3(l,r,HEIGHT))
        end
    end
    -- pipe off bottom of screen, remove from table 
    for a,b in pairs(tab) do
        if b.z<-100 then
            table.remove(tab,a)
        end
    end
end

function pipe(left,right,pos)
    for x=0,left-1 do
        sprite(asset.builtin.Platformer_Art.Block_Brick,size/2+x*size,pos,size)
    end
    for x=0,right-1 do
        sprite(asset.builtin.Platformer_Art.Block_Brick,WIDTH-size/2-x*size,pos,size)
    end
end

Oooh I really like the idea of camera scrolling And combining it with Dave’s example…some how.

And now for @dave1707

I have a few questions as a noob
inhale

Why the use of a Z coordinate?

What do the variables inside the () like in “function pipe(left,right, pos)” do in general?

What’s a,b in “a,b in pairs” represent?

And how you get away with not defining “pos” with a value?

Can you describe what the “for (thing) do (thing)” are doing like “x=0,left-1”

Thank you sir for your knowledge and experience

@Dezmo

I’m not using a Z coordinate. If you’re referring to b.z, that just the way to access vec variables. vec2 is x,y. vec3 is x,y,z. vec4 is x,y,z,w.

The left, right, pos are: left is the number of squares to draw from the left side. right is the number of squares to draw from the right side. pos is the screen position to draw the pipes as they move down the screen.

The a,b in the for loop are just variables the contain values that are returned from the for loop iterating thru the table. The variable a will just be a count, 1 thru the number of table values. The variable b will contain the value in the table. In this case, a vec3. To access each of the 3 values, that would be b.x, b.y, b.z . The variables a,b can be any name. I just tend to use a,b .

The variable pos will contain the table value b.z .

The for thing using the left and right -1 is just executing the sprite code a certain number of times. Since I’m starting at 0, I want to go one less than the value I’m passing to the function.

All of this probably doesn’t make a whole lot of sense right now, but as you code more it will become normal. I’ve been coding for so long that I don’t even think about writing something. It’s like I already know what to type without thinking about what needs to be done.

Ooooh okay I see the working connection between pipe(b.x,b.y,b.y) and pipe(left,right,pos) now. And with everything else I can understand what I reading A LOT better now.

But new a question
Why is it when I put a camera in nothing shows up. Not even my joystick set to t.x,t.y

I tried camera(o.x,o.y)
O being my physics body and ellipse
Even width/2,height/2 just gives me a background

Could you explain the eyeX,Y,Z, centerX,Y,Z, and upX,Y,Z

Apologies for all the questions

@Dezmo Using the camera is a lot more complicated than just doing what you’re trying to do. If you’re trying to do something similar to flappy bird but going vertically, you don’t need the camera.

The camera is a 3D view into the world, so in 2D it’s rather a loss to use it unless you also use meshes and make them 3D through applied vertices.

But for 2D there’s actually ortho() which is much simpler and only needs left,right,top,bottom boundaries, here is a class I made to manipulate ortho


--[[*************************
* A camera for the 2D world 
***************************]]
local orthoL = ortho
Camera = class()
function Camera:init(o)
  self.width = o.width or WIDTH
  self.halfWidth = self.width/2
  self.height = o.height or HEIGHT
  self.halfHeight = self.height/2
  self.screenAspect = self.width/self.height
  self.detectResize = o.detectResize or true
  self.left = 0
  self.right = self.width
  self.bottom = 0
  self.top = self.height
  self.zoom = 0
  self.zoomHeight = (self.top + (self.zoom)) - (self.bottom - (self.zoom))
  self.zoomWidth = (self.right + (self.zoom * self.screenAspect)) - (self.left - (self.zoom * self.screenAspect))
  self.minZoom = -self.height/2 + .5 -- any less and inversion happens
  self.zoomScale = round((self.zoom - self.minZoom) / (self.halfHeight), 2)
  self.xOffset = 0
  self.yOffset = 0
  self:setBounds()
  self.img = image(self.width, self.height)
end

function Camera:update()
  if self.detectResize then
    if HEIGHT ~= self.height or WIDTH ~= self.width then
      print("screen resize detected")
      self.width = WIDTH
      self.height = HEIGHT
      self.halfWidth = self.width/2
      self.halfHeight = self.height/2
      self.screenAspect = self.width/self.height
      self.right = self.width 
      self.left = 0   
      self.bottom = 0 
      self.top = self.height 
      self.zoomHeight = (self.top + (self.zoom)) - (self.bottom - (self.zoom))
      self.zoomWidth = (self.right + (self.zoom * self.screenAspect)) - (self.left - (self.zoom * self.screenAspect))
      self.zoomScale = round((self.zoom - self.minZoom) / (self.halfHeight), 2)
      self.minZoom = -self.height/2 + .5 -- any less and inversion happens
      self:setBounds()
    end
  end
end

function Camera:drawOrtho()
  orthoL(self.boundLeft, self.boundRight, self.boundBottom, self.boundTop)
end

function Camera:drawOrthoNoZoom()
  orthoL(self.left, self.right, self.bottom, self.top)
end

function Camera:drawOrthoNoChange()
  orthoL(0, self.width, 0, self.height)
end

function Camera:setZoom(amount)
  self.zoom = amount
  self.zoomHeight = (self.top + (self.zoom)) - (self.bottom - (self.zoom))
  self.zoomWidth = (self.right + (self.zoom * self.screenAspect)) - (self.left - (self.zoom * self.screenAspect))
  self.zoomScale = round((self.zoom - self.minZoom) / (self.halfHeight), 2)
  self:setBounds()
end

function Camera:resetOffsets()
  self.xOffset = 0
  self.yOffset = 0
  self:setBounds()
end

function Camera:setBounds()
  self.boundRight = self.right + self.xOffset + (self.zoom * self.screenAspect)
  self.boundLeft = self.left + self.xOffset - (self.zoom * self.screenAspect)
  self.boundTop = self.top + self.yOffset + self.zoom
  self.boundBottom = self.bottom + self.yOffset - self.zoom
end

function Camera:input(input)
  self.xOffset = self.xOffset + input.x
  self.yOffset = self.yOffset + input.y
  self:setBounds()
end

function round(num, numDecimalPlaces) --Todo negative is broken?
  local mult = 10^(numDecimalPlaces or 0)
  local res = math.floor(num * mult + 0.5) / mult
  --print(res)
  return res
end












@dave1707 understood I’ll “KISS” because my brain is still mushy

@skar NICE … I have no clue how to use that but I’ll I’ll keep it for dependencies down the line.

Okay final question:

What is the best way to attached physic objects to the pillars

My game currently is heavily physics based

Without the code this time. Just kinda point me in the right direction for me to trial and error it a bit.

“Attach” is a term to use loosely. In Codea physics bodies have their own properties. Once you instantiate a physics body it will update its own x,y values to the physics engine which will try to process any interactions every frame. So a physics body with gravity enabled will change its x,y based on the gravity (default is -Y). So you can remove gravity from the whole system, or make the body STATIC. Even more complex you can wrap the physics body in a class and force it to do what you want like ignore the changes to y even with gravity enabled.

To use my camera:

function setup()
  myCam = Camera({})
end

function draw()
  myCam:drawOrtho()
  —…
end

and you can call myCam:input(vec2(10,10)) to change position
myCam:setZoom(100) to change zoom

@Dezmo The easiest way to attach a physics object to the pillars is to use polygon.

Okay…my brain is frying. The closest I got to made the entire program slow down so I have only imagine how many physics objects I managed to make
I’m trying my last idea now(at least I tried on my own)
But some would be greatly appreciated :frowning:

Post your code so we have a better idea of where you’re struggling

This is the “it don’t do nothing” version, not the version that slowed everything down.

@Dezmo Heres another version. Slide your finger to move the ball. Each time you hit a row, the speed increases. When the speed reaches 4 the game ends. You get points each time rows go off the bottom of the screen. You can add or remove what code you want. Not sure how well this works on an iPhone, might have to play with the squaresAcross code.

viewer.mode=FULLSCREEN

function setup()
    physics.gravity(0,0)
    polyTab={}
    size=30
    squaresAcross=WIDTH//size-2  -- number of squares across the screen
    dist=200    -- distance between rows of pipes
    speed=1
    score=0
    gameOver=false
    createPipes()
    
    b1=physics.body(CIRCLE,20)
    b1.x=WIDTH/2
    b1.y=200
    b1.type=DYNAMIC
    b1.sleepingAllowed=false
end

function draw()
    background(0)
    
    if gameOver then
        text("GAME OVER",WIDTH/2,HEIGHT/2)
        return
    end
    
    lineCapMode(SQUARE)
    stroke(255, 73, 0)
    strokeWidth(size)
    
    for z=1,#polyTab,2 do
        -- left side row
        line(0,polyTab[z].y+size/2,polyTab[z].z,polyTab[z].y+size/2)
        polyTab[z].y=polyTab[z].y-speed  
        
        -- right side row  
        line(WIDTH-polyTab[z+1].z,polyTab[z+1].y+size/2,WIDTH,polyTab[z+1].y+size/2)
        polyTab[z+1].y=polyTab[z+1].y-speed
    end
    
    -- create more rows
    for a,b in pairs(polyTab) do
        if b.y<HEIGHT-dist and b.a==1 then
            b.a=0
            createPipes()
        end
    end
    
    -- remove row off bottom of screen
    for z=#polyTab,1,-1 do
        if polyTab[z].y<0 then
            table.remove(polyTab,z)
            score=score+10*speed
        end
    end
  
    -- ball  
    noStroke()
    ellipse(b1.x,b1.y,40)
    
    fill(255)
    text("speed  "..speed,WIDTH/2,HEIGHT-50)
    text("score  "..score,WIDTH/2,HEIGHT-100)
end

function touched(t)
    if t.state==BEGAN then
        tx,ty=t.x,t.y
    end
    if t.state==CHANGED then
        b1.linearVelocity=vec2((t.x-tx)*7,(t.y-ty)*7)
    end
end

function collide(c)
    if c.state==BEGAN then
        -- increase speed on each collision
        speed=speed+.1
        if speed>4 then
            speed=0
            gameOver=true
        end
    end
end

function createPipes()
    l=math.random(squaresAcross)   -- random left squares
    r=squaresAcross-l  -- total squares minus number of left squares
    lDist=l*size
    rDist=r*size
    
    -- left polygon
    local p=physics.body(POLYGON,vec2(0,0),vec2(lDist/2,0),
            vec2(lDist/2,size),vec2(-lDist/2,size),vec2(-lDist/2,0),vec2(0,0))
    p.x=lDist/2
    p.y=HEIGHT-size/2
    p.z=lDist
    p.a=1
    p.type=STATIC
    table.insert(polyTab,p)
    
    -- right polygon
    local p=physics.body(POLYGON,vec2(0,0),vec2(rDist/2,0),
            vec2(rDist/2,size),vec2(-rDist/2,size),vec2(-rDist/2,0),vec2(0,0))
    p.x=WIDTH-rDist/2
    p.y=HEIGHT-size/2
    p.z=rDist
    p.a=0
    p.type=STATIC
    table.insert(polyTab,p)
end

Per Dave’s code, you’re missing the collide function to do something when the bodies collide :wink:

Excellent! I’ll dissect it and use it in my code
I do have a question tho
What is “.a”

I just realized this draft was saved for quite some time but THE GAME IS ALMOST DONE
Buuuut

I’m having some issues. I wanted to create a proper loop without cheating by using “viewer.reset”

Everything I have is working very well except that some left over physics body are still there after the game over screen but disappear after a collision. I want to make a clear function to put into my game over if statement but not sure how. I’ve tried “destroy” and “table.remove” in different areas but the phantom physics still remain.

Here’s what I have so far

@Dezmo I don’t have time right now to look thru your code, but if you have leftover physics bodies, you need to go thru the table and destroy any object still in the table.