By efficient, I mean performance wise. If you have to access class members rapidly, like when drawing UI, what is the best way to index them?
My understanding is that table-based classes use less memory and are faster at creating instances, while closure-based classes have faster function calls and let you have private fields which are quickly indexed because they are stored as upvalues. What's the best implementation for a situation like the example class below?
-- Example of Table-based Class
local class = {}
class.x = 0
class.y = 0
class.w = 0
class.h = 0
-- Draw would be called for potentially dozens of instances many times per second
function class:Draw()
draw_rect(self.x, self.y, self.w, self.h)
end
-- Example of Closure-based class
local function class(_x, _y, _w, _h)
-- the new instance
local self = {
-- public fields
visible = false
}
-- private fields are locals
local x, y, w, h = _x, _y, _w, _h
function self.SetPos(_x, _y)
x = _x
y = _y
end
function self.GetPos()
return x, y
end
function self.GetVisible()
return self.visible
end
-- return the instance
return self
end
local obj = class(10, 20, 40, 80)
print(obj.GetPos()) --> 10, 20
obj.SetPos(50, 100)
print(obj.GetPos()) --> 50, 100
obj.x = 21
obj.y = 42
print(obj.GetPos()) --> 50, 100 (unchanged, private)
obj.visible = true
print(obj.GetVisible()) -- true (public)
Related
I know Lua remember value in the same field/function with the Same defined Variable. but I still lack the concept behind it. Let's say I'see Int Variable = 2 which can be modify if we iterate over it Int variable = 8
Same logic I was applying on the Code. I got the results, but I didn't understand the concept behind it.
You Can see in my Code I was saving some value in Var x and saveX
and I was doing saveX = x from my logic The value of x should be saved inside saveX instead it was saving the loop value as I'm applying over it. Why is that?
-- Graphical Representation of table1
--[[ { { {},{},{},{} },
{ {},{},{},{} },
{ {},{},{},{} },
{ {},{},{},{} } } ]]
_Gtable1 = {}
for y = 1, 4 do
table.insert(_Gtable1, {})
for x = 1, 4 do
table.insert(_Gtable1[y], {
x = (x - 1) * 10, --Coordinate value of X
y = (y - 1) * 10, --Coordinate value of Y
-- what if i want to save Coordinate value on another variable How should i do?
saveX = x, -- Saving Loop X
saveY = y, -- Saving Loo Y
t = "Check" -- to Debug
})
end
end
ti = _Gtable1
for y = 1, 4 do
for x = 1, 4 do
tim = ti[y][x]
-- print(tim.saveX) -- output 1, 2, 3, 4
print(tim.x) -- output 0, 10, 20, 30
end
end
I hope I understood the question:
Let's simplify the example:
local t = {
x = 10,
y = x,
}
You assume t.y is 10, because you declared it a line before that? But it is nil here, because x = 10, is no variable. It's part of the table constructor and can not be referenced in the same constructor. Check out https://www.lua.org/pil/3.6.html for more details.
After the constructor has finished, you can access that as t.x (not x!).
Now for your example, you need to move (x - 1) * 10 outside the constructor, e.g.: local tempX = (x - 1) * 10. Then use tempX inside the constructor. Or you can set your values individually, e.g. _Gtable1[y][x].saveX = _Gtable1[y][x].x. I would prefer the former.
I'm trying to have my __index method fire anytime a key is read from this table, but it is apparently not firing since A) the contents read are never modified according to its intended math and B) its print statement never fires. What am I missing, please?
local scalar = 2
local scalar_template = {
__index = function(t, k)
print('hi?') --never fires
return rawget(t,k) * scalar
end
}
local m = {}
m.scalars = setmetatable({width = 2, height = 1, x = 10, y = 10}, scalar_template)
print(m.scalars.width) --2 rather than (2 * ui_scalar = 4)
scalar = 2.5
print(m.scalars.width) -- 2 rather than (2 * ui_scalar = 5)
You seem to have made a pretty common mistake, __index is only called if the key being used does NOT have a value in the table you are indexing with it.
You need to make use a blank table in the setmetatable call and reference the actually table in the meta function. You often see this with "protected" or "read only" tables.
local scalar = 2
local protected = {width = 2, height = 1, x = 10, y = 10}
local scalar_template = {
__index = function(_, k)
print('hi?')
return protected[k] * scalar
end
}
local m = {}
m.scalars = setmetatable({}, scalar_template)
print(m.scalars.width) --2 rather than (2 * ui_scalar = 4)
scalar = 2.5
print(m.scalars.width) -- 2 rather than (2 * ui_scalar = 5)
Using, Lua, I've made a function to cubically (easing in/"accelerating", then easing out/"decelerating" afterwards) interpolate from one number to another; SimpleSpline takes a number between 0-1 (the time the animation's been going, easiest put), and SimpleSplineBetween does the same, but keeps it in between two given minimum/maximum values.
function SimpleSpline( v )
local vSquared = v*v
return (3 * vSquared - 2 * vSquared * v)
end
function SimpleSplineBetween( mins, maxs, v )
local fraction = SimpleSpline( v )
return (maxs * fraction + mins * (1 - fraction))
end
It all works fine. However, I've run into a bit of an issue. I'd like this to be a bit more dynamic. For example, assume my "mins" is 0.5, and my "maxs" is 1, then I have a variable for time that I pass as V; we'll say it's 0.5, so our current interpolation value is 0.75. Now, let's also assume that suddenly, "maxs" is jerked up to 0.25, so now, we have a new goal to reach.
My current approach to handling situations like the above is to reset our "time" variable and change "mins" to our current value; in the above case 0.75, etc. However, this produces a very noticable "stop" or "freeze" in the animation, because it is being entirely reset.
My question is, how can I make this dynamic without that stop? I'd like it to transition smoothly from moving to one goal number to another.
local Start_New_Spline, Calculate_Point_on_Spline, Recalculate_Old_Spline
do
local current_spline_params
local function Start_New_Spline(froms, tos)
current_spline_params = {d=0, froms=froms, h=tos-froms, last_v=0}
end
local function Calculate_Point_on_Spline(v) -- v = 0...1
v = v < 0 and 0 or v > 1 and 1 or v
local d = current_spline_params.d
local h = current_spline_params.h
local froms = current_spline_params.froms
current_spline_params.last_v = v
return (((d-2*h)*v+3*h-2*d)*v+d)*v+froms
end
local function Recalculate_Old_Spline(new_tos)
local d = current_spline_params.d
local v = current_spline_params.last_v
local h = current_spline_params.h
local froms = current_spline_params.froms
froms = (((d-2*h)*v+3*h-2*d)*v+d)*v+froms
d = ((3*d-6*h)*v+6*h-4*d)*v+d
current_spline_params = {d=d, froms=froms, h=new_tos-froms, last_v=0}
end
end
Usage example according to your values:
Start_New_Spline(0.5, 1) -- "mins" is 0.5, "maxs" is 1
local inside_spline = true
while inside_spline do
local goal_has_changed = false
for time = 0, 1, 0.015625 do -- time = 0...1
-- It's time to draw next frame
goal_has_changed = set to true when goal is changed
if goal_has_changed then
-- time == 0.5 -> s == 0.75, suddenly "maxs" is jerked up to 0.25
Recalculate_Old_Spline(0.25) -- 0.25 is the new goal
-- after recalculation, "time" must be started again from zero
break -- exiting this loop
end
local s = Calculate_Point_on_Spline(time) -- s = mins...maxs
Draw_something_at_position(s)
wait()
end
if not goal_has_changed then
inside_spline = false
end
end
First I create the functions that will spawn and check collisions:
floors = {}
function makeFloor(x, y, w, h)
floor = {}
floor.x = x
floor.y = y
floor.w = w
floor.h = h
table.insert(floors, floor)
end
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
I then I call the makeFloor() funciton for every platform I want to spawn (its primitive I know), check the collisions and draw the the platforms:
-- place platforms
makeFloor(750, 600, 300, 10)
makeFloor(20, 500, 700, 10)
--check collision
for i, f in ipairs(floors) do
if checkCollision(player.x,player.y,player.w,player.h,f.x,f.y,f.w,f.h) then
player.isGrounded = true
player.canJump = true
else player.isGrounded = false
player.canJump = false
end
end
end
function gameDraw()
for i, f in ipairs(floors) do
love.graphics.rectangle("fill", f.x, f.y, f.w, f.h)
end
end
The collision check only works on the last platform called, can someone please explain what logic I am not seeing here? Why does it ignore the first platform? Is it being over-ridden?
You're erasing results of previous collision checks when you assign false to .isGrounded and .canJump fields in the else clause of collision check.
Do not assign false after every check. Instead initialize those fields to false just before entering the loop, and only assign true when collision found.
I'm making a simple shooter in lua using love2d. For some reason when i launch the game the program thinks the enemy has been shot and doesn't spawn it. I think theres an issue on line 80. It seems to think enemy is nil at all times no matter what. I'm not getting any errors. Ill link to a pastebin with the code.
Edit: I've updated my code quite a bit and have solved the issue above. I think im checking for collision using the bounding box incorrectly. No matter where the bullet passes the enemy is never set to nil. I think it's because it checks with bullets.x instead of o.x but i cant check with o.x because its a local variable in the for loop earlier in the code.
http://pastebin.com/iwL0QHsc
Currently, your code does
if (CheckCollision) then
If you don't provide any parameters, it will check if the variable 'CheckCollision' exists. In this case, it does because you have declared it as a function on line 53, so every update 'enemy' will be set to nil.
if CheckCollision(x3,y3,x2,y2,w2,h2) then
Use this line but replace the variables with the variables of the respective entity/s.
Using this, you can use
if enemy then
In your draw call, which will check if 'enemy' exists.
Just by the way, in lua an if statement doesnt need to be within brackets.
if (x > 3) then
Functions exactly the same as
if x > 3 then
Edit:
In the new code you have provided, when you declare the function you are naming its arguments as variables that already exist. Usually, you put in some arbitrary variables you haven't used as arguments. Eg.
function test(a, b)
print(a + b)
end
Then to use it.
test(1, 2)
Or if you want to use variables.
var1 = 1
var2 = 2
test(var1, var2)
Using variables that already exist isn't too bad, it just means you can't use them. Using variables in a table, lua probably isn't too happy about.
So where you have
function CheckCollision(o.x,o.y,o.w,o.h, enemy.x,enemy.y,enemy.w,enemy.h)
return o.x < enemy.x+enemy.w and
enemy.x < o.x+o.w and
o.y < enemy.y+enemy.h and
enemy.y < o.y+o.h
end
Use something like this instead.
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
Alternatively, you could skip out on the parameters and have it hard coded.
function CheckCollision()
return o.x < enemy.x+enemy.w and
enemy.x < o.x+o.w and
o.y < enemy.y+enemy.h and
enemy.y < o.y+o.h
end
I'm not sure if this is the source of your error as I don't have access to a proper computer to try it but it is useful information anyway.
When you load/run a file in Lua, Lua looks through the whole file in a sequential manner once, so your line to check collisions only occurs upon main.lua's loading, never to be looked at again.
As your code stands now, it only checks the collision of the enemy and bullet once
if CheckCollision(enemy.x,enemy.y,enemy.w,enemy.h,bullets.x,bullets.y,bullets.w,bullets.h) then enemy = nil
end
If you put this into the love.update(dt) method, it will achieve your desired effect.
I'd like to note that once the enemy is set to nil (collision occurs), you will throw an error about attempting to index a nil value since your enemy variable is no longer a table.
Also noteworthy, these lines
bullets.x = o.x
bullets.y = o.y
in the for loop
for i, o in ipairs(bullets) do
cause your bullets to behave improperly (at least, I assume you don't wish for the behavior they have) Each time a new bullet is fired, it is added to the bullets table with the code
table.insert(bullets, {
x = player.x,
y = player.y,
dir = direction,
speed = 400
})
This puts each new table into the #bullets + 1 (the table's last index + 1) index of bullets. Since your for loop iterates over each bullet object in the bullets table, the last assignment that occurs is always on the last bullet in the table.
Let me try to explain this simpler.
Say a player fires two bullets. The first bullet firing will invoke the table.insert(...) call I mentioned before. So, our bullets table will look like this
bullets = {
x = 100,
y = 100, -- This is what you set player.x and player.y to in the start.
w = 15,
h = 15,
-- This is that new table we added - the 1st bullet fired.
{
-- This will all be inside it according to the table.insert(...) call.
x = 100, -- What player.x is equal to
y = 100, -- What player.y is equal to
dir = ... -- Who knows what it is, just some radians that you calculated.
speed = 400
}
}
Now, you used an ipairs(...) call which means that our for loop will only look at the tables inside bullets - smart thinking. But there is a problem with its implementation.
-- With our new table inside bullets, we will only have that table to look at for the entire loop. So, lets jump right into the loop implementation.
local i, o
for i, o in ipairs(bullets) do
-- This is fine. These lines look at the new table's x and y values and move them correctly.
o.x = o.x + math.cos(o.dir) * o.speed * dt
o.y = o.y + math.sin(o.dir) * o.speed * dt
-- This is the problem.
bullets.x = o.x -- Now, those x and y values in the bullets table are set to the new table's x and y values.
bullets.y = o.y
-- The rest of the loop works fine.
...
end
Now, for one new bullet, it works fine. Each update will update bullets.x and bullets.y correctly as the new bullet travels. But now lets considered the second bullet our player fired.
The new look of bullets is like this
bullets = {
x = 150, -- These are equal to the first bullet's values - for now, at least.
y = 150,
w = 15,
h = 15,
-- This 1st bullet is still here.
{
x = 150, -- Lets say the bullet has moved 50 pixels in both directions.
y = 150,
dir = ...
speed = 400
},
-- This is the new second bullet.
{
x = 100, -- Remember player.x and player.y are both 100
y = 100,
dir = ...
speed = 400
}
}
See where this is going yet? Lets jump to the for loop on the first iteration.
-- First iteration occurs. We're looking at the first bullet.
for i, o in ipairs(bullets) do
o.x = o.x + math.cos(o.dir) * o.speed * dt -- Lets say o.x = 160 now
o.y = o.y + math.sin(o.dir) * o.speed * dt -- and o.y = 160
bullets.x = o.x -- bullets.x = o.x, so bullets.x = 160
bullets.y = o.y -- bullets.y = o.y, so bullets.y = 160
...
end
But then we get to the second bullet...
-- Second iteration, second bullet.
for i, o in ipairs(bullets) do
-- Since it's the new table, o.x and o.y start at 100
o.x = o.x + math.cos(o.dir) * o.speed * dt -- Lets say o.x = 110
o.y = o.y + math.sin(o.dir) * o.speed * dt -- Lets say o.y = 110 as well
bullets.x = o.x
bullets.y = o.y
-- But now our bullets.x and bullets.y have moved to the new bullet!
-- The position of the 1st bullet is completely forgotten about!
...
end
And this is the problem. The way the loop is written currently, the program only cares about the most recently fired bullet because it is going to be placed last in the bullets table - it is checked and assigned to bullets.x and bullets.y last. This causes the collision check to only care if the most recently fired bullet is touching the enemy, none of the other ones.
There are two ways to fix this. Either evaluate the position of each bullet separately on collisions and add widths and heights to their tables, like this
-- When you add a bullet
table.insert(bullets, {
x = player.x,
y = player.y,
w = bullets.w,
h = bullets.h,
dir = direction,
speed = 400
})
-- When looking for collisions
local i, o
for i, o in ipairs(bullets) do
o.x = o.x + math.cos(o.dir) * o.speed * dt
o.y = o.y + math.sin(o.dir) * o.speed * dt
-- This will destroy an enemy correctly
if CheckCollision(enemy.x,enemy.y,enemy.w,enemy.h, o.x, o.y, o.w, o.h) then enemy = nil end
if (o.x < -10) or (o.x > love.graphics.getWidth() + 10)
or (o.y < -10) or (o.y > love.graphics.getHeight() + 10) then
table.remove(bullets, i)
end
end
This way you just move the collision checker's position to inside the loop and change its parameters.
The other way is to make class-like tables that can be "instantiated," whose objects' metatables point to the class-like table. Harder, but better practice and much easier to write methods and what not for. Makes generic inspections and evaluations of multiple players, enemies, bullets, etc. much easier as well.