Loading screen with coroutines

Hello all, I have been working with terrain/mesh generation, and it has been a success so far but I have been attempting to create a loading screen as my code does take quite a while to load. I have tried to use coroutines but I cannot seem to get them to work, here is the code that I have tried:

-- test-

-- Use this function to perform your initial setup
function setup()
    --Initialize tables
    points={}
    points2={}
    --Set up an image for the static background scenery, as to not have to calculate everything 60 times a second.
    img=image(WIDTH,HEIGHT)
    --Make sure the noise is random
    control=math.random(1,100)
    --Set up physics bodies
    cp=physics.body(CHAIN,false,unpack(points))
    mp=physics.body(CIRCLE,10)
    mp.position=vec2(WIDTH/2,HEIGHT/2)
    generate()
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 40)
    fill(0,255,0)
    rect(0,HEIGHT/2-10,progress*WIDTH/2,20)
    coroutine.resume(cos)
    --All we need to do in draw():
    --[[sprite(img,WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    noStroke()
    ellipse(mp.x,mp.y,mp.radius*2)]]--
end

function generate()
    cos=coroutine.create(doLevelGen)
    coroutine.resume(cos)
end
function doLevelGen()
    progress=0
    coroutine.yeild()
    --Generate the initial random terrain with noise
    for i=0, WIDTH do
        table.insert(points, vec2(i,noise(i/100,control)*50+100))
    end
    
    progress=progress+1
    print(progress)
    coroutine.yeild()
    table.insert(points, vec2(WIDTH,0))
    table.insert(points, vec2(0,0))
    --Set up our mesh for the ground
    m=mesh()
    m.vertices=triangulate(points)
    m:setColors(0,255,0)
    --Generate the background image
    setContext(img)
    for a=1,#points-1 do
        m:draw()
    end
    setContext()
    progress=progress+1
    cos=nil
end

Any help is appreciated, and the coroutine code is based off of someone else’s on the forums, but I do not remember who. The rest of the code is mine, but you are welcome to use the generation and/or mesh code.

And, if it helps, this is the code I was working off of. Thanks again for any help!

-- test-

-- Use this function to perform your initial setup
function setup()
    --Initialize tables
    points={}
    
    --Set up an image for the static background scenery, as to not have to calculate everything 60 times a second.
    img=image(WIDTH,HEIGHT)
    --Make sure the noise is random
    control=math.random(1,100)
    --Generate the initial random terrain with noise
    for i=0, WIDTH do
        table.insert(points, vec2(i,noise(i/100,control)*50+100))
    end
    table.insert(points, vec2(WIDTH,0))
    table.insert(points, vec2(0,0))
    --Set up our mesh for the ground
    m=mesh()
    m.vertices=triangulate(points)
    m:setColors(0,255,0)
    --Generate the background image
    setContext(img)
    for a=1,#points-1 do
        m:draw()
    end
    setContext()
    --Set up physics bodies
    cp=physics.body(CHAIN,false,unpack(points))
    mp=physics.body(CIRCLE,10)
    mp.position=vec2(WIDTH/2,HEIGHT/2)
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)

    -- This sets the line color
    
    -- Do your drawing here
    --All we need to do in draw():
    sprite(img,WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    noStroke()
    ellipse(mp.x,mp.y,mp.radius*2)
end

It’s yield, not yeild

Oops! Simple typo-can’t believe that didnt turn up an error :slight_smile:
Before I realized I spelled it yelid and apparently fixed it to yeild :stuck_out_tongue:

Hey @Ignatz-I finished the loading screen, but I was wondering if there was a way to make a smoother loading screen bar movement without hindering the performance. Thank you in advance, here is the code:

-- test-

-- Use this function to perform your initial setup
function setup()
    --Initialize tables
    points={}
    points2={}
    --Set up an image for the static background scenery, as to not have to calculate everything 60 times a second.
    img=image(WIDTH,HEIGHT)
    --Make sure the noise is random
    control=math.random(1,100)
    generate()
end

-- This function gets called once every frame
function draw()
    if cos then
        background(255, 0, 0, 255)
        font("Courier-Bold")
        fill(255)
        text("LOADING",WIDTH/2,HEIGHT/2+50)
        fill(0,255,0)
        rect(0,HEIGHT/2-10,progress,20)
        coroutine.resume(cos)
    end
    if not cos then
        --All we need to do in draw():
        background(0)
        sprite(img,WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
        noStroke()
        fill(255, 255, 255, 255)
        ellipse(mp.x,mp.y,mp.radius*2)
    end
end

function generate()
    cos=coroutine.create(doLevelGen)
    coroutine.resume(cos)
end
function doLevelGen()
    progress=0
    coroutine.yield()
    --Generate the initial random terrain with noise
    for i=0, WIDTH do
        table.insert(points, vec2(i,noise(i/100,control)*50+100))
    end
    progress=WIDTH/2
    coroutine.yield()
    table.insert(points, vec2(WIDTH,0))
    table.insert(points, vec2(0,0))
    --Set up our mesh for the ground
    m=mesh()
    m.vertices=triangulate(points)
    m:setColors(0,255,0)
    progress=WIDTH
    coroutine.yield()
    --Generate the background image
    setContext(img)
        m:draw()
    setContext()
    cos=nil
    mp=physics.body(CIRCLE,10)
    mp.position=vec2(WIDTH/2,HEIGHT/2)
    cp=physics.body(CHAIN,false,unpack(points))
end

@TheSolderKing - I don’t think you need a progress bar, when on my iPad3, it only takes a few seconds to load, far less than Facebook or many game apps.

I would do something like the following, I’ve numbered some lines, see notes underneath. Even if you don’t end up using this, there are a couple of nice tricks for you.

function setup()
    --Initialize tables
    points={}
    points2={}
    --Set up an image for the static background scenery, as to not have to calculate everything 60 times a second.
    img=image(WIDTH,HEIGHT)
    --Make sure the noise is random
    control=math.random(1,100)
    draw0=draw   --1    --< SEE NOTES at bottom for these numbered lines
    draw=DrawLoading --2
end

function DrawLoading()
    background(255, 0, 0, 255)
    font("Courier-Bold")
    fill(255)
    text("LOADING",WIDTH/2,HEIGHT/2+50)
    fill(0,255,0)
    if not drawnOnce then drawnOnce=true --4
    else
        doLevelGen() --5
        draw=draw0 --6
    end
end

-- This function gets called once every frame
function draw()  
     --All we need to do in draw():
    background(0)
        sprite(img,WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    noStroke()
    fill(255, 255, 255, 255)
    ellipse(mp.x,mp.y,mp.radius*2)    
end

function doLevelGen()
    --Generate the initial random terrain with noise
    for i=0, WIDTH do
        table.insert(points, vec2(i,noise(i/100,control)*50+100))
    end
    table.insert(points, vec2(WIDTH,0))
    table.insert(points, vec2(0,0))
    --Set up our mesh for the ground
    m=mesh()
    m.vertices=triangulate(points)
    m:setColors(0,255,0)
    progress=WIDTH
    --Generate the background image
    setContext(img)
        m:draw()
    setContext()
    cos=nil
    mp=physics.body(CIRCLE,10)
    mp.position=vec2(WIDTH/2,HEIGHT/2)
    cp=physics.body(CHAIN,false,unpack(points))
end
  1. You don’t really want “if” tests in your draw function, when they only apply to the first few seconds of loading. So store the normal draw function in another variable for a moment, then…

  2. Assign the function DrawLoading to draw, so Codea will use this for drawing

  3. In DrawLoading, draw the “Loading” text, then if we’ve drawn it at least once (ie drawn one frame, and this is the second), then

  4. if this is the first time we’ve run DrawLoading, set DrawnOnce to true and nothing else. This is because if we load the level data now, the Loading text won’t appear on screen. We need to let the drawing finish, and then generate data in the second frame, after Codea has drawn Loading on the screen.

  5. if this is the second time we’ve run DrawLoading, ie the second frame, generate the level data (leaving the Loading text from the first frame on the screen), and

  6. set the draw variable to point at the original draw function which we had hidden away in draw0, because as soon as the level generation is done, we can draw normally

When you run this, Codea runs DrawLoading instead of draw for the first frame, and again for the second frame, when it generates the level data. The screen will freeze meanwhile, showing the “Loading” text from the first frame. When the generation is complete, Codea will start using your normal draw function.

Why this is neat is that once the data is loaded, you have a very clean draw function that doesn’t have to bother testing for coroutines, data generation, or anything else. It can just draw your game.

If you search the forum, you’ll find various examples where people have a separate draw function for each stage of their game, eg splash screen, menu, the game itself, end of game, leaderboard, etc. All you have to do is set draw equal to whatever function you want to draw with!

@Ignatz, if we wanted a progress bar, how would we go about doing it?

Then you have to break the loading into bits as you have done and report progress at each frame. But that makes your code messy, which is why I would avoid it.

@matkatmusic I did succeed in making the loading screen, if you wanted to take a look at my code. Coroutines are quite simple when you get down to it, actually. That is, unless you spell yield yelid :stuck_out_tongue:

i guess from a visual design point of view, you’d just use tweens and tween between percentages of the full size of the progress bar. like, if(progress = 15%) { tween from currentPos to Progress over X number of frames } and every time progress changes, do the tween again