[RESOLVED]Render a lot of the same thing (i.e. bullets)

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
lilkoopa
Prole
Posts: 3
Joined: Sun May 12, 2013 7:31 am

[RESOLVED]Render a lot of the same thing (i.e. bullets)

Post by lilkoopa »

Hello, a while ago I decided to use play around with love and I'm... well... loving it. I'm also pretty new to Lua and I'm loving that too! I feel bad posting here considering my first post is asking for help, but hopefully I'll be able to contribute with time as I get a better grasp on things.

I've been playing around with a kind of bullet-hell style game which involved rendering lots and lots of bullets onto the screen. What I have is a simple loop that renders entities. For bullets in particular it just renders a colored rectangle.

Code: Select all

-- Bullet is a 'class'
function Bullet:draw()
    local x,y, w,h = physics.get_world_box(self) -- gets bounding box in 2d space (this is not not love.physics)

    love.graphics.setColor(255,0,0, 255)
    love.graphics.rectangle('fill', x,y, w,h)
    love.graphics.setColor(255,255,255, 255)
end
This works fine up until a point. Once about 450 entities pile up the game just freezes without notice or lag even. 450 might sound like a lot, and it is, but it's within the realm of possibility that there will be more than that. If it was just a little slower I wouldn't been as determined to fix it but this is a complete freezing of the game entirely. Before you ask, this is almost definitely rendering that's causing the problem, when I stop drawing entirely the game just slows down as they pile up, no freeze.

My goal is to, at the very least, stop the game from freezing when there's a lot of entities, but I haven't managed to find a way. I tried manipulating an ImageData and drawing a new Image in draw, there was no freeze, but it was incredibly slow. I don't think a canvas will help because the bullets will be moving in all directions constantly.

Could a shader work? Any undocumented tools that might be useful here? Why is it freezing in the first place?

Thanks.
Last edited by lilkoopa on Sun May 12, 2013 7:30 pm, edited 1 time in total.
User avatar
micha
Inner party member
Posts: 1083
Joined: Wed Sep 26, 2012 5:13 pm

Re: Render a lot of the same thing (i.e. bullets)

Post by micha »

As far as I know the rendering function in löve (line, rectangle etc) are quiet slow. You can first try to replace the rectangle by drawing an image (love.graphics.draw).

Second you can speed up the drawing by using a spritebatch instead of looping over all drawing commands. This should also speed up the drawing.

I don't know if the freezing is related to that.
User avatar
T-Bone
Inner party member
Posts: 1492
Joined: Thu Jun 09, 2011 9:03 am

Re: Render a lot of the same thing (i.e. bullets)

Post by T-Bone »

A Spritebatch might not suit bullets, since they move. Maybe if many bullets move in the same direction, you can place all the bullets moving straight down in a single Spritebatch, and just move the entire batch downwards. The nice thing about Spritebatches is that you can add more bullets to the batch while it's moving.

Drawing a few thousand images shouldn't be a problem though, as long as you only call love.graphics.newImage once. Note so sure about rectangles, haven't used that all that much. But it still seems strange that drawing 450 rectangles should be an issue.
lilkoopa
Prole
Posts: 3
Joined: Sun May 12, 2013 7:31 am

Re: Render a lot of the same thing (i.e. bullets)

Post by lilkoopa »

I had no idea that rectangle was slow. I tried out a sprite batch and it actually stopped the freezing. I'm going to try a more isolated example to see what happens, I'll post code and results in a bit.
lilkoopa
Prole
Posts: 3
Joined: Sun May 12, 2013 7:31 am

Re: Render a lot of the same thing (i.e. bullets)

Post by lilkoopa »

I made a test to compare the different ways of rendering bullets, without a doubt sprite batch is the winner. Sprite batches only seem to slow down a few frames every 1000 or so bullets. Rectangles freeze after a few hundred and continue to freeze repeatedly every few frames. Images freeze up for a long time when some are created after a lot already exist and then resume at a normal framerate. Note that image objects are NOT created each frame, I'm not sure why the freezing happens.

I'll mark this post as resolved.

Code: Select all

local
    line,
    mouse,
    bullet_image,
    sprite_batch,
    is_mouse_down,
    bullet_list,
    use_random_bullet,
    render_mode_index,
    render_mode,
    frame_time,
    longest_frame_time

local player = {x=love.graphics.getWidth(), y=0}
local sprite_batch_size = 65536
local bullet_color = {128,0,0,255}
local bullet_dimensions = {10,10}
local line_height = 12
local bullet_speed = 0.25
local bullet_quadrant = 3
local render_modes = {'Sprite Batch','Images','Rectangles'}

local function make_image()
    local image_data = love.image.newImageData(unpack(bullet_dimensions))
    image_data:mapPixel(function() return unpack(bullet_color) end)

    return love.graphics.newImage(image_data)
end

local function normalize(x,y)
    local length = math.sqrt(x^2 + y^2)
    return x/length, y/length
end

local function random_vector(quadrant)
    local x,y = normalize(math.random(-1000,1000), math.random(-1000,1000))

    if quadrant == 1 then
        x,y = math.abs(x),-math.abs(y)
    elseif quadrant == 2 then
        x,y = -math.abs(x),-math.abs(y)
    elseif quadrant == 3 then
        x,y = -math.abs(x),math.abs(y)
    elseif quadrant == 4 then
        x,y = math.abs(x),math.abs(y)*-1
    end

    return x,y
end

local function new_bullet(x,y, mx,my)
    local vx,vy = normalize(mx-x, my-y)

    vx,vy = vx*bullet_speed, vy*bullet_speed
    table.insert(bullet_list, {x=x,y=y, vx=vx,vy=vy})
end

local function new_random_bullet(x,y)
    local vx,vy = random_vector(bullet_quadrant)

    vx,vy = vx*bullet_speed, vy*bullet_speed
    table.insert(bullet_list, {x=x,y=y, vx=vx,vy=vy})
end

local function new_line()
    local result = line*line_height
    line = line+1
    return result
end

function love.load()
    frame_time = 0
    longest_frame_time = 0

    is_mouse_down = false
    mouse = {x=0, y=0}

    render_mode_index = 1
    render_mode = 'Sprite Batch'

    bullet_image = make_image()
    bullet_list = {}
    use_random_bullet = true
    sprite_batch = love.graphics.newSpriteBatch(bullet_image, sprite_batch_size)
end

function love.update(dt)
    frame_time = dt
    if dt > longest_frame_time then
        longest_frame_time = dt
    end

    mouse.x,mouse.y = love.mouse.getPosition()
    if is_mouse_down then
        if use_random_bullet then
            new_random_bullet(player.x,player.y)
        else
            new_bullet(player.x,player.y, mouse.x,mouse.y)
        end
    end

    if render_mode == 'Sprite Batch' then
        sprite_batch:clear()
        for _,bullet in ipairs(bullet_list) do
            bullet.x,bullet.y = bullet.x+bullet.vx, bullet.y+bullet.vy
            sprite_batch:add(bullet.x,bullet.y)
        end
    else
        for _,bullet in ipairs(bullet_list) do
            bullet.x,bullet.y = bullet.x+bullet.vx, bullet.y+bullet.vy
        end
    end
end
function love.draw()
    line = 0

    if render_mode == 'Sprite Batch' then
        local rendered = #bullet_list > sprite_batch_size and
            sprite_batch_size or
            #bullet_list

        love.graphics.draw(sprite_batch)
        love.graphics.print('No. of rendered bullets: ' .. rendered, 0,new_line())
    elseif render_mode == 'Images' then
        for _,bullet in ipairs(bullet_list) do
            love.graphics.draw(bullet_image, bullet.x,bullet.y)
        end
        love.graphics.setColor(255,255,255,255)
    elseif render_mode == 'Rectangles' then
        love.graphics.setColor(unpack(bullet_color))
        for _,bullet in ipairs(bullet_list) do
            local w,h = unpack(bullet_dimensions)
            love.graphics.rectangle('fill', bullet.x,bullet.y, w,h)
        end
        love.graphics.setColor(255,255,255,255)
    end

    love.graphics.print('No. of bullets: ' .. #bullet_list, 0,new_line())
    love.graphics.print('FPS: ' .. love.timer.getFPS(), 0,new_line())
    love.graphics.print('FT: ' .. frame_time, 0,new_line())
    love.graphics.print('Longest FT: ' .. longest_frame_time, 0,new_line())
    love.graphics.print('Render mode: ' .. render_mode, 0,new_line())
end

function love.mousepressed(x,y, button)
    is_mouse_down = true
end
function love.mousereleased(x,y, button)
    is_mouse_down = false
end
function love.keypressed(key)
    if key == 'escape' then
        love.event.push('quit')
    elseif key == 'right' then
        render_mode_index = ((render_mode_index) % #render_modes) + 1
        render_mode = render_modes[render_mode_index]
    elseif key == 'left' then
        render_mode_index = (render_mode_index - 1) % #render_modes
        render_mode_index = render_mode_index == 0 and
            #render_modes or
            render_mode_index

        render_mode = render_modes[render_mode_index]
    elseif key == 'r' then
        if use_random_bullet then
            use_random_bullet = false
        else
            use_random_bullet = true
        end
    end
end
Controls:
  • mouse click to fire
    r to toggle bullet direction (random or towards mouse)
    esc to exit
    left and right to alternate between draw modes
EDIT:

I did a little more testing and I noticed that rectangles render at the highest fps if the number of bullets don't change, but when the number of bullets changes it starts freezing.
Attachments
fast_render_test.love
(1.36 KiB) Downloaded 124 times
Post Reply

Who is online

Users browsing this forum: pgimeno and 2 guests