Attempt to index field (a nil value), for an Object - lua
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.
Related
How do I keep the renderBlocks() function from going into an infinite loop?
-- This is Minecraft, but 2D, and in Lua. -- ************************************** -- ID 0: Air -- ID 1: Stone -- ID 2 Logs -- ID 3: Leaves -- ID 4: Planks -- ID 5: Crafting Table -- ID 6: Furnance -- ID 7: Player position detector block -- ************************************** blockTable = { -- Stores all the blocks {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6} } tableRow = 1 -- Y value for block render pointer tableColumn = 1 -- X value for block render pointer runOnce = true -- Run a loop once tickNum = 0 -- Number of ticks since startup function renderBlock(X, Y, K) -- Render a block if K == 1 then -- if blockID == 1, draw a grey square screen.setColor(100,100,100) print(X, Y) screen.drawRectF(X, Y, 8, 8) end if K == 6 then -- If the blockID == draw a grey square screen.setColor(100,100,100) screen.drawRectF(X, Y, 8, 8) end end function renderBlocks() -- Render all the blocks (Scans though the blockTable in a raster pattern) while runOnce == true -- Run this code once do while #blockTable >= tableRow do while #blockTable[tableRow] >= tableColumn do print("TC " .. tableColumn) tableColumn = tableColumn + 1 blockID = blockTable[tableRow][tableColumn] renderBlock(tableRow, tableColumn, blockID) if tableColumn > #blockTable[tableRow] then tableColumn = 1 end end runOnce = false end print("TR " .. tableRow) tableRow = tableRow + 1 end end function onTick() print("Tick! Tick count: " .. tickNum) -- Print the current tick, as well as say that there has been a tick tickNum = tickNum + 1 end function onDraw() while runOnce == true -- Run this code once do renderBlocks() runOnce = false end end Here's my code in the IDE for the framework: Link I have no clue why this is happening, I'm at a loss here.
When doing loops over tables it is far better to use for loops than while or repeat loops, that are far more difficult to understand and bugfix. Use for index,value in ipairs(yourtable) do if you want to get the indices (number in the table) and the values associated or for key,value in pairs(yourtable) do if you want to get the keys (generally strings that is used as key for a value: yourTable["key"] = 0) and the values associated with it. for row,innerTable in ipairs(blockTable) do for col,value in ipairs(innerTable) do print("TC " .. col) blockID = value renderBlock(row, col, blockID) end end The last should do it inside your renderBlocks function. But that: while runOnce == true do -- Doing something runOnce = false end Is not very nice stuff. If it shall only be called once, do not put it into a loop! Also it would help to have a little better code identation to understand what is happening faster. And add some Space between blocks of code that handle different concerns. Edit: So your function could look like this: function renderBlocks() if runOnce then runOnce = false for row,innerTable in ipairs(blockTable) do for col,value in ipairs(innerTable) do blockID = value renderBlock(row, col, blockID) end end end end
There are a couple issues in your renderBlocks() function function renderBlocks() while runOnce == true do while #blockTable >= tableRow do while #blockTable[tableRow] >= tableColumn do print("TC " .. tableColumn) tableColumn = tableColumn + 1 blockID = blockTable[tableRow][tableColumn] renderBlock(tableRow, tableColumn, blockID) if tableColumn > #blockTable[tableRow] then tableColumn = 1 end end runOnce = false end print("TR " .. tableRow) tableRow = tableRow + 1 end end First we can look at the inner most loop. Here we have a problem with the contrition used to evaluate the loop and how it is incremented. while #blockTable[tableRow] >= tableColumn do print("TC " .. tableColumn) tableColumn = tableColumn + 1 -- incremented here blockID = blockTable[tableRow][tableColumn] renderBlock(tableRow, tableColumn, blockID) if tableColumn > #blockTable[tableRow] then tableColumn = 1 -- reset here end end in the loop body you do tableColumn = tableColumn + 1 and later in the same body you do if tableColumn > #blockTable[tableRow] then tableColumn = 1 end so this is a problem when tableColumn is increased to a value that would end the loop we reset it to 1 preventing the loop from ever ending. Now lets look at the next loop up. Here we never increment tableRow it is incremented just after the end of this loop so it is not possible for the loop to end. while #blockTable >= tableRow do while #blockTable[tableRow] >= tableColumn do ... end runOnce = false end I believe you intended print("TR " .. tableRow) tableRow = tableRow + 1 to occur within this loop not outside of it.
You're missing a few block definitions here. Don't think that's what's sending it into an infinite loop, but you might not see results, otherwise. function renderBlock(X, Y, blockID) -- Render a block if blockID == 0 then -- Air -- screen.setColor(10, 20, 220, 0) -- is there an alpha channel? screen.setColor(10, 20, 220) elseif blockID == 1 then -- Stone screen.setColor(100, 100, 100) elseif blockID == 2 then -- Logs screen.setColor(200, 50, 50) elseif blockID == 3 then -- Leaves screen.setColor(20, 220, 40) elseif blockID == 4 then -- Planks screen.setColor(180, 30, 10) elseif blockID == 5 then -- Craft Table screen.setColor(180, 30, 10) elseif blockID == 6 then -- Furnace screen.setColor(100, 100, 100) elseif blockID == 7 then -- Player detect screen.setColor(220, 30, 30) end print(X, Y) screen.drawRectF(X, Y, 8, 8) end It's easy to use for loops. set initial condition, then the final, completed condition, and let it run the course. function renderBlocks() -- Render all blocks (Scans though blockTable in a raster pattern) for row = 1, #blockTable do for col = 1, #blockTable[row] do print("TC " ..col) local blockID = blockTable[row][col] renderBlock(row, col, blockID) end print("TR " ..row) end end You likely don't need that runOnce. If you find you do for something else, go ahead, but it shouldn't be needed with a nested for loop. function onDraw() renderBlocks() end
LUA Scripting in Tabletop Simulator
Not sure if this is the place to post this kind of question, I think someone needs to be familiar with the TableTop Simulator game to help, but I figured I'd try. I am trying to create a script that uses Script Boxes to check what the description of a card is in the location, and once a new card is drawn to one of the 4 Script Boxes, if that description equals 'Fast', the card gets moved to the first Box and all the previous cards move over 1 box. However every time the 4th card is Fast it moves reads the 4th card twice and moves it to the first Box and then back to the 4th box, instead of moving the 3rd card to the 4th box. I can't figure out why it's getting the 4th card twice. fc = the amount of cards in place + the one being added d = nil fz = 0 function onLoad() DeckZone = {getObjectFromGUID('2d5588'), getObjectFromGUID('bf2a55'), getObjectFromGUID('5bdb70'), getObjectFromGUID('d01463')} -- guid of script boxes with decks in them CombatZone = {getObjectFromGUID('2d2792'), getObjectFromGUID('74aafc'), getObjectFromGUID('68ebc9'), getObjectFromGUID('30818f')} -- the guids of the boxes Combat = {CombatZone[1].getPosition(),CombatZone[2].getPosition(),CombatZone[3].getPosition(),CombatZone[4].getPosition()} -- the Positions of the boxes -- Buttons correlating to the decks being pulled from -- DeckZone[3].createButton({ click_function="click_drawCombat", function_owner=self, alignment=3, position={0, -0.45, 0}, height=450, width=450, font_size=1000, color = {1,1,1,1}, tooltip = "Click to Draw Combat Card." }) DeckZone[4].createButton({ click_function="click_drawCombat", function_owner=self, alignment=3, position={0, -0.45, 0}, height=450, width=450, font_size=1000, color = {1,1,1,1}, tooltip = "Click to Draw Combat Card." }) end function click_drawCombat(obj) local deck = obj.getGUID() if deck == "5bdb70" then d = 3 elseif deck == "d01463" then d = 4 end findDeck() if #objects > 0 then if type == 'footsoldier' then if fz < 4 then fz = fz + 1 table.insert(FSDeckZoneSaved, obj) local faceup = {} faceup.position = Combat[fz] faceup.flip = true local pos = Combat[fz] for _, obj in ipairs(objects) do globalDeck.takeObject(faceup) Wait.time(function() fastCard(fz) end, 0.3, 1) end end end end end function findDeck() globalDeck = nil objects = DeckZone[d].getObjects() for i, deck in ipairs(objects) do globalDeck = getObjectFromGUID(deck.getGUID()) end end function findCombat() globalDeck = nil objects = CombatZone[cz].getObjects() for i, deck in ipairs(objects) do globalDeck = getObjectFromGUID(deck.getGUID()) end end --part that not's working below-- function fastCard(fc) cz = fc fC = fc findCombat() fast = globalDeck.getDescription() fastCardTest(fc) end function fastCardTest(fc) if fast ~= 'Fast' or fc == 1 or fc == 5 then Wait.time(combatDeckButtons, 1.5, 1) end if fast == 'Fast' and fc > 1 and fc ~= 5 then for t=1,cz do if cz == fc then findCombat() guid = globalDeck.getGUID() pos = Combat[1] globalDeck.setPositionSmooth(pos, false, false) end if cz ~= fc then findCombat() pos = Combat[cz+1] globalDeck.setPositionSmooth(pos, false, false) end cz = cz - 1 end Wait.time(combatDeckButtons, 1.5, 1) end end
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.
How do I drag and drop specific objects in Love2d?
I'm new to Lua and coding in general so I decided to write a Chess Program to learn. I have setup a class and created objects from it to represent the pieces. Now I want to begin moving the pieces with my mouse. I looked at a tutorial, but it only handled one rectangle. My first though was to use a "for" loop in the love.mousePressed() function to go though each of the objects until it found an object with a matching x, y coordinate. This obviously did not work the way I did it. Instead, it only goes to the next object every time the mouse is pressed or at least it would if the program didn't immediately crash once the button was released. So my question is, what is the right way to be going about this? local blackPawn = love.graphics.newImage("Textures/Blackpawn.png") local blackRook = love.graphics.newImage("Textures/Blackrook.png") local blackKnight = love.graphics.newImage("Textures/Blackknight.png") local blackBishop = love.graphics.newImage("Textures/Blackbishop.png") local blackQueen = love.graphics.newImage("Textures/Blackqueen.png") local blackKing = love.graphics.newImage("Textures/BlackKing.png") local whitePawn = love.graphics.newImage("Textures/Whitepawn.png") local whiteRook = love.graphics.newImage("Textures/Whiterook.png") local whiteKnight = love.graphics.newImage("Textures/Whiteknight.png") local whiteBishop = love.graphics.newImage("Textures/Whitebishop.png") local whiteQueen = love.graphics.newImage("Textures/Whitequeen.png") local whiteKing = love.graphics.newImage("Textures/WhiteKing.png") local chessboard = love.graphics.newImage("Textures/ChessBoard.png") local register = {} local id = 0 piece = { xSquare = 0, ySquare = 0, x = 0, y = 0, height = 64, width = 64, pawn = false, Rook = false, Knight = false, Bishop = false, Queen = false, King = false, color = "", texture = whitePawn, dragging = {active = false, diffx = 0, diffy = 0} } function piece.new() newPiece = {} for k, v in pairs(piece) do newPiece[k] = v end return newPiece end function piece:draw() end function getMouse() local x, y = love.mouse.getPosition() local isDown = love.mouse.isDown(1,2) return x, y, isDown end function createBoard(id) for x = 1, 8 do for y = 1, 8 do if y ~= 3 and y ~= 4 and y ~=5 and y ~= 6 then id = id + 1 register[id] = piece.new() register[id].x = x * 64 - 48 register[id].y = (y - 1) * 64 if y == 2 then register[id].pawn = true register[id].color = "white" register[id].texture = whitePawn print("item " .. id .. " is here x = " .. register[id].x .. " y = " .. register[id].y .. " Is pawn = " .. tostring(register[id].pawn) .. " Color is " .. register[id].color) elseif y == 7 then register[id].pawn = true register[id].color = "black" register[id].texture = blackPawn print("item " .. id .. " is here x = " .. register[id].x .. " y = " .. register[id].y .. " Is pawn = " .. tostring(register[id].pawn) .. " Color is " .. register[id].color) elseif y == 1 then register[id].color = "white" if x == 1 or x == 8 then register[id].Rook = true register[id].texture = whiteRook elseif x == 2 or x == 7 then register[id].Knight = true register[id].texture = whiteKnight print("knight is here") elseif x == 3 or x == 6 then register[id].Bishop = true register[id].texture = whiteBishop elseif x == 5 then register[id].King = true register[id].texture = whiteKing elseif x == 4 then register[id].Queen = true register[id].texture = whiteQueen end elseif y == 8 then register[id].color = "black" if x == 1 or x == 8 then register[id].Rook = true register[id].texture = blackRook elseif x == 2 or x == 7 then register[id].Knight = true register[id].texture = blackKnight elseif x == 3 or x == 6 then register[id].Bishop = true register[id].texture = blackBishop elseif x == 5 then register[id].King = true register[id].texture = blackKing elseif x == 4 then register[id].Queen = true register[id].texture = blackQueen end end end end end end function drawBoard(id, register) love.graphics.draw(chessboard, 0, 0) for id = 1, 32 do love.graphics.draw(register[id].texture, register[id].x, register[id].y) end end function love.load() createBoard(id) end function love.update(dt) for id = 1, 32 do if register[id].dragging.active == true then register[id].x = love.mouse.getX() - register[id].dragging.diffx register[id].y = love.mouse.getY() - register[id].dragging.diffy end end end function love.draw() drawBoard(id, register) end function love.mousepressed(x, y, button) for id = 1, 32 do if (button == 1 or button == 2) and x > register[id].x and x < register[id].x + register[id].width and y > register[id].y and y < register[id].y + register[id].height then register[id].dragging.active = true register[id].dragging.diffx = x - register[id].x register[id].dragging.diffy = y - register[id].y end end end function love.mousereleased(x, y, button) for id = 1, 32 do if button == 1 or button == 2 then register[id].dragging.active = false end end end function love.keypressed(key, unicode) end function love.keyreleased(key) end function love.focus(bool) end function love.quit() end Update: I fixed the crashing, but I still have the weird bug where it changes the dragged piece into a different piece Update 2: After a little more debugging I have figured out that the major issue is that it for some reason does not correctly check if the dragging is active. As the code stands right now I need an else dragging.active = false after to correctly set it, but now that it is correctly set it won't drag anything at all despite the correct object have dragging set to active (unless I try and drag the object with value 32 where it drags everything at once). I am very confused as to what's wrong. Why isn't Lua able to check value like this?
First, I'd create a global boolean for if a piece has been selected and then a variable to hold the piece selected local selected = false local selectedPiece = {} Then create a playing board and split it into a grid, with each square being of equal size. Something like this board = { size = { 8, 8 }, -- 8x8 grid squareSize = 40, -- 40 pixels long sides pieces = { { -- First row contains which pieces? Piece:Rook(), Piece:Bishop(), ... }, { -- Second row Piece:Pawn(), ... }, { -- etc. Piece:Empty(), ... } } } I advise not using nil in your table for empty squares due to the odd behavior of tables with nil indexes. In your love.mousepressed() method, you check where the click was based on its position (this is assuming the board takes up the whole window) function love.mousepressed(x, y, btn) -- If a piece hasn't been clicked on. if (not selected) then -- This line is assuming that since all board squares are equal size, then the mouse click has to be in at least one square. -- Therefore, if we take the floor of the position/board.squareSize, we will always get a value from 0 - 7 (8 values) on the board. local piece = board.pieces[math.floor(x/board.squareSize)][math.floor(y/board.squareSize)] -- If there is a piece here. if (piece:isNotAnEmpty()) then selectedPiece = piece -- Select the piece. selected = not selected -- Notify program that a piece is selected to handle such things accordingly in other methods. end else -- Assuming you wrote a method that determines if a piece can be moved to a certain spot on the board. if (board:CanMovePieceHere(selectedPiece, x/board.size, y/board.size)) then -- Do your stuff here. ... -- Eventually, reset your variables. selected = not selected selectedPiece = {} end end end This is how I'd approach it, but your question is very open to interpretation.
My Lua maze builder that is braiding
I am building a Lua script that generates a maze using a version of the Recursive Backtracker implemented with a stack rather than recursion. Presently the maze is coming out braided and I can't seem to figure out where in my logic this is happening. The function below takes in x and y as a starting point for generating the maze which is a 2d structure (table of tables): local function buildMazeInternal(x,y,maze) local stack = {} local directions = {'North','East','South','West'} table.insert(stack,{x=x,y=y}) while #stack > 0 do local index = 1 local nextX = x local nextY = y local braid = false for i = #directions, 2, -1 do -- backwards local r = calc:Roll(1,i) -- select a random number between 1 and i directions[i], directions[r] = directions[r], directions[i] -- swap the randomly selected item to position i end while index <= #directions and nextX == x and nextY == y do if directions[index] == 'North' and y > 1 and not maze[y-1][x].Visited then maze[y][x].North = true maze[y-1][x].South = true nextY = y-1 elseif directions[index] == 'East' and x < width and not maze[y][x+1].Visited then maze[y][x].East = true maze[y][x+1].West = true nextX = x+1 elseif directions[index] == 'South' and y < height and not maze[y+1][x].Visited then maze[y][x].South = true maze[y+1][x].North = true nextY = y+1 elseif directions[index] == 'West' and x > 1 and not maze[y][x-1].Visited then maze[y][x].West = true maze[y][x-1].East = true nextX = x-1 else index = index + 1 end end if nextX ~= x or nextY ~= y then x = nextX y = nextY maze[y][x].Visited = true table.insert(stack,{x=x,y=y}) else x = stack[#stack].x y = stack[#stack].y table.remove(stack) end end end I know I'm overlooking something but I can't seem to nail it down. Note that the calc:Roll(1,100) method is a .net method in my app used to simulate rolling dice, in this case 1 * 100 sided die, it could be replaced with a call to math.Random(1,100) for use outside of my application.
I see at least one problem. When you go "want to go up", you check whether the "cell which is up" was visited, and if it is, you "skip" going up. This does not seem to be correct IMHO. If you want to go up, but the cell which is "up" from the current cell was visited but has a "down" exit, you should still be able to go up (instead of skipping because it is visited). The same applies to the other directions. That's all I got.
I found the answer after posting on Reddit. I wasn't setting the initial cell to visited allowing it to be passed through twice. The correction was to add maze[y][x].Visited = true immediately before table.insert(stack,{x=x,y=y}).