[SOLVED] Spawning/instantiating several objects on a grid

General discussion about LÖVE, Lua, game development, puns, and unicorns.
Post Reply
User avatar
nice
Party member
Posts: 191
Joined: Sun Sep 15, 2013 12:17 am
Location: Sweden

[SOLVED] Spawning/instantiating several objects on a grid

Post by nice »

Hello everyone!

The game
I'm working on a prototype where I have a player (white circle) that collects "trash" (a square with T) to protect "animals" that walks across the screen. A new piece of "trash" spawns on the screen when the Player collides with a "trash" object on the screen.

Here's a screenshot

What's happening right now?
How the spawning of the "Trash" works right now, is that they take random X/Y coordinates and spawn 10 of them across the screen.
But the problem with this is that sometimes they overlap each other when spawning on the screen or spawn in a cluster.

What I want to happen
What I want to happen is that when the trash is spawned on screen, the trash position themselves accordingly to the grid (see screenshot), making the spawn pattern (hopefully) more "even" (if that makes any sense).

What I have right now
As a foundation, I have a grid drawn out on the screen to better visualize how the grid will work. It's not aligned perfectly to the screen size (1680x1050) as the tiles themselves are 32x32.
I've used CS50's snake tutorial as a starting point and modify it to suit my needs.

What I want help with
What I want help with is how should I go about spawning the "Trash" accordingly to the tiles, the tutorial goes explains how stuff is spawned in the game by changing the tiles on the screen to something else (for example the snake's head). I think that I want something similar but that only affects the "trash".

If there's something that needs to be explained better, please let me know.
Thank you for your time!

main.lua

Code: Select all

require("mobdebug").start()

Player = require "player"
trash = require "trash"
animal = require "animal"

local tileGrid = {}

function love.load()
  trashController.spawnTrash(10)
  animalController.spawnAnimal(1)
  spawnTimer = 1

  Window = {}
  Window.Width = love.graphics:getWidth()
  Window.Height = love.graphics:getHeight()

  TILE_SIZE = 32
  MAX_TILES_X = (Window.Width / TILE_SIZE)
  MAX_TILES_Y = (Window.Height / TILE_SIZE)
  TILE_EMPTY = 0
  TILE_TRASH = 1
  
  --math.randomseed(os.time())

end

function love.update(dt)

  Player:update(dt)
  trash:update(dt)
  animal:update(dt)
  
end

function love.draw()

  Player:draw()
  trash:draw()
  animal:draw()
  
  for y = 1, MAX_TILES_Y do
    for x = 1, MAX_TILES_X do
        love.graphics.setColor(0, 1, 1, 0.3)
        love.graphics.rectangle('line', 
                               (x - 1) * TILE_SIZE, 
                               (y - 1) * TILE_SIZE,
                               TILE_SIZE,
                               TILE_SIZE)

        love.graphics.setColor(1, 1, 1, 1)
    end
  end
end

function love.keypressed(key)
  
  Player:keypressed(key)
  
  -- Close Window
  if key == 'escape' then
    love.event.quit()
  end
  
end

function love.keyreleased(key)
  
  Player:keyreleased(key)
  
end
trash.lua (Spawns "trash" and what I want to affect)

Code: Select all

Player = require "player"

trash =  {}
trashController = {}
trashController.trashes = {}

--[[
attaching stuff to grid (starting to explain it)
https://youtu.be/ld_xcXdRez4?t=3505
]]
function trashController.spawnTrash(maxTrash)
  
  local mRand = math.random
  
  local width = love.graphics:getWidth()
  local height = love.graphics:getHeight()
  local minValue = 100
  local minX = minValue
  local maxX = width - minValue
  local minY = minValue
  local maxY = height - minValue
  local halfWidth = love.graphics:getWidth() * 0.5
  local halfHeight = love.graphics:getHeight() * 0.5
  
  love.math.setRandomSeed(love.timer.getTime())
  
  local randValueX = love.math.random(minX, maxX)
  local randValueY = love.math.random(minY, maxY)
  local minRandValueX = love.math.random(minX, halfWidth)
  local maxRandValueX = love.math.random(halfWidth, maxX)
  local minRandValueY = love.math.random(minY, halfHeight)
  local maxRandValueY = love.math.random(halfHeight, maxY)
  
  for i = 1, maxTrash do
    x = mRand(minRandValueX, maxRandValueX)
    y = mRand(minRandValueY, maxRandValueY)

    Trash = {}
    Trash.Sprite = love.graphics.newImage("trash.png")
    Trash.posX = x
    Trash.posY = y
    Trash.Width = Trash.Sprite:getWidth()
    Trash.Height = Trash.Sprite:getHeight()
    
    table.insert(trashController.trashes, Trash)
  end
  
end

function trashController.draw()
  for i, trash in ipairs(trashController.trashes) do
    love.graphics.draw(trash.Sprite, trash.posX, trash.posY)
  end
end

function trash:trash_hit_player(trash, index)
  local playerHalfWidth = Player.Width * 0.5
  local playerHalfHeight = Player.Height * 0.5
  local trashWidth = Trash.Sprite:getWidth()
  local trashHeight = Trash.Sprite:getHeight()

  if(trash.posX <= Player.posX + playerHalfWidth and
    trash.posX + trashWidth >= Player.posX and
    trash.posY < Player.posY + Player.Height and
    trash.posY + trashHeight >= Player.posY) then
    print("Trash hit Player")
    table.remove(trashController.trashes, index)
  end
end

function trash:update(dt)
  for _, t in ipairs(trashController.trashes) do
    trash:trash_hit_player(t, _)
  end
  
  if #trashController.trashes < 10 then
    trashController.spawnTrash(1)
  end
end

function trash:draw()
  trashController.draw()
end

return trash
animal.lua (Spawns "animals" that walks across the screen)

Code: Select all

trash = require "trash"

animal = {}
animalController = {}
animalController.animals = {}

function animalController.spawnAnimal(maxAnimal)
  love.math.setRandomSeed(love.timer.getTime())
  local mRand = math.random
    
  local randSpawnValue = love.math.random(1, 4)
  local randSpriteValue = love.math.random(1, 2)
  -- Widths and Heights
  local width = love.graphics:getWidth()
  local height = love.graphics:getHeight()
  local halfWidth = love.graphics:getWidth() * 0.5
  local halfHeight = love.graphics:getHeight() * 0.5
  
  -- sp = spawn point
  local sp_LEFT = 50
  local sp_RIGHT = 1600
  local sp_TOP = 50
  local sp_BOTTOM = 950
  
  -- sr = spawn min/max range
  local sr_minX = 100
  local sr_maxX = width - 100
  local sr_minY = 100
  local sr_maxY = height - 100

  -- Animal Sprite
  local bigAnimal = love.graphics.newImage("Deer.png")
  local bigAnimalWidth = bigAnimal:getWidth()
  local bihgAnimalHeight = bigAnimal:getHeight()
  
  local smallAnimal = love.graphics.newImage("smallAnimal.png")
  local smallAnimalWidth = smallAnimal:getWidth()
  local smallAnimalHeight = smallAnimal:getHeight()
 -- local Scale = 1
 -- local Direction = 1
    
    Animal = {}
    --Animal.randomSprite = love.math.random(smallAnimal, bigAnimal)
    Animal.Sprite = bigAnimal
    Animal.Life = 2
    Animal.collidedLastFrame = false
    Animal.color = {0,1,0,1}
    -- local randSpawnValue = love.math.random(1, 4)
    if randSpawnValue == 1 then
      Animal.spawnLeft = true
      Animal.spawnRight = false
      Animal.spawnTop = false
      Animal.spawnBottom = false
      Animal.posX = sp_LEFT
      Animal.posY = mRand(sr_minY, sr_maxY)
      
    elseif randSpawnValue == 2 then
      --[[
        Create its own separate sprite that's in
        the correct direction?
        Would be ugly solution and it would be necessary 
        that it uses the same timer.
      ]]
      Animal.spawnLeft = false
      Animal.spawnRight = true
      Animal.spawnTop = false
      Animal.spawnBottom = false
      Animal.posX = sp_RIGHT
      Animal.posY = mRand(sr_minY, sr_maxY)
      
    elseif randSpawnValue == 3 then            
      Animal.spawnLeft = false
      Animal.spawnRight = false
      Animal.spawnTop = true
      Animal.spawnBottom = false
      Animal.posX = mRand(sr_minX, sr_maxY)
      Animal.posY = sp_TOP
        
    elseif randSpawnValue == 4 then
      Animal.spawnLeft = false
      Animal.spawnRight = false
      Animal.spawnTop = false
      Animal.spawnBottom = true
      Animal.posX = mRand(sr_minX, sr_maxX)
      Animal.posY = sp_BOTTOM
      
    end
    
    table.insert(animalController.animals, Animal)
end

function animalController.draw()
    for i, animal in ipairs(animalController.animals) do
      love.graphics.print(animal.Life, animal.posX, animal.posY, 0, 2, 2)
      love.graphics.setColor(animal.color)
      love.graphics.draw(Animal.Sprite, 
                         animal.posX, 
                         animal.posY, 
                         animal.Radians)
    end
    love.graphics.setColor(1, 1, 1, 1)
  end

function animal_hit_trash(animal, index)
  
  local trashWidth = Trash.Sprite:getWidth()
  local trashHeight = Trash.Sprite:getHeight()
  local trashHalfWidth = Trash.Sprite:getWidth() * 0.5
  local trashHalfHeight = Trash.Sprite:getHeight() * 0.5
  
  local animalWidth = animal.Sprite:getWidth()
  local animalHeight = animal.Sprite:getHeight()
  local animalHalfWidth = animal.Sprite:getWidth() * 0.5
  local animalHalfHeight = animal.Sprite:getHeight() * 0.5
  
  local Collided = false
  
  for _, t in ipairs(trashController.trashes) do
    if (animal.posX <= t.posX + trashHalfWidth and
        animal.posX + animalWidth > t.posX and
        animal.posY < t.posY + trashHeight and
        animal.posY + animalHeight > t.posY) then
  
        Collided = true
  
        if animal.collidedLastFrame == false then
           animal.Life = animal.Life - 1
          animal.collidedLastFrame = true
          print("Animal hit Trash")
          if animal.Life == 0 then
            table.remove(animalController.animals, index)
          else
            animal.color = {1,0,0,1}
          end
        end
    end
  end
  
  if Collided == false then
      animal.collidedLastFrame = false
  end
end

function animal:update(dt)

    local moveSpeed = 400
  --print(spawnTimer)
  spawnTimer = spawnTimer - dt
  if spawnTimer <= 0 then
    animalController.spawnAnimal()
    spawnTimer = spawnTimer + 1
  end
  
  for _, animal in pairs(animalController.animals) do
    if animal.spawnLeft == true then
      animal.posX = animal.posX + moveSpeed * dt
    elseif animal.spawnRight == true then
      animal.posX = animal.posX - moveSpeed * dt
    elseif animal.spawnTop == true then
      animal.posY = animal.posY + moveSpeed * dt
    elseif animal.spawnBottom == true then
      animal.posY = animal.posY - moveSpeed * dt
    end
  end
  
  -- Destroy animal when it collides with Trash
  for i, animal in ipairs(animalController.animals) do
    animal_hit_trash(animal, i)
  end
  
  -- Remove animals if they're outside of the screen
  for i, animal in ipairs(animalController.animals) do
    if animal.posX >= 1800 then
      table.remove(animalController.animals, i)
      print("removed animal RIGHT")
    elseif animal.posX <= -250 then
      table.remove(animalController.animals, i)
      print("removed animal LEFT")
    elseif animal.posY >= 1200 then
      table.remove(animalController.animals, i)
      print("removed animal BOTTOM")
    elseif animal.posY <= -250 then
      table.remove(animalController.animals, i)
      print("removed animal TOP")
    end
  end
end

function animal:draw()
  animalController.draw()
end

return animal
player.lua

Code: Select all

local isLeftHeld
local isRightHeld
local isUpHeld
local isDownHeld

-- " p " is for "player"
local p_LEFT = {'left', 'a'}
local p_RIGHT = {'right', 'd'}
local p_UP = {'up', 'w'}
local p_DOWN = {'down', 's'}

Player = {}
  -- Player Draw
Player.Sprite = love.graphics.newImage("player.png")
Player.posX = love.graphics.getWidth() * 0.5
Player.posY = love.graphics.getHeight() * 0.5
Player.Radians = 0
Player.scaleX = 1
Player.scaleY = 1
Player.originX = 0
Player.originY = 0

-- Player Movement
Player.Acceleration = 1
Player.Speed = 500

Player.Width = Player.Sprite:getWidth()
Player.Height = Player.Sprite:getHeight()

-- Player Clamping
Player.clampTop = Player.Sprite:getWidth() * 0
Player.clampBottom = love.graphics:getHeight() - 64
Player.clampLeft = Player.Sprite:getHeight() * 0
Player.clampRight = love.graphics:getWidth() - 64

function Player:update(dt)  
  
  -- Player Movement
  if love.keyboard.isDown(p_LEFT) then
    self.posX = self.posX - (self.Acceleration * self.Speed) * dt
  elseif love.keyboard.isDown(p_RIGHT) then
    self.posX = self.posX + (self.Acceleration * self.Speed) * dt
  end
  
  if love.keyboard.isDown(p_UP) then
    self.posY = self.posY - (self.Acceleration * self.Speed) * dt
  elseif love.keyboard.isDown(p_DOWN) then
    self.posY = self.posY + (self.Acceleration * self.Speed) * dt
  end

  -- Player Clamping
  if self.posY <= self.clampTop then
    self.posY = self.clampTop
  end
  if self.posY >= self.clampBottom then
    self.posY = self.clampBottom
  end
  if self.posX <= self.clampLeft then
    self.posX = self.clampLeft
  end
  if self.posX >= self.clampRight then
    self.posX = self.clampRight
  end
  
end

function Player:draw()
  love.graphics.draw(self.Sprite, 
                     self.posX, 
                     self.posY, 
                     self.Radians, 
                     self.scaleX, 
                     self.scaleY, 
                     self.originX, 
                     self.originY)
end

function Player:keypressed(key)
end

function Player:keyreleased(key)
end

return Player
Last edited by nice on Wed May 08, 2019 7:39 am, edited 1 time in total.
:awesome: Have a good day! :ultraglee:
User avatar
raidho36
Party member
Posts: 2063
Joined: Mon Jun 17, 2013 12:00 pm

Re: [HELP] Spawning/instantiating several objects on a grid

Post by raidho36 »

This is what uniform random distribution looks like. What you want is an even, non-random distribution. With that in mind, I suggest that you start with a random potential coordinate and then check against other pieces of trash to offset the spawn location, to make sure they're not clustered. You can try multiple random potential coordinates to see which one produces best results.
User avatar
pgimeno
Party member
Posts: 3673
Joined: Sun Oct 18, 2015 2:58 pm

Re: [HELP] Spawning/instantiating several objects on a grid

Post by pgimeno »

You don't have 1680x1050 tiles, you have 1680/32 x 1050/32 = 52x32 tiles.

So, pick up random numbers between 0 and 51 for x, and between 0 and 31 for y, multiply them by 32, and you will have the coordinates of a grid cell. Note that there's still some probability that two trash items end up in the same cell, so you may need to guard for that.

Alternatively, if you want the trash to have a minimum separation but not be grid-aligned, look up Poisson discs.
User avatar
raidho36
Party member
Posts: 2063
Joined: Mon Jun 17, 2013 12:00 pm

Re: [HELP] Spawning/instantiating several objects on a grid

Post by raidho36 »

pgimeno wrote: Tue May 07, 2019 8:05 pmPoisson discs.
It might not be suitable for this application: it only generates points directly next to existing points.
User avatar
nice
Party member
Posts: 191
Joined: Sun Sep 15, 2013 12:17 am
Location: Sweden

Re: [HELP] Spawning/instantiating several objects on a grid

Post by nice »

I asked a co-worker for suggestions and the solution to my problem is kinda easy.

First off, I divide the local variables by 32

Code: Select all

  local minRandValueX = love.math.random(minX, halfWidth) / 32
  local maxRandValueX = love.math.random(halfWidth, maxX) / 32
  local minRandValueY = love.math.random(minY, halfHeight) / 32
  local maxRandValueY = love.math.random(halfHeight, maxY) / 32
Secondly, I use math.floor on the 'x' and 'y' where I spawn "trash" and multiply it by 32

Code: Select all

    x = mRand(math.floor(minRandValueX), math.floor(maxRandValueX)) * 32
    y = mRand(math.floor(minRandValueY), math.floor(maxRandValueY)) * 32
For my purposes, this works perfectly fine and I hope this solution helps others
:awesome: Have a good day! :ultraglee:
User avatar
pgimeno
Party member
Posts: 3673
Joined: Sun Oct 18, 2015 2:58 pm

Re: [HELP] Spawning/instantiating several objects on a grid

Post by pgimeno »

raidho36 wrote: Wed May 08, 2019 3:58 am
pgimeno wrote: Tue May 07, 2019 8:05 pmPoisson discs.
It might not be suitable for this application: it only generates points directly next to existing points.
You're describing more or less what a dense Poisson disc distribution does, but it doesn't need to be dense. See e.g. https://staffwww.dcs.shef.ac.uk/people/ ... isson.html


nice wrote: Wed May 08, 2019 7:10 am I asked a co-worker for suggestions and the solution to my problem is kinda easy.

First off, I divide the local variables by 32

Code: Select all

  local minRandValueX = love.math.random(minX, halfWidth) / 32
  local maxRandValueX = love.math.random(halfWidth, maxX) / 32
  local minRandValueY = love.math.random(minY, halfHeight) / 32
  local maxRandValueY = love.math.random(halfHeight, maxY) / 32
Secondly, I use math.floor on the 'x' and 'y' where I spawn "trash" and multiply it by 32

Code: Select all

    x = mRand(math.floor(minRandValueX), math.floor(maxRandValueX)) * 32
    y = mRand(math.floor(minRandValueY), math.floor(maxRandValueY)) * 32
For my purposes, this works perfectly fine and I hope this solution helps others
Glad it works for you. You didn't mention that you wanted the trash items to be restricted to a random rectangle that includes the origin, though, and that looks like a weird requirement. Other than that, the method is the same I explained, and the caveat of the possibility that two trash items fall on the same square applies. Especially if the random rectangle has an area <= 9, e.g. if the rectangle is 3x3.
User avatar
nice
Party member
Posts: 191
Joined: Sun Sep 15, 2013 12:17 am
Location: Sweden

Re: [HELP] Spawning/instantiating several objects on a grid

Post by nice »

pgimeno wrote: Wed May 08, 2019 1:42 pm Glad it works for you. You didn't mention that you wanted the trash items to be restricted to a random rectangle that includes the origin, though, and that looks like a weird requirement. Other than that, the method is the same I explained, and the caveat of the possibility that two trash items fall on the same square applies. Especially if the random rectangle has an area <= 9, e.g. if the rectangle is 3x3.
Sorry that I didn't mention the rectangles, my bad :P
At the moment it works for what I need and at a later date, I will go back and prevent so that they doesn't spawn on each other
:awesome: Have a good day! :ultraglee:
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot] and 3 guests