How to make a death animation in LOVE2D? - love2d

I have created collision for the player and enemies (the collision isn't perfect but works for the most part) in my game but am having a hard time figuring out how to create a death animation for the player.
I want the player's ship to explode when the enemies hit you, how would I go about this?
Here is my player file:
Player = Class{}
function Player:init(x, y, width, height)
self.player = {}
self.x = x
self.y = y
self.height = height
self.dx = 0
self.image = love.graphics.newImage('images/player.png')
self.width = self.image:getWidth()
self.fire_sound = love.audio.newSource('sounds/laser.wav', 'static')
self.fire_sound:setVolume(.25)
self.player_explosion = love.audio.newSource('sounds/player_explosion.wav', 'static')
self.player_explosion:setVolume(25)
self.cooldown = 10
self.bullets = {}
end
function Player:update(dt)
self.x = self.x + self.dx * dt
if self.x <= 0 then
self.dx = 0
self.x = 0
end
if self.x >= WINDOW_WIDTH - self.width * 4 then
self.dx = 0
self.x = WINDOW_WIDTH - self.width * 4
end
end
function Player:fire()
if self.cooldown <= 0 then
love.audio.play(player.fire_sound)
if BULLET_COUNTER >= 1 then
love.audio.stop(player.fire_sound)
love.audio.play(player.fire_sound)
self.cooldown = 30
bullet = {}
bullet.x = player.x + 25
bullet.y = player.y + 5
table.insert(self.bullets, bullet)
BULLET_COUNTER = 0
return
end
self.cooldown = 10
bullet = {}
bullet.x = self.x + 25
bullet.y = self.y + 5
table.insert(self.bullets, bullet)
BULLET_COUNTER = BULLET_COUNTER + 1
end
end
function Player:render()
love.graphics.setColor(255, 255, 255)
love.graphics.draw(self.image, self.x, self.y, 0, 4)
end
function Player:checkCollision(enemies)
for i,e in ipairs(enemies) do
if e.y + e.height >= self.y and e.y <= self.y + self.height and e.x <= self.x + self.width and e.x + e.width >= self.x + self.width then
love.audio.stop(self.player_explosion)
love.audio.play(self.player_explosion)
table.remove(enemies, i)
LIVES = LIVES - 1
end
end
end
Here is my main file if you need it:
Class = require 'class'
require 'Player'
GAME_STATE = 'start'
WINDOW_WIDTH = 720
WINDOW_HEIGHT = 900
love.window.setMode(WINDOW_WIDTH, WINDOW_HEIGHT)
SCORE = 0
LIVES = 3
PLAYER_SPEED = 300
-- map variables
map = {}
map.image = love.graphics.newImage('images/background_final.png')
map.width = 720
map.height = 900
map.music = love.audio.newSource('sounds/Battle-in-the-Stars.wav', 'static')
-- enemy variables
enemy = {}
enemies_controller = {}
enemies_controller.enemies = {}
alienImage = love.graphics.newImage('images/alien2.png')
alienship1_Image = love.graphics.newImage('images/enemyship1.png')
alienship2_Image = love.graphics.newImage('images/enemyship4.png')
enemies_controller.explosion = love.audio.newSource('sounds/explosion.wav', 'static')
enemies_controller.explosion:setVolume(.25)
spawnTimer = 0
spawnTimer_Max = 0.75
SMALL_WIDTH = 70
SMALL_HEIGHT = 70
-- star variables for warp effect
star = {}
stars_controller = {}
stars_controller.stars = {}
starSpawnTimer = 0
starSpawnTimer_Max = 0.25
STAR_WIDTH = 100
STAR_HEIGHT = 100
BULLET_COUNTER = 0
-- this function allows us to scale an image we created to a desired height and width
function getImageScaleForNewDimensions(image, newWidth, newHeight)
local currentWidth, currentHeight = image:getDimensions()
return ( newWidth / currentWidth ), ( newHeight / currentHeight )
end
-- check collisions for enemies and bullets
function checkCollisions(enemies, bullets)
for i,e in ipairs(enemies) do
for j,b in ipairs(bullets) do
-- checks if the hitboxes for the bullet and the enemy collide
-- if they do remove the enemy and the bullet that collided
if b.y <= e.y + e.height and b.x > e.x and b.x < e.x + e.width then
love.audio.stop(enemies_controller.explosion)
table.remove(enemies, i)
table.remove(bullets, j)
love.audio.play(enemies_controller.explosion)
SCORE = SCORE + 10
end
end
end
end
function love.load()
love.graphics.setDefaultFilter('nearest', 'nearest')
love.window.setTitle('Space Attack')
title_largeFont = love.graphics.newFont('fonts/Mario-Kart-DS.ttf', 40)
title_smallFont = love.graphics.newFont('fonts/Mario-Kart-DS.ttf', 20)
largeFont = love.graphics.newFont('fonts/04B_30__.TTF', 32)
smallFont = love.graphics.newFont('fonts/04B_30__.TTF', 20)
love.graphics.setFont(largeFont)
player = Player(WINDOW_WIDTH / 2 - 40, WINDOW_HEIGHT - 100, 10, 10)
map.music:setLooping(true)
map.music:setVolume(.25)
map.music:play()
end
function enemies_controller:spawnEnemy(x, y, n)
enemy = {}
enemy.x = x
enemy.y = y
enemy.width = SMALL_WIDTH
enemy.height = SMALL_HEIGHT
enemy.bullets = {}
enemy.cooldown = 10
-- n is a random variable that determines what type of enemy spawns
if n == 0 then
enemy.image = alienImage
enemy.speed = 5
elseif n == 1 then
enemy.image = alienship1_Image
enemy.speed = 6
else
enemy.image = alienship2_Image
enemy.speed = 7
end
table.insert(self.enemies, enemy)
end
function stars_controller:spawnStars(x, y)
star = {}
star.x = x
star.y = y
star.speed = 10
star.image = love.graphics.newImage('images/star.png')
star.width = SMALL_WIDTH
star.height = SMALL_HEIGHT
table.insert(self.stars, star)
end
function love.update(dt)
player.cooldown = player.cooldown - 1
checkCollisions(enemies_controller.enemies, player.bullets)
player:checkCollision(enemies_controller.enemies)
-- checks spawn timer, if > 0 don't spawn an enemy but if it is spawn an enemy
if spawnTimer > 0 then
spawnTimer = spawnTimer - dt
else
if GAME_STATE == 'play' then
enemies_controller:spawnEnemy(love.math.random(0, WINDOW_WIDTH - SMALL_WIDTH), 0, love.math.random(0,2))
spawnTimer = spawnTimer_Max
end
end
-- same situation as spawn timer for enemies, but this is for the warp effect
if starSpawnTimer > 0 then
starSpawnTimer = starSpawnTimer - dt
else
stars_controller:spawnStars(love.math.random(0, WINDOW_WIDTH - SMALL_WIDTH), 0)
starSpawnTimer = starSpawnTimer_Max
end
-- checking user input to move left and right
if love.keyboard.isDown('right') then
if GAME_STATE == 'play' then
player.dx = PLAYER_SPEED
end
elseif love.keyboard.isDown('left') then
if GAME_STATE == 'play' then
player.dx = -PLAYER_SPEED
end
else
player.dx = 0
end
-- checking user input for firing function
if love.keyboard.isDown('space') then
if GAME_STATE == 'play' then
player:fire()
end
end
-- this removes enemies if they are too far off the scree
-- also how the enemies move downards
-- NEED TO ADD MOVEMENT FUNCTIONS HERE FOR DIFFERENT ENEMIES
for i,e in ipairs(enemies_controller.enemies) do
e.y = e.y + e.speed
if e.y > WINDOW_HEIGHT then
table.remove(enemies_controller.enemies, i)
end
end
-- removes bullets if they travel too far off the screen
-- this also moves the bullets upwards
for i,v in ipairs(player.bullets) do
if v.y < 0 then
table.remove(player.bullets, i)
end
v.y = v.y - 8
end
-- removes stars if they travel too far off screen
-- moves the stars to create warp effect
for i,s in ipairs(stars_controller.stars) do
s.y = s.y + s.speed
if s.y > WINDOW_HEIGHT then
table.remove(stars_controller.stars, i)
end
end
-- checks lives to determine game state
if LIVES == 0 then
GAME_STATE = 'end'
end
player:update(dt)
end
-- key pressed function so we can exit the game and start the game
function love.keypressed(key)
if key == 'escape' then
love.event.quit()
elseif key == 'enter' or key == 'return' then
if GAME_STATE == 'start' then
GAME_STATE = 'play'
end
end
end
function love.draw()
-- checks gamestate and if it is
if GAME_STATE == 'start' then
-- draw the warp effect
for _,s in pairs(stars_controller.stars) do
local scaleX, scaleY = getImageScaleForNewDimensions(s.image, STAR_WIDTH, STAR_HEIGHT)
love.graphics.draw(s.image, s.x, s.y, 0, 2, 4)
end
-- draw the player
player:render()
love.graphics.setColor(200, 0, 200, 255)
love.graphics.setFont(title_largeFont)
love.graphics.print('WELCOME TO SPACE ATTACK!', WINDOW_WIDTH / 2 - 290, WINDOW_HEIGHT / 2)
love.graphics.setFont(title_smallFont)
love.graphics.print('press ENTER to begin!', WINDOW_WIDTH / 2 - 110, WINDOW_HEIGHT / 2 + 100)
end
if GAME_STATE == 'play' then
-- draw the map
love.graphics.setColor(255, 255, 255)
local map_width, map_height = getImageScaleForNewDimensions(map.image, map.width, map.height)
love.graphics.draw(map.image, 0, 0, 0, map_width, map_height)
-- draw the enemies
for _,e in pairs(enemies_controller.enemies) do
local scaleX, scaleY = getImageScaleForNewDimensions(e.image, SMALL_WIDTH, SMALL_HEIGHT)
love.graphics.draw(e.image, e.x, e.y, 0, scaleX, scaleY)
end
-- draw the warp effect
for _,s in pairs(stars_controller.stars) do
love.graphics.draw(s.image, s.x, s.y, 0, 1, 4)
end
-- draw the player
player:render()
-- draw the bullets
love.graphics.setColor(255, 255, 255)
for _,v in pairs(player.bullets) do
love.graphics.rectangle('fill', v.x, v.y, 10, 10)
end
displayScore()
displayLives()
end
if GAME_STATE == 'end' then
love.audio.stop()
love.graphics.setColor(200, 0, 200, 255)
love.graphics.setFont(title_largeFont)
love.graphics.print('GAME OVER!', WINDOW_WIDTH / 2 - 120, WINDOW_HEIGHT / 2)
love.graphics.setFont(title_smallFont)
love.graphics.print('score ' .. tostring(SCORE), WINDOW_WIDTH / 2 - 60, WINDOW_HEIGHT / 2 + 50)
end
end
function displayScore()
if GAME_STATE == 'play' then
love.graphics.setColor(0, 255, 0, 255)
love.graphics.setFont(largeFont)
love.graphics.print('Score: ' .. tostring(SCORE), WINDOW_WIDTH / 2 - 130, 10)
end
end
function displayLives()
if GAME_STATE == 'play' then
love.graphics.setColor(255, 0, 0, 255)
love.graphics.setFont(smallFont)
love.graphics.print('Lives: ' .. tostring(LIVES), WINDOW_WIDTH - 150, 10)
end
end

I want the player's ship to explode when the enemies hit you, how would I go about this?
I assume you are asking about creating the actual visual effect of an explosion. LÖVE's particle system has all the functionality to create that sort of animation / effect, but can be a bit overkill if you are just starting out.
A simple alternative would be to remove the player's ship and replace it with an animated sprite of an explosion. You could use a library to handle the animation code for you, or just use it as a reference point for your own animation code.
An animation usually consists of multiple frames or images which are displayed in a sequence. E.g. a very simple animation could look like this:
Frame: Small Explosion
Frame: Explosion gets bigger
Frame: Explosion gets smaller again
Frame: Smoke appears
Frame: Smoke vanishes and animation is finished
Each of these frames would be contained in one big image or spritesheet.
In LÖVE you will have to load the spritesheet and divide it into its frames by using quads:
local spritesheet = love.graphics.newImage("animation.png")
-- Create a quad for each frame in the spritesheet.
-- Here we assume that each frame is a 16x16 square inside of the spritesheet.
local animation = {
love.graphics.newQuad(0, 0, 16, 16, spritesheet:getDimensions()),
love.graphics.newQuad(16, 0, 16, 16, spritesheet:getDimensions()),
-- more frames
}
-- To display the animation we draw one quad each frame and then update the index variable.
-- Of course in a real game you'd add a delay between each frame and check for the boundaries
-- of the array and so on.
local i = 1
function love.draw()
love.graphics.draw(spritesheet, animation[i], 50, 50)
i = i + 1
end
This is not a perfect example, but I hope it shows the general idea of how animations can work.

Related

Looping trough a list of objects in lua

I'm writing lua to script inside a videogame called Dual Universe, I'm trying to loop trough an array filled with objects and call their methods but it seems to turn my objects into numbers for some reason.
Here is the error message: attempt to index a number value (local 'projectile')
it's in the for loop of ProjectileGroup:move(), where I call projectile:move().
--Projectile class
Projectile = {x = 0, y = 0, z = 0, six = 0, siy = 0, siz = 0, spx = 0, spy = 0, spz = 0}
function Projectile:new (o,x,y,z,six,siy,siz,spx,spy,spz)
o = o or {}
setmetatable(o, self)
self.__index = self
self.x = x
self.y = y
self.z = z
self.six = six
self.siy = siy
self.siz = siz
self.spx = spx
self.spy = spy
self.spz = spz
return o
end
function Projectile:move ()
self.x = self.x + self.spx
self.y = self.y + self.spy
self.z = self.z + self.spz
end
function Projectile:log ()
system.print('x: '..tostring(self.x)..'|y: '..tostring(self.y)..'|z: '..tostring(self.z)..'|six :'..tostring(self.six)..'|siy :'..tostring(self.siy)..'|siz :'..tostring(self.siz)..'|spx :'..tostring(self.spx)..'|spy :'..tostring(self.spy)..'|spz :'..tostring(self.spz))
end
--ProjectileGroup
ProjectileGroup = {projectiles=0}
function ProjectileGroup:new (o,projectiles)
o = o or {}
setmetatable(o,self)
self.__index = self
self.projectiles = projectiles
return o
end
function ProjectileGroup:add_projectile(projectile)
table.insert(self.projectiles, projectile)
end
function ProjectileGroup:move()
for projectile in pairs(self.projectiles) do
projectile:move()
end
end
function ProjectileGroup:log()
for projectile in pairs(self.projectiles) do
projectile:log()
end
end
projectileA = Projectile:new(nil,0,0,0,10,10,10,0.1,0.3,0.4)
projectileB = Projectile:new(nil,0,0,0,10,10,10,0.4,0.2,-0.4)
projectileC = Projectile:new(nil,0,0,0,10,10,10,0.1,0.05,0.1)
projectileGroup = ProjectileGroup:new(nil,{projectileA,projectileB,projectileC})
projectileGroup:move()

in love2d im having issues with collision ditection

I am trying to make it so when my red circle touches the white circle it cannot move past it.
I have tried basic if statements but it does not work. Anyway here is the code...
win = love.window.setMode(600, 600)
Xpos = 300
TX = 50
function love.draw()
love.graphics.setColor(1, 1, 1)
love.graphics.circle("fill", Xpos, 200, 25)
love.graphics.setColor(1, 0, 0)
love.graphics.circle("fill", TX, 200, 60)
if Xpos == TX then
Xpos = Xpos + 0.1
end
if TX >= Xpos then
TX = TX - 35
end
if love.keyboard.isDown("right") then
TX = TX + 5
end
end
Try the basic AABB collision detector:
function Something:collides(target)
if self.x > target.x + target.width or target.x > self.x + self.width then
return false
end
if self.y > target.y + target.height or target.y > self.y + self.height then
return false
end
end

How can I call a class function inside a class using middleclass in Lua

I've been trying to figure out how to call a class function inside another class function in Lua, but the way I thought would work doesn't.
local class = require 'libs.middleclass'
local Level = class('Level')
function Level:initialize(width, height, tileSize)
self.width = width
self.height = height
self.tileSize = tileSize
self.data = {}
--Generate a 1D Array for the map data
for x = 1, self.width do
for y = 1, self.height do
table.insert(self.data, 0)
end
end
end
function Level:get(x, y)
return self.data[x + (y-1) * self.width]
end
function Level:set(x, y, type)
self.data[x + (y - 1) * self.width] = type
end
function Level:draw()
for x = 1, self.width do
for y = 1, self.height do
if self.Level:get(x, y) == 0 then
love.graphics.setColor(255, 255, 255)
love.graphics.rectangle("fill", x * tileSize, y * tileSize, tileSize, tileSize)
love.graphics.setColor(0, 0, 0)
love.graphics.rectangle("line", x * tileSize, y * tileSize, tileSize, tileSize)
elseif self.Level:get(x, y) == 1 then
love.graphics.setColor(255, 255, 255)
love.graphics.rectangle("fill", x * tileSize, y * tileSize, tileSize, tileSize)
end
end
end
end
return Level
Not sure if you need all of the code, but this is what I have in my level.lua object class thingy. I thought that calling it using self.method would work, but it gives me:
objects/level.lua:29: attempt to index field 'Level' (a nil value)
That's about all I can say about it since I'm new to doing OOP in Lua, also I'm using the Love2D framework if that is in any way relevant.
Thanks for taking your time to answer.
So Egor answered the question, but did so in the comments. Anyway all I had to do was use self instead of self.Level. Thanks Egor.

How to set max length of a draw line?(Corona SDK)

I am currently working on some project and it contains an option of a line to be drawn. But I can't normally set max length of line. How can I do that?
Here is the code of distance formula (as I think):
local function distanceBetween( e, prev )
local distanceBetween = 0
local dist_x = e.x - prevX ; local dist_y = e.y - prevY;
local distanceBetween = math.sqrt((dist_x*dist_x) + (dist_y*dist_y))
return distanceBetween
end
And here is a line code:
local lines = {}
local myLines = {}
local prevX,prevY
local i = 1
--prevX = x1, prevY = y1, e.x = x2, e.y = y2
local function drawLine(e)
distanceBetween = false
if(e.phase == "began") then
myLines[i] = {}
prevX = e.x
prevY = e.y
elseif(e.phase == "moved")then
if prevX then
myLines[i][#myLines[i] + 1] = display.newLine(prevX,prevY,e.x,e.y)
myLines[i][#myLines[i]]:setColor(255,255,0)
myLines[i][#myLines[i]].width = 5
myLines[i][#myLines[i]].alpha=1
myLines[i][#myLines[i]].myName = "Line"
dist_x = e.x - prevX
dist_y = e.y - prevY
physics.addBody(myLines[i][#myLines[i]], "static", { density = 1, friction = 0.5, bounce = 1.6, shape = {0, 0, dist_x, dist_y, 0, 0} } )
lineGroup:insert(myLines[i][#myLines[i]])
distanceBetween = true
if (distanceBetween > 50) then
drawLine("ended")
end
end -- this is how i think line should be maximized
prevX = e.x
prevY = e.y
elseif(e.phase == "ended")then
prevX = nil
prevY = nil
i = i + 1
removeLine(myLines[#myLines-1])
end
end
Runtime:addEventListener("touch",drawLine);
Edited: this works fine in my Corona app. Is this what you're trying to do?
local physics = require "physics"
physics.start()
local lines = {}
local lineGroup = display.newGroup()
local prevX,prevY
local isDrawing = false
local i = 0
local function distanceBetween(x1, y1, x2, y2)
local dist_x = x2 - x1
local dist_y = y2 - y1
local distanceBetween = math.sqrt((dist_x*dist_x) + (dist_y*dist_y))
return distanceBetween
end
local function drawLine(e)
if(e.phase == "began") then
prevX = e.x
prevY = e.y
isDrawing = true
i = i + 1
elseif(e.phase == "moved") then
local distance = distanceBetween(prevX, prevY, e.x, e.y)
if(isDrawing and distance < 100) then
if(lines[i]) then lineGroup:remove(i) end
lines[i] = display.newLine(prevX, prevY, e.x, e.y)
lines[i]:setColor(255, 255, 0)
lines[i].width = 5
local dist_x = e.x - prevX
local dist_y = e.y - prevY
physics.addBody(lines[i], "static", { density = 1, friction = 0.5, bounce = 1.6, shape = {0, 0, dist_x, dist_y, 0, 0} } )
lineGroup:insert(lines[i])
end
elseif(e.phase == "ended") then
isDrawing = false
end
end
Runtime:addEventListener("touch",drawLine)

How to Detect if Sprite is going Up or Down

I Use the following code to detect if a sprite should be going up or down and the response
If (pos.Y + 100) >= Sprite.BottomY Then
Going_up = True
pos.Y = Sprite.BottomY - 130
End If
If pos.Y <= Sprite.TopY Then
Going_up = False
pos.Y = Sprite.TopY - 1
Vel.Y = 3
End If
then
If Going_up Then
Vel.Y -= CSng(gameTime.ElapsedGameTime.TotalMilliseconds / 40)
pos.Y -= Vel.Y
Else
Vel.Y += CSng(gameTime.ElapsedGameTime.TotalMilliseconds / 40)
pos.Y += Vel.Y
End If
Sprite.velocity = Vel
Sprite.position = pos
but it's pretty terrible. It only works when the sprite starts at the top, and when I want to change the BottomY and TopY, it just starts glitching. What is a better to detect if the sprite should be going up or down?
Can you not just do:
If (Vel.Y > 0) Then
Going_up = True
' Do rest of up code
Else If (Vel.Y < 0) Then
Going_up = False
' Do rest of not going up code
End If
Something that might help is utilizing a getter and setter like this:
Essentially, you could create a tempX and tempY. Every 100ms compare the sprite's current properties with the temp variables.
Dim tempX As Double
Dim tempY As Double
While True
tempY = sprite.GetY()
If gameTime.ElapsedGameTime.TotalMilliseconds Mod 100 = 0 Then 'or something to sample the game time
If sprite.Vel.Y > tempY Then
Going_up = True
Else
Going_up = False
End If
End IF
End While
So you're sort of taking a sample of your game board to create approximate reference points that you can compare the actual value to. Hope that helps!
-sf