applyLinearImpulse error on player.lua

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
User avatar
linux_facil
Prole
Posts: 3
Joined: Tue Jul 12, 2022 8:06 pm
Location: Omaha, NE. USA.
Contact:

applyLinearImpulse error on player.lua

Post by linux_facil »

Hi,
I'm starting to learn love2d and I'd love (pun intended) it if somebody can help me to understand this.

I'm using windfield from a327ex. (https://github.com/a327ex/windfield)

If I create only one file named main.lua containing all logic, everything goes well, the problem is if I want to create one file for all "Player" definitions, if I do this, I get an error when I press space for "jumping". the error is:
Error: main.lua:28: attempt to call method 'applyLinearImpulse' (a nil value)
stack traceback:
[love "boot.lua"]:345: in function 'applyLinearImpulse'
main.lua:28: in function <main.lua:25>
[love "callbacks.lua"]:154: in function <[love "callbacks.lua"]:144>
[C]: in function 'xpcall'
main.lua

Code: Select all

WF = require("windfield")
Player = require("player")
function love.load()
	World = WF.newWorld(0, 0, true)
	World:setGravity(0, 512)
	World:addCollisionClass("Player")
	World:addCollisionClass("Ground")

	Player:load()

	Ground = World:newRectangleCollider(0, 500, 800, 100)
	Ground:setType("static")
	Ground:setCollisionClass("Ground")
end

function love.update(dt)
	World:update(dt)
end

function love.draw()
	-- for debugging purposes you can draw the World
	World:draw()
end

function love.keypressed(key)
	Player:keypressed(key)
end
player.lua

Code: Select all

Player = {}

function Player:load()
	self = World:newRectangleCollider(100, 100, 20, 50)
	self:setCollisionClass("Player")
end

function Player:update(dt) end

function Player:draw() end

function Player:keypressed(key)
	if key == "space" then
		self:applyLinearImpulse(0, -500)
	end
end

return Player
As you can see is very simple, the "Player" and "Ground" are perfectly drawn, the problem is when pressing "Space"

Thank you!
“We suffer more often in imagination than in reality” -Seneca.
User avatar
ReFreezed
Party member
Posts: 612
Joined: Sun Oct 25, 2015 11:32 pm
Location: Sweden
Contact:

Re: applyLinearImpulse error on player.lua

Post by ReFreezed »

Hi! Welcome to the forums.

There are several fundamental problems here. First of all...

Code: Select all

-- This...
function Player:load()
-- Means this...
function Player.load(self)
Because you use the colon syntax (instead of a dot) 'self' is a hidden first argument, which means it's a local variable in the function. So in the actual function...

Code: Select all

function Player:load()
	self = World:newRectangleCollider(100, 100, 20, 50) -- You're assigning the value returned from World:newRectangleCollider() to the local variable 'self'.
	self:setCollisionClass("Player")
	-- After the function finishes executing you won't have access to the value that 'self' holds anymore.
end
Furthermore, these kind of calls using the colon syntax...

Code: Select all

Player:load(key)
-- Mean this...
Player.load(Player)
Which means 'Player' and 'self' in the Player:load() function refers to the same value (until you assign a new value to the 'self' variable, as mentioned above). The global variable 'Player' never change value though. (See the next problem further below.)

The same happens when you call Player:keypressed(key). The 'self' argument/variable in the Player:keypressed() function refers to the 'Player' object, but you don't define:

Code: Select all

function Player:applyLinearImpulse()
	-- ...
end
...anywhere, which leads to the error you're getting (trying to call a nil value, because self.applyLinearImpulse/Player.applyLinearImpulse is nil). You probably meant to call applyLinearImpulse on the value returned from World:newRectangleCollider() above, but that value is lost.

One solution is to assign the value to a field in the 'Player' object.

Code: Select all

function Player:load()
	self.collider = World:newRectangleCollider(100, 100, 20, 50) -- Add the field 'collider' to self/Player.
	self.collider:setCollisionClass("Player")
end

function Player:keypressed(key)
	if key == "space" then
		self.collider:applyLinearImpulse(0, -500)
	end
end
The next problem is the usage of globals. Note that the first lines in both files assign values to the global variables 'WF' and 'Player' (the latter gets assigned twice), and in the love.load() function you assign values to the globals 'World' and 'Ground'. You should declare local variables using the 'local' keyword unless you actually intend to use globals. This is important to learn, especially in Lua because all variables are globals by default (unfortunately).

Another potential problem is that 'Player' looks like a class, but there's no instantiation of the class, and 'Player' itself is used as the one and only instance. If you only intend to have a single object representing the player this will probably work ok, but you might wanna look up how the concept of classes works in Lua (though it's a bit more advanced topic if you're a beginner).
Tools: Hot Particles, LuaPreprocess, InputField, (more) Games: Momento Temporis
"If each mistake being made is a new one, then progress is being made."
User avatar
pgimeno
Party member
Posts: 3640
Joined: Sun Oct 18, 2015 2:58 pm

Re: applyLinearImpulse error on player.lua

Post by pgimeno »

Ninja'd by ReFreezed, but since I already wrote it... Also, I think the bit about Player:load() is a bit clearer here.

You seem to have a confusion between class and instance. You also seem to have a confusion about what 'self' means. There also seems to be some confusion between globals and locals.

Let's go in that order.

Player should be a class. This means that it should act as a type, providing a constructor to create instances, which are the variables of that type. However, in main.lua you're using it as if the class was an object. Read a bit about OOP in Lua to try to understand the difference between class and instance and how to make them work. You're not creating a new instance in Player:load().

Then, self. self is an implicit parameter of the function. Normally you don't assign a different value to it. Certainly not in the way you're doing it. Within Player:load(), 'self' is a parameter, and you're changing that parameter so that it has a different value, and when the function ends, the new value is lost forever just as if you did this:

Code: Select all

function setvalue(a)
  a = 65
end

setvalue(5) -- This does nothing, because 'a' is lost when the function ends
If you change the parameter inside the function, the new value you set won't be visible outside the function, only until the end of the function.

You probably want the player's collider be a part of the player, not to be the player itself. So you need to set a field of the player instance to that value, not the whole instance.

Finally, globals vs. locals. You're using globals everywhere. That's bad because using the same name for different things in different files can lead to misbehaviours (bugs) that are hard to identify. In player.lua you're making Player a global. Later on you redefine the Player global, luckily to the same value, so in the end everything works out, but that's not how you do things. In general, modules should rarely need to define any globals.

Putting all together, this is how you are supposed to do it. I don't know Windfield so I might have made a mistake.

It's customary to start with a capital letter for classes, and with a small letter for instances, to make them easier to distinguish, so I'll follow that.

main.lua

Code: Select all

local WF = require("windfield")
local Player = require("player") -- the class
-- instances local to main.lua
local player
local world
local ground

function love.load()
	world = WF.newWorld(0, 0, true)
	world:setGravity(0, 512)
	world:addCollisionClass("Player")
	world:addCollisionClass("Ground")

	player = Player:new(world) -- call the constructor

	ground = world:newRectangleCollider(0, 500, 800, 100)
	ground:setType("static")
	ground:setCollisionClass("Ground")
end

function love.update(dt)
	world:update(dt)
end

function love.draw()
	-- for debugging purposes you can draw the World
	world:draw()
end

function love.keypressed(key)
	player:keypressed(key)
end
player.lua

Code: Select all

local Player = {}
Player.__index = Player

function Player:new(world)
	-- as an exception, 'self' is the class here, not an instance
	local obj = setmetatable({}, self)
	obj.collider = world:newRectangleCollider(100, 100, 20, 50)
	obj.collider:setCollisionClass("Player")
	return obj
end

function Player:update(dt) end

function Player:draw() end

function Player:keypressed(key)
	if key == "space" then
		self.collider:applyLinearImpulse(0, -500)
	end
end

return Player
(Edited to fix a few mistakes)
Last edited by pgimeno on Wed Jul 13, 2022 2:54 am, edited 1 time in total.
User avatar
linux_facil
Prole
Posts: 3
Joined: Tue Jul 12, 2022 8:06 pm
Location: Omaha, NE. USA.
Contact:

Re: applyLinearImpulse error on player.lua

Post by linux_facil »

Thank you.

I really appreciate you both have taken the time to write this magnificent and detailed responses, you have made me really want to be part of this community and keep learning Lua and Love2d.

I'm going to read more about how the concept of classes work in Lua, I see is not an easy topic but I love challenges.

Sincerely,
Jorge.
“We suffer more often in imagination than in reality” -Seneca.
User avatar
pgimeno
Party member
Posts: 3640
Joined: Sun Oct 18, 2015 2:58 pm

Re: applyLinearImpulse error on player.lua

Post by pgimeno »

I made a few mistakes. I've edited my post and checked that it works.
User avatar
darkfrei
Party member
Posts: 1195
Joined: Sat Feb 08, 2020 11:09 pm

Re: applyLinearImpulse error on player.lua

Post by darkfrei »

Very probably the function that you need:

Code: Select all

function Player:applyLinearImpulse(dvx, dvy)
  self.vx = self.vx + dvx
  self.vy = self.vy + dvy
end
And then you get the vertical jump by the calling:

Code: Select all

self:applyLinearImpulse(0, -500)
Actually in the games the vertical speed must be not higher than some value, for example

Code: Select all

if self.vy > MaxVY then 
  self.vy = MaxVY
elseif self.vy < -MaxVY then
  self.vy = -MaxVY
end 

Where the MaxVY is the highest allowed vertical velocity in game.
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
linux_facil
Prole
Posts: 3
Joined: Tue Jul 12, 2022 8:06 pm
Location: Omaha, NE. USA.
Contact:

Re: applyLinearImpulse error on player.lua

Post by linux_facil »

Thank you again, I followed your recommendations and I have this code working. I appreciate your comments, definitively I will be reading more on how classes work in Lua.

In Main.lua

Code: Select all

WF = require("windfield")
require("player")

function love.load()
	World = WF.newWorld(0, 0, true)
	World:setGravity(0, 512)
	World:addCollisionClass("Player")
	World:addCollisionClass("Ground")

	Player:load()

	Ground = World:newRectangleCollider(0, 500, 800, 100)
	Ground:setType("static")
	Ground:setCollisionClass("Ground")
end

function love.update(dt)
	World:update(dt)
	Player:update(dt)
end

function love.draw()
	-- for debugging purposes
	World:draw()
end

function love.keypressed(key)
	Player:keypressed(key)
end
In player.lua

Code: Select all

Player = {}

function Player:load()
	self.collider = World:newRectangleCollider(100, 100, 20, 50)
	self.collider:setCollisionClass("Player")
end

function Player:update(dt)
	if love.keyboard.isDown("up") then
		self.collider:applyLinearImpulse(0, -20)
	end
end

function Player:draw() end

function Player:keypressed(key)
	if key == "space" then
		self.collider:applyLinearImpulse(0, -500)
	end
end
I still have much to learn, thank you all.
“We suffer more often in imagination than in reality” -Seneca.
Post Reply

Who is online

Users browsing this forum: Bing [Bot] and 6 guests