Create multiple squares upon keyclicks

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
Giuliano Comoglio
Prole
Posts: 2
Joined: Sun Apr 21, 2024 3:22 pm

Create multiple squares upon keyclicks

Post by Giuliano Comoglio »

Dear LOVE community,

I'm new to the forum, to LOVE and to Lua as well, and I'm here because I'm really excited about learning the mechanics of the language and the framework.

My use of LOVE for now is not strictly game dev, but more to create a GUI that can transform upon key presses. In particular, I'm trying to draw some black squares in a row when clicking "c" on the keyboard and to remove them when clicking "delete", much like typing a text.

For now I'm trying to not go for OOP, as my understanding of it is still pretty low.
I've created a code that I hope you can forgive me for, which looks like this:

Code: Select all

_G.love = require("love")

Keyboard = love.keyboard

love.graphics.setBackgroundColor(1, 1, 1)
_G.mainCanvas = love.graphics.newCanvas()
_G.mainCanvasScroll = {}
_G.mainCanvasScroll.h = 0 -- horizontal scroll
_G.mainCanvasScroll.v = 0 -- vertical scroll

_G.love.graphics.setCanvas(mainCanvas)

love.graphics.setColor(0,0,0)
local square = {
    canvas=love.graphics.newCanvas(100, 100),
    placementX = 0,
    placementY = 100
}
_G.love.graphics.setCanvas(square)
_G.rectangle = love.graphics.rectangle("fill", 0, 0, 100, 100)
_G.squareSet = {}


function love.update(dt)
    --scroll
    if Keyboard.isDown("left") and (Keyboard.isDown("lctrl") or Keyboard.isDown("rctrl")) then
        mainCanvasScroll.h = mainCanvasScroll.h + 10
        --print(mainCanvasScroll.h)
    end
    if Keyboard.isDown("right") and (Keyboard.isDown("lctrl") or Keyboard.isDown("rctrl")) then
        mainCanvasScroll.h = mainCanvasScroll.h - 10
        --print(mainCanvasScroll.h)
    end

    if Keyboard.isDown("c") then
        square.placementX = square.placementX + 100
        love.timer.sleep(0.09)
        table.insert(squareSet, square)
        print(square.placementX)
    end

    if Keyboard.isDown("backspace") then
        if square.placementX > 0 then
            square.placementX = square.placementX - 100
            love.timer.sleep(0.09)
        else end
        table.remove(squareSet, #squareSet)
        print(square.placementX)
    end

    
end


function love.draw()
    love.graphics.setCanvas(mainCanvas)
    for i = 1, #squareSet do
        love.graphics.draw(
            squareSet[i].canvas, 
            square.placementX, 
            square.placementYent)
        --print(squareSet[#squareSet])
    love.graphics.setCanvas()
    end

    love.graphics.setCanvas(mainCanvas)



    love.graphics.setCanvas()
    love.graphics.draw(mainCanvas, mainCanvasScroll.h, mainCanvasScroll.v)
end
The idea is that of having a

Code: Select all

mainCanvas
on which are drawn the many

Code: Select all

square.canvas
that will eventually contain the actual black squares (

Code: Select all

rectangle("fill", 0, 0, 100, 100)
).

The problem is: the squares are not appearing on the screen.
I've tried to make sure that the order of the different

Code: Select all

setCanvas
methods is good, but I think I'm having a problem with the for loop that I'm using in the

Code: Select all

love.draw()
function for spawing the rectangles.

Do you have any suggestions about this issue?

Thank you in advance.
Giuliano
RNavega
Party member
Posts: 259
Joined: Sun Aug 16, 2020 1:28 pm

Re: Create multiple squares upon keyclicks

Post by RNavega »

Hi Giuliano, welcome. Here are my impressions:
- You don't need to reference the global table _G every time. This is already done for you when you use a name that was declared without the 'local' qualifier. It's a great practice to use "local" for everything, even things declared in the top-level scope of your script, like that "squareSet" table, as it does a "scope local" (the name will be visible only within the scope that it was declared in). So something declared like "local squareSet = {" etc. will be visible by everything else in that script, without polluting the _G table.
- The 'love' library is already injected into _G by the Löve framework when running your script, so you don't have to explicitly 'require' it. If you comment that line, your code will still work.
- love.graphics.rectangle() is a drawing command. It queues up a drawing command that will be performed by the graphics backend when love.graphics.present() is called (and that is done automatically for you inside the love.run() handler which is the mainloop of your program, which can be customized, but let's leave that for when you're more familiar with Löve). So love.graphics.rectangle() doesn't create an object. You'd need to call it each time you want to "stamp" a rectangle onto the active canvas.
- You don't need (or rather, shouldn't) use a canvas for each black rectangle: rather just store their geometry info (x, y, width, height etc) into those tables, then in the drawing loop you do a love.graphics.rectangle() for each, using those stored parameters.
- You don't need canvases for this. You can composite the screen in real-time, on each frame, inside love.draw(). Each time love.draw() is called, the default code in the love.run mainloop handier clears the screen (using the active background color, set with love.graphics.setBackgroundColor()). So, inside the love.draw() function, starting from that cleared screen, you'd draw each rectangle (which you're already doing, but you're sending it into that canvas). Once the love.draw() function finishes, the contents on screen are presented to the user. This will be repeated on the next frames as well.
So for what you're trying to do, canvases aren't needed. You can omit all uses of canvas and it'll work great, with the drawing commands being called with no canvas active so they affect the (backbuffer) screen.
Giuliano Comoglio
Prole
Posts: 2
Joined: Sun Apr 21, 2024 3:22 pm

Re: Create multiple squares upon keyclicks

Post by Giuliano Comoglio »

Thank you very much. I've edited the code as you suggested by commenting out redundant things and by turning all variables to local.

Now I've commented out also the "square" canvas, set the "mainCanvas" as the drwaing background, and indeed now the black rectangles
appear in a row upon typing "c" (thanks for the advice).

Now, I still have two issues:

1.The "backspace" key is not erasing the squares.
The code for the squares sits under the "love.update()" and looks like this:

Code: Select all

if Keyboard.isDown("c") then
        square.X = square.X + 150
        Timer.sleep(0.09)
        table.insert(squareSet, square) 
            elseif Keyboard.isDown("backspace") then
                if square.X > 0 then
                    square.X = square.X - 150
                    Timer.sleep(0.09)
                    table.remove(squareSet, #squareSet)
                end
    end
end
The coordinates "square.X" and "square.Y" are indeces 1 and 2 of a previous "square" table, and "squareSet" is a one-step-higher-table in hierarchy, populated in turn with "square" tables.

Then, under the "love.draw()" I have:

Code: Select all

    Graphics.setCanvas(mainCanvas)
    for i = 1, #squareSet do
        rectangle("fill",square.X,square.Y, 100, 100)
    end

    Graphics.setCanvas()
    Graphics.draw(mainCanvas, 0,0)
 
I'm basically trying to pop coordinate couples from the "squareSet" table (I believe) upon pressing backspace, but I'm afraid I should make the code check if certain conditions are matched, but I'm having a hard time figuring this out.

2. The "mainCanvas" is not inifinite apparently,
and when I draw rectangles in it they are rendered from left to right just up to the border of the window:
https://drive.google.com/file/d/1VI6mTl ... sp=sharing

In the conf. file the window is set to be resizable, and the mainCanvas doesn't have boundaries by now, so I'm trying to make it extend by default.

Do you have further hints about these two issues?
RNavega
Party member
Posts: 259
Joined: Sun Aug 16, 2020 1:28 pm

Re: Create multiple squares upon keyclicks

Post by RNavega »

Nice.
You can still remove the use of mainCanvas (that is, setCanvas(mainCanvas), setCanvas(), and draw(mainCanvas)), so having no canvases at all, and draw directly to the screen. You aren't doing anything different with the contents of the canvas except passing them to the screen already, so it's not needed.
Canvases (off-screen render targets) are usually only needed for multi-pass post-processing effects, like glow and blur, as well as for pixel art games that draw the low resolution pixel art to the canvas, then draw that canvas upscaled to fill the high resolution game window (a cool method for which is described in here).

The Löve window itself has a default canvas, so you can just issue drawing commands without doing anything, and they'll draw on screen as expected.

As to what you're doing with the squares, it seems like you're going for a "cursor" kind of behavior:
- You have that cursor (a reference point).
- When a key is hit, that cursor advances a step and a new square is created at the position of the cursor.
- When some other key is hit, the most recent square is removed, and the cursor goes back a step.

So, something like this:

Code: Select all

local function createSquare(cursor)
    local newSquare = {X = cursor.X, Y = cursor.Y}
    return newSquare
end

function love.update(dt)    
    (...)    
    if Keyboard.isDown("c") then
        square.X = square.X + 150
        table.insert(squareSet, createSquare(square))
        Timer.sleep(0.09)
    elseif Keyboard.isDown("backspace") then
        square.X = square.X - 150
        if square.X < 0 then
            square.X = 0
        end
        -- If no index is supplied, table.remove() pops the topmost (AKA the last) item.
        -- Also, it doesn't seem to fail even if the table is empty.
        -- See: https://www.lua.org/manual/5.4/manual.html#pdf-table.remove
        table.remove(squareSet)
        Timer.sleep(0.09)
    end
end
The problem is probably coming from you adding the cursor itself to the 'squareSet' table. So you're adding references to 'square' (the same table), instead of creating unique tables for each square.

Say, consider this:

Code: Select all

local squareSet = {}
local square = {X = 0, Y = 666}

table.insert(squareSet, square)
square.X = square.X + 5
table.insert(squareSet, square)
square.X = square.X + 5
table.insert(squareSet, square)
After that, the contents of squareSet would be this:

Code: Select all

(squareSet) {
    (square) {X = 10, Y = 666},
    (square) {X = 10, Y = 666},
    (square) {X = 10, Y = 666}
}
...because the code added references to the same table (the same object).
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot], gianmichele, Google [Bot], Ollie.ha and 7 guests