How to make an object move from top to bottom in Lua? - lua

In a game I'm making, there's a square where the game is happening inside. I want the circles that spawn in to start at the top and travel down to the bottom. In the original program, they travel from left to right. How would I go about this? I don't know how to get it to start at the top and travel down instead of starting at the left and traveling to the right.
Here's the original code (I know it's a lot, sorry). I am trying to help a friend.
--Made by Joms or /u/jomy582
--Please credit me if using this in a battle.
spawntimer = 0
timerspawn = 0
storedtime = Time.time
bullets = {}
bulletsbig = {}
bulletswarn = {}
dir = 1
bigheight = {33, 98}
function Update()
spawntimer = spawntimer + (Time.dt*Time.mult)
timerspawn = timerspawn + (Time.dt*Time.mult)
--normal bullets
--change the number in the if statement to make them spawn more frequently/less frequently
--EX: spawntimer > 0.2 spawns them pretty fast
--EX2: spawntimer > 1 spawns them pretty slow
--Make sure to change the subtraction method in the if statement
if spawntimer > 0.16 then
spawntimer = spawntimer-0.16
local bullet = CreateProjectile("bullet", -Arena.width/2, math.random(-Arena.height/2, Arena.height/2))
bullet.SetVar("deadly", true)
table.insert(bullets, bullet)
end
--warning. spawns a warning every 5 seconds
--You could change it, but that may cause some bugs
if timerspawn > 2.2 then
timerspawn = timerspawn-2.2
dir = math.random(1,2)
local bulletwarn = CreateProjectile("warning", 0, Arena.height/2 - bigheight[dir])
bulletwarn.SetVar("warningtimer", 0)
bulletwarn.SetVar("soundtimer", 0)
bulletwarn.SetVar("animtimer", 0)
bulletwarn.SetVar("anim", 1)
table.insert(bulletswarn, bulletwarn)
end
--controlling normal bullets
--a simple method that moves the bullets 1 pixel each frame
--you can change it by editing the bullet.move
--EX: bullet.Move(5*Time.mult, 0) moves the bullets 5 pixels each frame
for i=1, #bullets do
local bullet = bullets[i]
if bullet.isactive then
bullet.Move(2*Time.mult, 0)
if bullet.y > -Arena.height/2 then
bullet.Remove()
end
end
end
--controlling warning timer
--a method that controls the warning timer
for i=1, #bulletswarn do
local bullet = bulletswarn[i]
if bullet.isactive then
local warningtimer = bullet.GetVar("warningtimer") + (Time.mult*Time.dt)
local soundtimer = bullet.GetVar("soundtimer") + (Time.mult*Time.dt)
local animtimer = bullet.GetVar("animtimer") + (Time.mult*Time.dt)
local bulletSprite = bullet.sprite
local anim = bullet.GetVar("anim")
--flashing colors
--change the animtimer > TIME where time is how often you want the warning to blink
--warnings last for 3 seconds. Can change that as well
--to get different colors, find the rgb value of the color you want and insert them below
if animtimer > 0.08 then
animtimer = animtimer-0.08
if anim == 1 then
local r = 0 --put Red value here
local g = 0 --put Green value here
local b = 169 --put Blue value here
bulletSprite.color = {r/255, g/255, b/255} -- changes the color to whatever you'd like. Uses RGB.
bullet.SetVar("anim", 2)
elseif anim == 2 then
local r = 0 --put Red value here
local g = 0 --put Green value here
local b = 255 --put Blue value here
bulletSprite.color = {r/255, g/255, b/255} -- changes the color to whatever you'd like. Uses RGB.
bullet.SetVar("anim", 1)
end
end
--plays a timer evert 10 frames for 3 seconds.
--change the soundname to change the sound
--change the soundtimer > 0.16 to change how often it plays
--can change how long it lasts as well by changing the less than statement
if soundtimer > 0.10 then
soundtimer = soundtimer-0.10
Audio.PlaySound("alarm")
end
--this controls when to spawn the bullets
--change the statement to change how long the warning timer is
if warningtimer > 2 then
warningtimer = warningtimer-2
Audio.PlaySound("shoot")
local bullet1 = CreateProjectile("leftbigbullet", Arena.width/2+30, Arena.height/2 - bigheight[dir]) --where to spawn them
bullet1.SetVar("speed", -4) --how fast
bullet1.SetVar("deadly", true)
local bullet2 = CreateProjectile("rightbigbullet", -Arena.width/2-30, Arena.height/2 - bigheight[dir]) --where to spawn them
bullet2.SetVar("speed", 4) --how fast
bullet2.SetVar("deadly", true)
table.insert(bulletsbig, bullet1)
table.insert(bulletsbig, bullet2)
bullet.Remove()
end
bullet.SetVar("warningtimer", warningtimer)
bullet.SetVar("animtimer", animtimer)
bullet.SetVar("soundtimer", soundtimer)
end
end
--controlling big bullets
--this method controls the big bullets
for i=1, #bulletsbig do
local bullet = bulletsbig[i]
if bullet.isactive then
local speed = bullet.GetVar("speed")
bullet.SendToBottom()
bullet.Move(speed*Time.mult, 0)
if bullet.absx > 700 or bullet.absx < -70 then
bullet.Remove()
end
end
end
end
function OnHit(bullet)
if(bullet.getVar("deadly")) then
Player.Hurt(3)
end
end

Related

How can I change color depending on health?

I want to know how to edit my script below to make the color lower or higher depending on the health amount instead of a set amount in the script. Everything works currently, i'm just trying to edit the script to do what I said above.
local parts = script.Parent.Parent:WaitForChild("Collectables"):GetChildren()
for i, v in pairs(parts) do
if v:IsA("Part") then
if v:WaitForChild("ClickDetector") then
if v:FindFirstChild("Values") then
local Values = v:FindFirstChild("Values")
Values.Red.Value = 0
Values.Green.Value = 255
Values.Blue.Value = 0
Values.Health.Value = 100
Values.Change.Value = true
v.SurfaceGui.TextLabel.Text = 100
Values.Color.Value = Color3.fromRGB(Values.Red.Value,Values.Green.Value,Values.Blue.Value)
v.SurfaceGui.TextLabel.TextColor3 = Values.Color.Value
v.ClickDetector.MouseClick:Connect(function()
if Values.Health.Value > 5 then
Values.Health.Value -= 5
else
v:Destroy()
end
if v:FindFirstChild("SurfaceGui") then
v.SurfaceGui.TextLabel.Text = v.SurfaceGui.TextLabel.Text - 5
if Values.Change.Value == true then
if Values.Red.Value < 250 then
Values.Red.Value += 50
elseif Values.Red.Value == 250 then
Values.Red.Value += 5
elseif Values.Red.Value == 255 and Values.Green.Value > 5 then
Values.Green.Value -= 50
elseif Values.Green.Value == 5 then
Values.Green.Value -= 5
Values.Change.Value = false
end
Values.Color.Value = Color3.fromRGB(Values.Red.Value,Values.Green.Value,Values.Blue.Value)
v.SurfaceGui.TextLabel.TextColor3 = Values.Color.Value
end
end
end)
end
end
end
end
Rather than use your stepwise function for calculating the color, a more simple way to handle this might be to define the colors you want ahead of time or simplify it down to ranges of colors based on health. Like,
if it's between 100 - 60, the color is green.
if it's between 60 - 30, it's yellow.
if it's less than 30, it is red.
And to make the color react to the health, I would set up a series of dependencies using the Changed signal on your NumberValue objects.
The ClickDetector's only job is to modify the health of the block. But when the health changes, the color values change. And when the color values change, the text color changes too.
First off, delete the R, G, and B NumberValues, you can set the text color without them.
local DEFAULT_HEALTH = 100
local HEALTH_PER_CLICK = 5
-- colors are sorted by health in descending order, add more as desired
local COLORS = {
{ health = 60, color = Color3.fromRGB( 0, 255, 0)}, -- green
{ health = 30, color = Color3.fromRGB(255, 255, 0)}, -- yellow
{ health = 0, color = Color3.fromRGB(255, 0, 0)}, -- red
}
local function observeHealthChanges(values, textLabel)
-- whenever the health changes, update the text and the text color
local health = values.Health
health.Changed:Connect(function(newValue)
textLabel.Text = tostring(newValue)
-- set the color based on the health by looping through the colors to find the right ones
for i, colorData in ipairs(COLORS) do
local colorRange = colorData.health
local healthColor = colorData.color
if newValue > colorRange then
textLabel.TextColor3 = healthColor
break
end
end
end)
end
local function observeClicks(part, values, detector)
local health = values.Health
-- listen for clicks to reduce the health
detector.MouseClick:Connect(function()
if health.Value > HEALTH_PER_CLICK then
health.Value -= HEALTH_PER_CLICK
else
part:Destroy()
end
end)
end
local parts = script.Parent.Parent:WaitForChild("Collectables"):GetChildren()
for _, v in ipairs(parts) do
-- escape if the object isn't a part
if v:IsA("Part") then
-- escape if the part doesn't have Values, a ClickDetector, and a SurfaceGui
local Values = v.Values
local Detector = v.ClickDetector
local Gui = v.SurfaceGui
if Values and Detector and Gui then
-- register the change listeners
local textLabel = v.SurfaceGui.TextLabel
observeHealthChanges(Values, textLabel)
observeClicks(v, Values, Detector)
-- set the health and all the rest will follow
Values.Health.Value = DEFAULT_HEALTH
end
end
end

Attempt to index field (a nil value), for an Object

I have a problem with trying to add things to a LOVE2D version of Match-3. (From the CS50 course)
I added a function swapTiles() into my Board class and made a class object called self.board in a Class called PlayState. Then when I try to access the new function, it says this error:
Error
src/states/PlayState.lua:155: attempt to index field 'board' (a nil value)
I'll provide my Board and PlayState class below:
Board: (keep in mind the new function is literally in the code)
Board = Class{}
function Board:init(x, y, level) -- Added "level" as an integer for the block variations.
self.x = x
self.y = y
self.matches = {}
self.level = level
self:initializeTiles()
end
function Board:swapTiles(tile1, tile2)
-- swap grid positions of tiles
local tempX = tile1.gridX
local tempY = tile1.gridY
tile1.gridX = tile2.gridX
tile1.gridY = tile2.gridY
tile2.gridX = tempX
tile2.gridY = tempY
-- swap tiles in the tiles table
self.tiles[tile1.gridY][tile1.gridX] = tile1
self.tiles[tile2.gridY][tile2.gridX] = tile2
end
function Board:initializeTiles()
self.tiles = {}
-- There should only be two shiny tiles.
for tileY = 1, 8 do
-- empty table that will serve as a new row
table.insert(self.tiles, {})
for tileX = 1, 8 do
self.isPowerup = false
if math.random(1, 25) == 4 then
self.isPowerup = true
end
-- create a new tile at X,Y with a random color and variety
table.insert(self.tiles[tileY], Tile(tileX, tileY, math.random(18), math.min(8, math.random(1, self.level)), self.isPowerup))
end
end
while self:calculateMatches() do
-- recursively initialize if matches were returned so we always have
-- a matchless board on start
self:initializeTiles()
end
end
--[[
Goes left to right, top to bottom in the board, calculating matches by counting consecutive
tiles of the same color. Doesn't need to check the last tile in every row or column if the
last two haven't been a match.
]]
function Board:calculateMatches()
local matches = {}
-- how many of the same color blocks in a row we've found
local matchNum = 1
-- horizontal matches first
for y = 1, 8 do
local colorToMatch = self.tiles[y][1].color
matchNum = 1
-- every horizontal tile
for x = 2, 8 do
-- if this is the same color as the one we're trying to match...
if self.tiles[y][x].color == colorToMatch then
matchNum = matchNum + 1
else
-- set this as the new color we want to watch for
colorToMatch = self.tiles[y][x].color
-- if we have a match of 3 or more up to now, add it to our matches table
if matchNum >= 3 then
local match = {}
-- go backwards from here by matchNum
for x2 = x - 1, x - matchNum, -1 do
-- add each tile to the match that's in that match
table.insert(match, self.tiles[y][x2])
-- Shiny Check
if self.tiles[y][x2].isShiny == true then
for i = 1, 8 do
table.insert(match, self.tiles[y][i])
end
end
end
-- add this match to our total matches table
table.insert(matches, match)
end
-- don't need to check last two if they won't be in a match
if x >= 7 then
break
end
matchNum = 1
end
end
-- account for the last row ending with a match
if matchNum >= 3 then
local match = {}
-- go backwards from end of last row by matchNum
for x = 8, 8 - matchNum + 1, -1 do
table.insert(match, self.tiles[y][x])
end
table.insert(matches, match)
end
end
-- vertical matches
for x = 1, 8 do
local colorToMatch = self.tiles[1][x].color
matchNum = 1
-- every vertical tile
for y = 2, 8 do
if self.tiles[y][x].color == colorToMatch then
matchNum = matchNum + 1
else
colorToMatch = self.tiles[y][x].color
if matchNum >= 3 then
local match = {}
for y2 = y - 1, y - matchNum, -1 do
table.insert(match, self.tiles[y2][x])
if self.tiles[y2][x].isShiny == true then
for i = 1, 8 do
table.insert(match, self.tiles[i][x])
end
end
end
table.insert(matches, match)
end
matchNum = 1
-- don't need to check last two if they won't be in a match
if y >= 7 then
break
end
end
end
-- account for the last column ending with a match
if matchNum >= 3 then
local match = {}
-- go backwards from end of last row by matchNum
for y = 8, 8 - matchNum, -1 do
table.insert(match, self.tiles[y][x])
end
table.insert(matches, match)
end
end
-- store matches for later reference
self.matches = matches
-- return matches table if > 0, else just return false
return #self.matches > 0 and self.matches or false
end
--[[
Remove the matches from the Board by just setting the Tile slots within
them to nil, then setting self.matches to nil.
]]
function Board:removeMatches()
for k, match in pairs(self.matches) do
for k, tile in pairs(match) do
self.tiles[tile.gridY][tile.gridX] = nil
end
end
self.matches = nil
end
--[[
Shifts down all of the tiles that now have spaces below them, then returns a table that
contains tweening information for these new tiles.
]]
function Board:getFallingTiles()
-- tween table, with tiles as keys and their x and y as the to values
local tweens = {}
-- for each column, go up tile by tile till we hit a space
for x = 1, 8 do
local space = false
local spaceY = 0
local y = 8
while y >= 1 do
-- if our last tile was a space...
local tile = self.tiles[y][x]
if space then
-- if the current tile is *not* a space, bring this down to the lowest space
if tile then
-- put the tile in the correct spot in the board and fix its grid positions
self.tiles[spaceY][x] = tile
tile.gridY = spaceY
-- set its prior position to nil
self.tiles[y][x] = nil
-- tween the Y position to 32 x its grid position
tweens[tile] = {
y = (tile.gridY - 1) * 32
}
-- set space back to 0, set Y to spaceY so we start back from here again
space = false
y = spaceY
spaceY = 0
end
elseif tile == nil then
space = true
if spaceY == 0 then
spaceY = y
end
end
y = y - 1
end
end
-- create replacement tiles at the top of the screen
for x = 1, 8 do
for y = 8, 1, -1 do
local tile = self.tiles[y][x]
-- if the tile is nil, we need to add a new one
if not tile then
local tile = Tile(x, y, math.random(18), math.random(1, self.level))
tile.y = -32
self.tiles[y][x] = tile
tweens[tile] = {
y = (tile.gridY - 1) * 32
}
end
end
end
return tweens
end
function Board:getNewTiles()
return {}
end
function Board:testForMatches()
for y = 1, 8 do
for x = 1, 8 do
-- Test for left swap
if x > 1 then
end
end
end
end
function Board:render()
for y = 1, #self.tiles do
for x = 1, #self.tiles[1] do
self.tiles[y][x]:render(self.x, self.y)
end
end
end
Here's my PlayState: (look for PlayState:swapTiles(), keep in mind that the self.board is being used several times for and works fine, except when i try calling self.board:swapTiles().)
PlayState = Class{__includes = BaseState}
function PlayState:init()
-- start our transition alpha at full, so we fade in
self.transitionAlpha = 255
-- position in the grid which we're highlighting
self.boardHighlightX = 0
self.boardHighlightY = 0
-- timer used to switch the highlight rect's color
self.rectHighlighted = false
-- flag to show whether we're able to process input (not swapping or clearing)
self.canInput = true
-- tile we're currently highlighting (preparing to swap)
self.highlightedTile = nil
self.score = 0
self.timer = 60
-- set our Timer class to turn cursor highlight on and off
Timer.every(0.5, function()
self.rectHighlighted = not self.rectHighlighted
end)
-- subtract 1 from timer every second
Timer.every(1, function()
self.timer = self.timer - 1
-- play warning sound on timer if we get low
if self.timer <= 5 then
gSounds['clock']:play()
end
end)
end
function PlayState:enter(params)
-- grab level # from the params we're passed
self.level = params.level
-- spawn a board and place it toward the right
self.board = params.board or Board(VIRTUAL_WIDTH - 272, 16)
-- grab score from params if it was passed
self.score = params.score or 0
-- score we have to reach to get to the next level
self.scoreGoal = self.level * 1.25 * 1000
end
function PlayState:update(dt)
if love.keyboard.wasPressed('escape') then
love.event.quit()
end
-- go back to start if time runs out
if self.timer <= 0 then
-- clear timers from prior PlayStates
Timer.clear()
gSounds['game-over']:play()
gStateMachine:change('game-over', {
score = self.score
})
end
-- go to next level if we surpass score goal
if self.score >= self.scoreGoal then
-- clear timers from prior PlayStates
-- always clear before you change state, else next state's timers
-- will also clear!
Timer.clear()
gSounds['next-level']:play()
-- change to begin game state with new level (incremented)
gStateMachine:change('begin-game', {
level = self.level + 1,
score = self.score
})
end
if self.canInput then
-- move cursor around based on bounds of grid, playing sounds
if love.keyboard.wasPressed('up') then
self.boardHighlightY = math.max(0, self.boardHighlightY - 1)
gSounds['select']:play()
elseif love.keyboard.wasPressed('down') then
self.boardHighlightY = math.min(7, self.boardHighlightY + 1)
gSounds['select']:play()
elseif love.keyboard.wasPressed('left') then
self.boardHighlightX = math.max(0, self.boardHighlightX - 1)
gSounds['select']:play()
elseif love.keyboard.wasPressed('right') then
self.boardHighlightX = math.min(7, self.boardHighlightX + 1)
gSounds['select']:play()
end
-- if we've pressed enter, to select or deselect a tile...
if love.keyboard.wasPressed('enter') or love.keyboard.wasPressed('return') then
-- if same tile as currently highlighted, deselect
local x = self.boardHighlightX + 1
local y = self.boardHighlightY + 1
-- if nothing is highlighted, highlight current tile
if not self.highlightedTile then
self.highlightedTile = self.board.tiles[y][x]
-- if we select the position already highlighted, remove highlight
elseif self.highlightedTile == self.board.tiles[y][x] then
self.highlightedTile = nil
-- if the difference between X and Y combined of this highlighted tile
-- vs the previous is not equal to 1, also remove highlight
elseif math.abs(self.highlightedTile.gridX - x) + math.abs(self.highlightedTile.gridY - y) > 1 then
gSounds['error']:play()
self.highlightedTile = nil
else
self:swapTiles(self.highlightedTile, self.board.tiles[y][x], true)
end
end
end
Timer.update(dt)
end
function PlayState:swapTiles(tile1, tile2, swapBackAtNoMatch)
local tile1 = tile1
local tile2 = tile2
local swapBackAtNoMatch = swapBackAtNoMatch
self.board:swapTiles(tile1, tile2) -- Causes the nil error.
if swapBackAtNoMatch then
-- tween coordinates between two swapping tiles
Timer.tween(0.1, {
[tile1] = {x = tile2.x, y = tile2.y},
[tile2] = {x = tile1.x, y = tile1.y}
})
-- once they've swapped, tween falling blocks
:finish(function ()
local matches = self.board:calculateMatches()
if matches then
self.calculateMatches(matches)
else
-- swap back if there's no match
self.swapTiles(tile1, tile2, false)
gSounds['error']:play()
end
end)
else
-- tween coordinates between the two so they swap
Timer.tween(0.1, {
[tile1] = {x = tile2.x, y = tile2.y},
[tile2] = {x = tile1.x, y = tile1.y}})
end
end
--[[
Calculates whether any matches were found on the board and tweens the needed
tiles to their new destinations if so. Also removes tiles from the board that
have matched and replaces them with new randomized tiles, deferring most of this
to the Board class.
]]
function PlayState:calculateMatches()
self.highlightedTile = nil
-- if we have any matches, remove them and tween the falling blocks that result
local matches = self.board:calculateMatches()
if matches then
gSounds['match']:stop()
gSounds['match']:play()
-- add score for each match
for k, match in pairs(matches) do
local varietyPoints = 0 -- We'll keep track of the bonus variety points here
-- We'll use vareity to calculate points for each tile within a match
for j, tiles in pairs(match) do
varietyPoints = varietyPoints + tiles.variety * 25
end
self.score = self.score + (#match * 50) + varietyPoints
-- Also add one second times the number of match to the timer
self.timer = self.timer + #match * 1
end
-- remove any tiles that matched from the board, making empty spaces
self.board:removeMatches()
-- gets a table with tween values for tiles that should now fall
local tilesToFall = self.board:getFallingTiles()
-- first, tween the falling tiles over 0.25s
Timer.tween(0.25, tilesToFall):finish(function()
local newTiles = self.board:getNewTiles()
-- then, tween new tiles that spawn from the ceiling over 0.25s to fill in
-- the new upper gaps that exist
Timer.tween(0.25, newTiles):finish(function()
-- recursively call function in case new matches have been created
-- as a result of falling blocks once new blocks have finished falling
self:calculateMatches()
end)
end)
-- if no matches, we can continue playing
else
self.canInput = true
end
end
Honestly doesn't make much sense. Please help!
function PlayState:swapTiles(tile1, tile2, swapBackAtNoMatch) end
is syntactic sugar for
PlayState.swaptiles = function(self, tile1, tile2, swapBackAtNoMatch) end
That's the reason why you can work with self inside that function.
A function defined like that needs to be called using the colon operator as well to make this work. Or you explicitly provide the table as first parameter.
Hence in your call self.swapTiles(tile1, tile2, false)
self is going to be tile1
tile1.board is nil so self.board is nil in this function call which causes the error
You have to call self:swapTiles(tile1, tile2, false) or self.swapTiles(self, tile1, tile2, false)
Please make sure you fully understand the colon syntax.

How to make love2d game compatible

I am getting the message "This game indicates it was made for version '0.9.1' of LOVE.
It may not be compatible with the running version (0.10.2)." when I try and run my game. The game still works but the message is annoying me. How do I update it to the latest version? My code is here:
debug = true
Main.lua
-- Timers
-- We declare these here so we don't have to edit them multiple places
canShoot = true
canShootTimerMax = 0.2
canShootTimer = canShootTimerMax
createEnemyTimerMax = 0.4
createEnemyTimer = createEnemyTimerMax
-- Player Object
player = { x = 200, y = 710, speed = 150, img = nil }
isAlive = true
score = 0
-- Image Storage
bulletImg = nil
enemyImg = nil
-- Entity Storage
bullets = {} -- array of current bullets being drawn and updated
enemies = {} -- array of current enemies on screen
-- Collision detection taken function from http://love2d.org/wiki/BoundingBox.lua
-- Returns true if two boxes overlap, false if they don't
-- x1,y1 are the left-top coords of the first box, while w1,h1 are its width and height
-- x2,y2,w2 & h2 are the same, but for the second box
function CheckCollision(x1,y1,w1,h1, x2,y2,w2,h2)
return x1 < x2+w2 and
x2 < x1+w1 and
y1 < y2+h2 and
y2 < y1+h1
end
-- Loading
function love.load(arg)
player.img = love.graphics.newImage('assets/plane.png')
enemyImg = love.graphics.newImage('assets/enemy.png')
bulletImg = love.graphics.newImage('assets/bullet.png')
end
-- Updating
function love.update(dt)
-- I always start with an easy way to exit the game
if love.keyboard.isDown('escape') then
love.event.push('quit')
end
-- Time out how far apart our shots can be.
canShootTimer = canShootTimer - (1 * dt)
if canShootTimer < 0 then
canShoot = true
end
-- Time out enemy creation
createEnemyTimer = createEnemyTimer - (1 * dt)
if createEnemyTimer < 0 then
createEnemyTimer = createEnemyTimerMax
-- Create an enemy
randomNumber = math.random(10, love.graphics.getWidth() - 10)
newEnemy = { x = randomNumber, y = -10, img = enemyImg }
table.insert(enemies, newEnemy)
end
-- update the positions of bullets
for i, bullet in ipairs(bullets) do
bullet.y = bullet.y - (250 * dt)
if bullet.y < 0 then -- remove bullets when they pass off the screen
table.remove(bullets, i)
end
end
-- update the positions of enemies
for i, enemy in ipairs(enemies) do
enemy.y = enemy.y + (200 * dt)
if enemy.y > 850 then -- remove enemies when they pass off the screen
table.remove(enemies, i)
end
end
-- run our collision detection
-- Since there will be fewer enemies on screen than bullets we'll loop them first
-- Also, we need to see if the enemies hit our player
for i, enemy in ipairs(enemies) do
for j, bullet in ipairs(bullets) do
if CheckCollision(enemy.x, enemy.y, enemy.img:getWidth(), enemy.img:getHeight(), bullet.x, bullet.y, bullet.img:getWidth(), bullet.img:getHeight()) then
table.remove(bullets, j)
table.remove(enemies, i)
score = score + 1
end
end
if CheckCollision(enemy.x, enemy.y, enemy.img:getWidth(), enemy.img:getHeight(), player.x, player.y, player.img:getWidth(), player.img:getHeight())
and isAlive then
table.remove(enemies, i)
isAlive = false
end
end
if love.keyboard.isDown('left','a') then
if player.x > 0 then -- binds us to the map
player.x = player.x - (player.speed*dt)
end
elseif love.keyboard.isDown('right','d') then
if player.x < (love.graphics.getWidth() - player.img:getWidth()) then
player.x = player.x + (player.speed*dt)
end
end
if love.keyboard.isDown(' ', 'rctrl', 'lctrl', 'ctrl') and canShoot then
-- Create some bullets
newBullet = { x = player.x + (player.img:getWidth()/2), y = player.y, img = bulletImg }
table.insert(bullets, newBullet)
canShoot = false
canShootTimer = canShootTimerMax
end
if not isAlive and love.keyboard.isDown('r') then
-- remove all our bullets and enemies from screen
bullets = {}
enemies = {}
-- reset timers
canShootTimer = canShootTimerMax
createEnemyTimer = createEnemyTimerMax
-- move player back to default position
player.x = 50
player.y = 710
-- reset our game state
score = 0
isAlive = true
end
end
-- Drawing
function love.draw(dt)
for i, bullet in ipairs(bullets) do
love.graphics.draw(bullet.img, bullet.x, bullet.y)
end
for i, enemy in ipairs(enemies) do
love.graphics.draw(enemy.img, enemy.x, enemy.y)
end
love.graphics.setColor(255, 255, 255)
love.graphics.print("SCORE: " .. tostring(score), 400, 10)
if isAlive then
love.graphics.draw(player.img, player.x, player.y)
else
love.graphics.print("Press 'R' to restart", love.graphics:getWidth()/2-50, love.graphics:getHeight()/2-10)
end
if debug then
fps = tostring(love.timer.getFPS())
love.graphics.print("Current FPS: "..fps, 9, 10)
end
end
conf.lua
-- Configuration
function love.conf(t)
t.title = "Scrolling Shooter Tutorial" -- The title of the window the game is in (string)
t.version = "0.9.1" -- The LÖVE version this game was made for (string)
t.window.width = 480 -- we want our game to be long and thin.
t.window.height = 800
-- For Windows debugging
t.console = true
end
As noted in the comments, the problem in your case was that, in conf.lua, the version was specified as "0.9.1". In some cases, changing this value to "0.10.2" is sufficient, but a significant amount of changes occurred between 0.9.0 and 0.10.0.
Be especially aware that mouse input is definitely going to be broken because, in versions before 0.10.0, LOVE used strings to represent mouse buttons, whereas in 0.10.0 and beyond, numbers are used. To fix this, look for mouse-related functions (love.mouse.isDown, love.mousepressed, etc.) and change "l" to 1, "r" to 2, and so on. See the full list of old values and love.mousepressed for more. Additionally, mousewheel movement changed as well, with the addition of the love.wheelmoved callback and removing the strings passed to love.mousepressed.
Additionally, read through the changelog for any changes that may have affected your program.

Game Dev - Having trouble with AABB collision detection during jumps

My collision detection works by getting intersection between rectangles and reversing the effect. This is happening during each frame. It works great except in the case where the player is sitting on top of a corner and jumps. every once in a while the vertical intersection is greater than the horizontal intersection which makes my player slide down the side of the platform. Any suggestions?
-- detect initial collision
if mathy.hasCollided(player, platform) then
local playerBoundaries = player:boundaries()
-- list of intersections between platform and player
local bottomBoundary = mathy.bottomBoundary( playerBoundaries, platform )
local topBoundary = mathy.topBoundary( playerBoundaries, platform )
local rightBoundary = mathy.rightBoundary( playerBoundaries, platform )
local leftBoundary = mathy.leftBoundary( playerBoundaries, platform )
local smallestDist = ""
local smallestBoundary
local boundaries = {
bottom = bottomBoundary,
top = topBoundary,
right = rightBoundary,
left = leftBoundary
}
-- get the smallest intersection (thats the side we're probably closest to)
for direction, boundary in pairs(boundaries) do
if not smallestBoundary then
smallestBoundary = boundary
smallestDist = direction
end
if smallestBoundary > boundary then
smallestBoundary = boundary
smallestDist = direction
end
end
-- reverse effects depending on collision location
if smallestDist == "bottom" then
player.desiredPos:add(diffX, -bottomBoundary)
player.velocity.y = 0
player.onGround = true
elseif smallestDist == "top" then
player.velocity.y = 250
player.desiredPos:add(0, topBoundary)
elseif smallestDist == "right" then
player.desiredPos:add(-rightBoundary, 0)
elseif smallestDist == "left" then
player.desiredPos:add(leftBoundary, 0)
end
end
Its hard to tell from the short clip, but I think the issue is a result of checking for the smallest intersection instead of checking for the intersection in the direction of the object's velocity. You could try something like this in place of the smallestBoundary loop:
local boundary = nil
if (player.velocity.y > 0) then
boundary = topBoundary
elseif (player.velocity.y < 0) then
boundary = bottomBoundary
elseif (player.velocity.x > 0) then
boundary = rightBoundary
elseif (player.velocity.x < 0) then
boundary = leftBoundary
end
Of course, this isn't as robust as it can be. You might also try to combine the two approaches and do something like this in place of the smallestBoundary loop:
local yMod = math.abs(player.velocity.y)
local xMod = math.abs(player.velocity.x)
local topMod = player.velocity.y > 0 and yMod or 1
local bottomMod = player.velocity.y < 0 and yMod or 1
local rightMod = player.velocity.x > 0 and xMod or 1
local leftMod = player.velocity.x < 0 and xMod or 1
local boundaries = {
bottom = (bottomMod / MAX_VELOCITY) * bottomBoundary,
top = (topMod / MAX_VELOCITY) * topBoundary,
right = (rightMod / MAX_VELOCITY) * rightBoundary,
left = (leftMod / MAX_VELOCITY) * leftBoundary
}
for direction, boundary in pairs(boundaries) do
if not smallestBoundary then
smallestBoundary = boundary
smallestDist = direction
end
if smallestBoundary > boundary then
smallestBoundary = boundary
smallestDist = direction
end
end
This will do exactly what you're doing now, but will adjust the size of the boundary (in context of the comparison only) by the velocity of the player in that direction. So if you're moving in x with -5 and in y with -10, downward collisions in the y plane will have twice the weight of leftward collisions in the x plane. Of course, there's always the other option of adjusting the player in every plane of collision:
if bottomBoundary > 0 then
player.desiredPos:add(diffX, -bottomBoundary)
player.velocity.y = 0
player.onGround = true
end
if topBoundary > 0 then
player.velocity.y = 250
player.desiredPos:add(0, topBoundary)
end
if rightBoundary > 0 then
player.desiredPos:add(-rightBoundary, 0)
end
if leftBoundary > 0 then
player.desiredPos:add(leftBoundary, 0)
end
This last method would make the most sense, except you don't seem to handle collisions in all directions uniformly, for whatever reason, so it might not fit your architecture.
Keep in mind that I'm not familiar with the framework you're using, so this code might not work out of the box. Also, this post assumes that +y is up, -y is down, +x is right, and -x is left.

How would I get my script to reset its score upon button press?

I need some help understanding how would I reset the score of numMiss, numHit, and numPercent back to 0 as soon as I tap the "reset.png" button and while doing so will also start the game from the beginning again.
Also, let me know if there are any corrections to be made within my code.
Heres what I have of the code so far
--width and height
WIDTH = display.contentWidth --320
HEIGHT = display.contentHeight --480
--display background
local p = display.newImageRect("park.png" ,500, 570)
p.x = WIDTH/2
p.y = HEIGHT/2
--display bouncing dog
local RADIUS = 5
local d = display.newImageRect("dogeball.png", 70, 70)
d.x = 50
d.y = 100
--display treat
local t = display.newImageRect("treat.png", 50, 50)
t.x = 245
t.y = math.random(HEIGHT)
--displays the reset button
local r = display.newImageRect("reset.png", 100,100)
r.x = 280
r.y = 480
--starting value of gravity and bounce(will change)
local GRAVITY = 0.3
local BOUNCE = 0.75
--downward force
local velocity = 0
--Tells the score to reset when true
local reset = false
--shows number of hits
local numHit = 0
--shows number of misses
local numMiss = 0
--Gets Percentage score
local numPercent = 0
--make hits and misses display
scoreHits = display.newText("Hits = " .. numHit, WIDTH/7, 1, native.systemFont, 18)
scoreMisses = display.newText("Misses = " .. numMiss, WIDTH/2.1, 1, native.systemFont, 18)
scorePercent = display.newText("Hit % = " .. numPercent, WIDTH/1.2, 1, native.systemFont, 18)
function enterFrame()
d.y = d.y + velocity
velocity = velocity + GRAVITY
local HIT_SLOP = RADIUS * 8 -- Adjust this to adjust game difficulty
if math.abs(t.x - d.x) <= HIT_SLOP
and math.abs(t.y - d.y) <= HIT_SLOP then
numHit = numHit + 1
scoreHits.text = "Hits = " .. numHit
--count 1 hit once dog and treat hit eachother
if (t.x - d.x) <= HIT_SLOP and (t.y - d.y) <= HIT_SLOP then
t.x = 400 --resets treat postioning
t.y = math.random(HEIGHT) --gives treat a random y coordinate
end
end
--puts the barrier at the bottom of the screen and tells dog to bounce from there
if (d.y > HEIGHT) then
d.y = HEIGHT
velocity = -velocity * BOUNCE
end
t.x = t.x - 5 --speed treat goes
if t.x < -350 then--position of the treat
t.x = 400
scoreMisses.text = "Misses = " .. numMiss
else if t.x < -100 then
t.y = math.random(HEIGHT) --random height after treat goes past dog
else if t.x < -99 then
numMiss = numMiss + 1 --calculates misses when goes past screen
scoreMisses.text = "Misses = " .. numMiss
end
end
end
--calculate percentage hits
numPercent = 100 * numHit / (numHit + numMiss)
scorePercent.text = "Hit % = " .. math.round(numPercent) --prints and rounds percentage
function tapped(event) --when tapped on reset, score gets reset
--reset function goes here
end
end
r:addEventListener( "tap", tapped )
end
function touched(event)
-- print(event.phase)
if event.phase == "began" then
velocity = velocity - 6 -- thrusts dog
end
return true
end
Runtime:addEventListener( "enterFrame" , enterFrame )
Runtime:addEventListener( "touch", touched )
To make your image a button you need to add an event listener that responds to touch or tap events.
see http://docs.coronalabs.com/api/event/touch/index.html
Or you use the widget library which gives you the possibility to use a blank button background and set only the label for each button what will be very handy when you include translations for other languages.
see http://docs.coronalabs.com/api/library/widget/newButton.html
In my game I have a function gameInit() that sets the hole game and all variables. This function is called when the game starts and also when the player wants to do a reset as it over writes the old variables. (there are other technics, depending on the complexity of your game and if you for example want to store the game settings for the next time the player starts the game)

Resources