How to make my bullets fire one by one

General discussion about LÖVE, Lua, game development, puns, and unicorns.
Post Reply
AdventureFan
Prole
Posts: 14
Joined: Wed Jul 24, 2013 7:03 am

How to make my bullets fire one by one

Post by AdventureFan »

I was following along a tutorial (Invaders must die at http://www.headchant.com/2010/11/27/lov ... -must-die/),
right now I am trying to recreate just the part where the bullets are fired.

I have some code which fires 5 bullets at the same time (that's actually the code for the invaders but I adapted it a bit so it works the other way around).
But what I really want is to fire one bullet on the press of spacebar (or mouse click) and fire another shot when the spacebar is pressed again.

However, if I use "love.keyboard.isDown" or "love.keypressed", when I press space the bullet only moves a little bit. After the keypress it should move regardless if I'm pressing space or not. Despite the tutorial and trying it for myself, I can't grasp how to do this in Love2d. Well, I can of course copy the tutorial, but that doesn't mean I understand the how and why of this.

Side question:
I'm also a bit confused with the proper 'place' of the functions: sometimes I see people put them in love.draw, sometimes in love.update and sometimes in love.load. Sometimes people create other lua files (e.g. "player.lua" or "room.lua") and put functions there. I try to put my functions as much as possible in love.load, but that doesn't always give the result I want. Does that mean it doesn't really matter where functions are, or are there 'best practices'?

Code: Select all

function love.load()
	hero = {}
	hero.x = 300
	hero.y = 450
	hero.speed = 300

	magazine={}
	for i=0,5 do
		bullet = {}
		bullet.width = 5
		bullet.height = 10
		bullet.x = hero.x + 20*i + bullet.width
		bullet.y = hero.y + 10*i - bullet.height
		bullet.speed = 300
		table.insert(magazine, bullet)
	end	
end

function love.update(dt)
	if love.keyboard.isDown("left") then
		hero.x = hero.x - hero.speed * dt
	elseif love.keyboard.isDown("right") then
		hero.x = hero.x + hero.speed * dt
	end	

	for i,v in ipairs(magazine) do
		if v.y > 40 then
			v.y = v.y - bullet.speed * dt		
		else
		table.remove(magazine, i)
		end	
	end
end

function love.draw()

	-- draw the ground
	love.graphics.setColor(0,255,0,255)
	love.graphics.rectangle("fill", 0, 465, 800, 150)

	-- draw the hero
	love.graphics.setColor(255,255,0,255)
	love.graphics.rectangle("fill", hero.x, hero.y, 30, 15)

	-- draw the bullets
	for i,v in ipairs(magazine) do
		love.graphics.rectangle("fill", v.x, v.y, v.width, v.height)
	end	
end
User avatar
veethree
Inner party member
Posts: 877
Joined: Sat Dec 10, 2011 7:18 pm

Re: How to make my bullets fire one by one

Post by veethree »

You need to store all the bullets in a table, And on mousepressed/keypressed you add a bullet to the table, Then in love.update you loop through the bullet table updating the bullet positions.

EDIT:
Here's a 100% untested and most likely not fully functional code example that should none the less give you an idea what you're after:

Code: Select all

function love.load()
	bullets = {}
	bullet_speed = 200
end

function love.update(dt)
	for i,v in ipairs(bullets) do 
		v.x = v.x + math.cos(v.a) * bullet_speed * dt
		v.y = v.y + math.sin(v.a) * bullet_speed * dt
	end
end

function love.mousepressed(x, y, k)
	if k == "l" then
		bullets[#bullets + 1] = {
				x = player.x,
				y = player.y,
				a = getAngle(player.x, player.y, x, y) --Angle between player and mouse

			}
		end
	end
end

function getAngle(x1, y1, x2, y2)
	return math.atan2(x2-x1, y2-y1)
end
AdventureFan
Prole
Posts: 14
Joined: Wed Jul 24, 2013 7:03 am

Re: How to make my bullets fire one by one

Post by AdventureFan »

Thank you. Actually, it looks like your code will send the bullets toward the position of the mouse.

My intent was just to move them up. But, with your help and some more experimenting, I succeeded in creating a small program which makes the bullets shoot upward. I know it can still use a lot of work, but I think I'm finally starting to understand how to program projectiles.

And that was the immediate goal.

Code: Select all

function love.load()
	bullets = {}
	bullet_speed = 200
	
end

function love.update(dt)
	

	function love.mousepressed(x, y, k)
	   if k == "l" then
	      bullets[#bullets + 1] = {
	          y = 495
	         }
	     end
	end

	for i,v in ipairs(bullets) do
		v.y = v.y - bullet_speed * dt
		if v.y < 100 then
			table.remove(bullets, 1)
		end	
	end


end

function love.draw()
	love.graphics.setBackgroundColor(0,0,0)
	love.graphics.setColor (255,255,255)
	
	love.graphics.rectangle("fill", 292, 500, 20, 5)
	for i,v in ipairs(bullets) do
		love.graphics.rectangle("fill", 300, v.y, 5, 10)
	end
	
end
User avatar
veethree
Inner party member
Posts: 877
Joined: Sat Dec 10, 2011 7:18 pm

Re: How to make my bullets fire one by one

Post by veethree »

AdventureFan wrote:Thank you. Actually, it looks like your code will send the bullets toward the position of the mouse.

My intent was just to move them up. But, with your help and some more experimenting, I succeeded in creating a small program which makes the bullets shoot upward. I know it can still use a lot of work, but I think I'm finally starting to understand how to program projectiles.

And that was the immediate goal.

Code: Select all

function love.load()
	bullets = {}
	bullet_speed = 200
	
end

function love.update(dt)
	

	function love.mousepressed(x, y, k)
	   if k == "l" then
	      bullets[#bullets + 1] = {
	          y = 495
	         }
	     end
	end

	for i,v in ipairs(bullets) do
		v.y = v.y - bullet_speed * dt
		if v.y < 100 then
			table.remove(bullets, 1)
		end	
	end


end

function love.draw()
	love.graphics.setBackgroundColor(0,0,0)
	love.graphics.setColor (255,255,255)
	
	love.graphics.rectangle("fill", 292, 500, 20, 5)
	for i,v in ipairs(bullets) do
		love.graphics.rectangle("fill", 300, v.y, 5, 10)
	end
	
end
Few notes about your code. love.mousepressed() shouldn't be inside love.update, Actually no functions should be defined within other functions, Love.mousepressed is automatically called when you press any button on the mouse. Also unless you plan on changing the background color during gameplay, There's no need to call setBackgroundColor under love.draw, You can just call that under love.load and it'll stay.

So yeah, Good luck learning :)
AdventureFan
Prole
Posts: 14
Joined: Wed Jul 24, 2013 7:03 am

Re: How to make my bullets fire one by one

Post by AdventureFan »

Thank you for the advice. I improved my code a little:

- I put all functions outside of the love.update and love.draw functions (if love.keyboard.isDown can still be in love.update, yes?)
- I tried to use variables where I could, instead of fixed values
- I made it possible for the player to move left and right using the arrow keys
- I made sure the X position of the bullet is always in the middle of the player
- I made sure the player cannot move off the screen

Next step:
- make the rate of bullet fire slower (the rate of fire, not the actual bullet speed). How to accomplish this?
- add some enemies
- check for collisions and remove the enemies on collision with bullets

Code: Select all

function love.load()

	love.graphics.setBackgroundColor(0,0,0)
	love.graphics.setColor(255, 0, 0)
	
	-- setup the player
	player = {}
	player.x = 300
	player.y = 500
	player.width = 40
	player.height = 10
	player.speed = 300
	
	-- setup the bullets
	bullets = {}
	bullets.speed = 300
	bullets.width = 10
	bullets.height = 5
	bullets.x = (player.x + player.width/2) - bullets.width/2
	
	-- shoot a bullet (by adding it to the bullets table and passing a .y value)
	function love.keypressed(key)
		if key == " " then
			bullets[#bullets+1] = {
			y= 495,
			x = (player.x + player.width/2) - bullets.width/2
			}
		end
	end	
	
	
end

function love.update(dt)

	-- move our player
	if love.keyboard.isDown("right") then
		if player.x < (800-player.width) then
			player.x = player.x + player.speed *dt
		elseif player.x < (800-player.width) then
			player.x = (800-player.width)
		end
	end
	
	if love.keyboard.isDown("left") then
		if player.x > 0 then
			player.x = player.x - player.speed *dt	
		elseif player.x < 0 then
			player.x = 0
		end	
	end
	
	-- update the position of the bullets in the table 'bullets'
	for i,v in ipairs(bullets) do
      v.y = v.y - bullets.speed * dt
      if v.y < 100 then
         table.remove(bullets, 1)
      end   
   end

end

function love.draw()
	
	love.graphics.rectangle("fill", player.x, player.y, player.width, player.height)

	for i, v in ipairs(bullets) do
		love.graphics.rectangle("fill", v.x, v.y, bullets.width, bullets.height)
	end

end


User avatar
veethree
Inner party member
Posts: 877
Joined: Sat Dec 10, 2011 7:18 pm

Re: How to make my bullets fire one by one

Post by veethree »

AdventureFan wrote:Thank you for the advice. I improved my code a little:

- I put all functions outside of the love.update and love.draw functions (if love.keyboard.isDown can still be in love.update, yes?)
- I tried to use variables where I could, instead of fixed values
- I made it possible for the player to move left and right using the arrow keys
- I made sure the X position of the bullet is always in the middle of the player
- I made sure the player cannot move off the screen

Next step:
- make the rate of bullet fire slower (the rate of fire, not the actual bullet speed). How to accomplish this?
- add some enemies
- check for collisions and remove the enemies on collision with bullets

Code: Select all

function love.load()

	love.graphics.setBackgroundColor(0,0,0)
	love.graphics.setColor(255, 0, 0)
	
	-- setup the player
	player = {}
	player.x = 300
	player.y = 500
	player.width = 40
	player.height = 10
	player.speed = 300
	
	-- setup the bullets
	bullets = {}
	bullets.speed = 300
	bullets.width = 10
	bullets.height = 5
	bullets.x = (player.x + player.width/2) - bullets.width/2
	
	-- shoot a bullet (by adding it to the bullets table and passing a .y value)
	function love.keypressed(key)
		if key == " " then
			bullets[#bullets+1] = {
			y= 495,
			x = (player.x + player.width/2) - bullets.width/2
			}
		end
	end	
	
	
end

function love.update(dt)

	-- move our player
	if love.keyboard.isDown("right") then
		if player.x < (800-player.width) then
			player.x = player.x + player.speed *dt
		elseif player.x < (800-player.width) then
			player.x = (800-player.width)
		end
	end
	
	if love.keyboard.isDown("left") then
		if player.x > 0 then
			player.x = player.x - player.speed *dt	
		elseif player.x < 0 then
			player.x = 0
		end	
	end
	
	-- update the position of the bullets in the table 'bullets'
	for i,v in ipairs(bullets) do
      v.y = v.y - bullets.speed * dt
      if v.y < 100 then
         table.remove(bullets, 1)
      end   
   end

end

function love.draw()
	
	love.graphics.rectangle("fill", player.x, player.y, player.width, player.height)

	for i, v in ipairs(bullets) do
		love.graphics.rectangle("fill", v.x, v.y, bullets.width, bullets.height)
	end

end


Further notes:
You're still defining functions within functions(keypressed inside love.load), While this doesn't necessarily affect the functionality or performance, It just doesn't look right.

In most cases you want to be calling setColor inside love.draw, That way you can draw the player and bullets and whatever else you're drawing in different colors.

You're using the bullets table to both add bullets, and carry the bullet information (speed and size etc.) Usually the information about the bullet behavior would be stored in a separate table. The reason is if for example at some point you wanted to remove all the bullets from the screen, With your setup you would have to remove all the actual bullets, but avoid removing the speed, width, height etc., If you had a separate table for the actual bullets you could just clear that table.
Another thing you could do is to simply store the width/heigh/speed etc information in the bullets (same place where you're storing the x and y under keypressed).

By rate of fire you do you mean you want to be able to hold down space to fire continuously?
AdventureFan
Prole
Posts: 14
Joined: Wed Jul 24, 2013 7:03 am

Re: How to make my bullets fire one by one

Post by AdventureFan »

So more like this?

Code: Select all

 function love.keypressed(key)
      if key == " " then
          -- shoot a bullet (by adding it to the bullets table and passing a .y value)
         bullets[#bullets+1] = {
         y= 495,
         speed = 300,
         width = 10,
         height = 5,
         x = (player.x + player.width/2) - 5
         }
      end
   end   

function love.load()

   love.graphics.setBackgroundColor(0,0,0)
   love.graphics.setColor(255, 0, 0)
   
   -- setup the player
   player = {}
   player.x = 300
   player.y = 500
   player.width = 40
   player.height = 10
   player.speed = 300
   
   -- setup the bullets
   bullets = {}
   
   -- when a key is pressed, call the function love.keypressed
  love.keypressed()
   
end

function love.update(dt)

   -- move our player
   if love.keyboard.isDown("right") then
      if player.x < (800-player.width) then
         player.x = player.x + player.speed *dt
      elseif player.x < (800-player.width) then
         player.x = (800-player.width)
      end
   end
   
   if love.keyboard.isDown("left") then
      if player.x > 0 then
         player.x = player.x - player.speed *dt   
      elseif player.x < 0 then
         player.x = 0
      end   
   end
   
   -- update the position of the bullets in the table 'bullets'
   for i,v in ipairs(bullets) do
      v.y = v.y - v.speed * dt
      if v.y < 100 then
         table.remove(bullets, 1)
      end   
   end

end

function love.draw()
   
   love.graphics.rectangle("fill", player.x, player.y, player.width, player.height)

   for i, v in ipairs(bullets) do
      love.graphics.rectangle("fill", v.x, v.y, v.width, v.height)
   end

end
By rate of fire you do you mean you want to be able to hold down space to fire continuously?
By "rate of fire" I mean that, once the player has fired a shot, he should have to wait a little before being able to fire the next one.
User avatar
veethree
Inner party member
Posts: 877
Joined: Sat Dec 10, 2011 7:18 pm

Re: How to make my bullets fire one by one

Post by veethree »

AdventureFan wrote:So more like this?

Code: Select all

 function love.keypressed(key)
      if key == " " then
          -- shoot a bullet (by adding it to the bullets table and passing a .y value)
         bullets[#bullets+1] = {
         y= 495,
         speed = 300,
         width = 10,
         height = 5,
         x = (player.x + player.width/2) - 5
         }
      end
   end   

function love.load()

   love.graphics.setBackgroundColor(0,0,0)
   love.graphics.setColor(255, 0, 0)
   
   -- setup the player
   player = {}
   player.x = 300
   player.y = 500
   player.width = 40
   player.height = 10
   player.speed = 300
   
   -- setup the bullets
   bullets = {}
   
   -- when a key is pressed, call the function love.keypressed
  love.keypressed()
   
end

function love.update(dt)

   -- move our player
   if love.keyboard.isDown("right") then
      if player.x < (800-player.width) then
         player.x = player.x + player.speed *dt
      elseif player.x < (800-player.width) then
         player.x = (800-player.width)
      end
   end
   
   if love.keyboard.isDown("left") then
      if player.x > 0 then
         player.x = player.x - player.speed *dt   
      elseif player.x < 0 then
         player.x = 0
      end   
   end
   
   -- update the position of the bullets in the table 'bullets'
   for i,v in ipairs(bullets) do
      v.y = v.y - v.speed * dt
      if v.y < 100 then
         table.remove(bullets, 1)
      end   
   end

end

function love.draw()
   
   love.graphics.rectangle("fill", player.x, player.y, player.width, player.height)

   for i, v in ipairs(bullets) do
      love.graphics.rectangle("fill", v.x, v.y, v.width, v.height)
   end

end
By rate of fire you do you mean you want to be able to hold down space to fire continuously?
By "rate of fire" I mean that, once the player has fired a shot, he should have to wait a little before being able to fire the next one.
Yeah that looks right, I personally keep love.load at the top as it's the first callback to be called so it makes sense, But it doesn't really matter.

As for the rate of fire, That's actually pretty easy.
Here's a code example (tested this time)

Code: Select all

function love.load()
	can_fire = true --If true player can shoot
	fire_wait = 0.3 --Delay between shots in seconds
	fire_tick = 0 --Used to count the seconds
end

function love.update(dt)
	if not can_fire then
		fire_tick = fire_tick + dt --Increases fire_tick by dt each frame which resaults in fire_tick increasing by 1 roughly every second
		if fire_tick > fire_wait then
			can_fire = true
			fire_tick = 0
		end
	end
end

function love.draw()
	love.graphics.print(tostring(can_fire), 12, 12)
end

function love.keypressed(key)
	if key == " " then
		if can_fire then
			--Shooting code
			can_fire = false
		end

	end
end
Commented some of it for clarity.

Using a similar concept you can make it so you shoot continuously while holding down space, which i personally think is a better mechanic in most cases.
AdventureFan
Prole
Posts: 14
Joined: Wed Jul 24, 2013 7:03 am

Re: How to make my bullets fire one by one

Post by AdventureFan »

I just finished adding the enemies, moving them down and then checking for collisions with the enemies.
This is my own code, so it probably won't be the most efficient or reusable one. But well, it seems to work all right! :)

Next step is to implement the 'slow rate of fire' code, then maybe build some menus, keep the score, etc.

Edit: I changed the code somewhat, to allow the use of an image file instead of 'just' a square box.

Code: Select all

function love.keypressed(key)
   if key == " " then
       -- shoot a bullet (by adding it to the bullets table and passing a .y value)
      bullets[#bullets+1] = {
      y = 495,
      speed = 300,
      width = 10,
      height = 5,
      x = (player.x + player.width/2) - 5
      }
   end
end   

function CheckCollision(ax1,ay1,aw,ah, bx1,by1,bw,bh)

  local ax2,ay2,bx2,by2 = ax1 + aw, ay1 + ah, bx1 + bw, by1 + bh
  return ax1 <  bx2 and ax2 < bx1 and ay1 < by2 and ay2 < by1
end

function love.load()

   -- setup the player
   player = {}
   player.x = 300
   player.y = 500
   player.speed = 300
   player.graphic = love.graphics.newImage('player_small.png')
   player.width = player.graphic:getWidth()
   player.height = player.graphic:getHeight()
 
   
   -- setup the bullets
   bullets = {}

   -- setup the enemies
   enemies = {}

   for i=0,7 do
      enemy = {}
      enemy.width = 40
      enemy.height = 20
      enemy.speed = 20
      enemy.x = i * (enemy.width+60) + 100
      enemy.y = enemy.height + 100
      table.insert(enemies, enemy)
   end
   
   -- when a key is pressed, call the function love.keypressed
  love.keypressed()
   
end

function love.update(dt)
   -- move our player
   if love.keyboard.isDown("right") then
      if player.x < (800-player.width) then
         player.x = player.x + player.speed *dt
      elseif player.x < (800-player.width) then
         player.x = (800-player.width)
      end
   end
   
   if love.keyboard.isDown("left") then
      if player.x > 0 then
         player.x = player.x - player.speed *dt   
      elseif player.x < 0 then
         player.x = 0
      end   
   end

   -- update the position of the enemies in the table 'enemies'
   for i,v in ipairs(enemies) do
      v.y = v.y + v.speed * dt     
   end
   
   -- update the position of the bullets in the table 'bullets'
   for i,v in ipairs(bullets) do
      
      -- move the bullets upward
      v.y = v.y - v.speed * dt

      
      -- Check for collision between bullets and enemies. If they collide, remove both bullets and emies
      for ii,vv in ipairs(enemies) do
         if v.y < vv.y and (v.x > vv.x and v.x < vv.x+vv.width) then
            table.remove(enemies, ii)
            table.remove(bullets, i)
         end
      end

      -- remove shots that are out of range
       if v.y < 120 then
         table.remove(bullets, i)
      end

   end

end

function love.draw()
   
   love.graphics.setBackgroundColor(0,0,0)
   love.graphics.setColor(255, 0, 0)
   
   -- draw the player   
   love.graphics.draw(player.graphic, player.x, player.y)

   -- draw the bullets
   love.graphics.setColor(255, 255, 255)
   for i, v in ipairs(bullets) do
      love.graphics.rectangle("fill", v.x, v.y, v.width, v.height)
   end

   -- draw the enemies
   love.graphics.setColor(0, 255, 255)
   for i, v in ipairs(enemies) do
      love.graphics.rectangle("fill", v.x, v.y, v.width, v.height)
   end

end
Post Reply

Who is online

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