Lua:在简单的射击游戏中制作碰撞系统,我在移除敌方物体时遇到问题
Lua: Making a collision system in a simple shooter and im having issues with removing the enemy object
我正在 lua 使用 love2d 制作一个简单的射击游戏。出于某种原因,当我启动游戏时,程序认为敌人已被击中并且不会生成它。我认为第 80 行存在问题。无论如何,它似乎始终认为 enemy 为零。我没有收到任何错误。生病 link 到一个带有代码的 pastebin。
编辑:我已经更新了我的代码并解决了上述问题。我认为我不正确地使用边界框检查碰撞。无论子弹从哪里经过,敌人都不会被设置为零。我认为这是因为它使用 bullets.x 而不是 o.x 检查,但我无法使用 o.x 检查,因为它是代码前面的 for 循环中的局部变量。
目前,您的代码
if (CheckCollision) then
如果您不提供任何参数,它将检查变量 'CheckCollision' 是否存在。在这种情况下,它确实是因为您在第 53 行将其声明为函数,因此每次更新 'enemy' 都将设置为 nil。
if CheckCollision(x3,y3,x2,y2,w2,h2) then
使用此行,但将变量替换为相应 entity/s.
的变量
使用这个,你可以使用
if enemy then
在您的绘图调用中,它将检查 'enemy' 是否存在。
顺便说一下,在 lua 中,if 语句不需要放在方括号中。
if (x > 3) then
功能完全一样
if x > 3 then
编辑:
在您提供的新代码中,当您声明函数时,您将其参数命名为已经存在的变量。通常,您会放入一些您没有用作参数的任意变量。例如
function test(a, b)
print(a + b)
end
然后使用它。
test(1, 2)
或者如果你想使用变量。
var1 = 1
var2 = 2
test(var1, var2)
使用已经存在的变量并不太糟糕,这只是意味着你不能使用它们。在 table、lua 中使用变量可能不太高兴。
所以你有
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
改用类似这样的东西。
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
或者,您可以跳过参数并对其进行硬编码。
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
我不确定这是否是您错误的根源,因为我无法访问合适的计算机来尝试它,但无论如何它都是有用的信息。
当您 load/run Lua 中的文件时,Lua 按顺序查看整个文件一次,因此您检查冲突的行仅发生在 main.lua
的加载中,再也不会被看到了。
按照你现在的代码,它只检查一次敌人和子弹的碰撞
if CheckCollision(enemy.x,enemy.y,enemy.w,enemy.h,bullets.x,bullets.y,bullets.w,bullets.h) then enemy = nil
end
如果你把它放到love.update(dt)
方法中,它会达到你想要的效果。
我想指出,一旦 enemy
设置为 nil
(发生冲突),您将抛出一个关于尝试索引 nil 值的错误,因为您的 enemy
变量不再是 table.
同样值得注意的是,这些行
bullets.x = o.x
bullets.y = o.y
在for循环中
for i, o in ipairs(bullets) do
导致你的子弹行为不当(至少,我假设你不希望他们有这种行为)每次发射新子弹时,它都会被添加到 bullets
table 代码
table.insert(bullets, {
x = player.x,
y = player.y,
dir = direction,
speed = 400
})
这会将每个新的 table 放入 bullets
的 #bullets + 1
(table 的最后一个索引 + 1)索引中。由于您的 for 循环遍历 bullets
table 中的每个项目符号对象,因此发生的最后一个赋值总是在 table 中的最后一个项目符号上。
让我试着解释得更简单一些。
假设玩家发射了两发子弹。第一个子弹发射将调用我之前提到的 table.insert(...)
调用。所以,我们的 bullets
table 看起来像这样
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
}
}
现在,您使用了 ipairs(...)
调用,这意味着我们的 for 循环将只查看 bullets
中的 tables - 聪明的想法。但是它的实现有问题。
-- 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
现在,对于一颗新子弹,它工作正常。随着新子弹的移动,每次更新都会正确更新 bullets.x
和 bullets.y
。但是现在让我们考虑一下我们的玩家发射的第二颗子弹。
bullets
的新造型是这样的
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
}
}
看到这是怎么回事了吗?让我们跳到第一次迭代的 for 循环。
-- 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
然后我们到了第二个项目符号...
-- 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
这就是问题所在。目前循环的编写方式,程序只关心最近发射的子弹,因为它将放在 bullets
table 的最后 - 它被检查并分配给 bullets.x
bullets.y
最后。这会导致碰撞检查只关心最近发射的子弹是否接触到敌人,其他的 none。
有两种方法可以解决这个问题。分别评估每个子弹在碰撞时的位置,然后将宽度和高度添加到它们的 table 中,就像这样
-- 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
这样您只需将碰撞检查器的位置移动到循环内部并更改其参数。
另一种方法是使 class 类 tables 可以是 "instantiated," 其对象的元 tables 指向 class -喜欢 table。更难,但更好的练习,更容易编写方法,而不是为了什么。使对多个玩家、敌人、子弹等的一般检查和评估也变得更加容易。
我正在 lua 使用 love2d 制作一个简单的射击游戏。出于某种原因,当我启动游戏时,程序认为敌人已被击中并且不会生成它。我认为第 80 行存在问题。无论如何,它似乎始终认为 enemy 为零。我没有收到任何错误。生病 link 到一个带有代码的 pastebin。
编辑:我已经更新了我的代码并解决了上述问题。我认为我不正确地使用边界框检查碰撞。无论子弹从哪里经过,敌人都不会被设置为零。我认为这是因为它使用 bullets.x 而不是 o.x 检查,但我无法使用 o.x 检查,因为它是代码前面的 for 循环中的局部变量。
目前,您的代码
if (CheckCollision) then
如果您不提供任何参数,它将检查变量 'CheckCollision' 是否存在。在这种情况下,它确实是因为您在第 53 行将其声明为函数,因此每次更新 'enemy' 都将设置为 nil。
if CheckCollision(x3,y3,x2,y2,w2,h2) then
使用此行,但将变量替换为相应 entity/s.
的变量
使用这个,你可以使用
if enemy then
在您的绘图调用中,它将检查 'enemy' 是否存在。
顺便说一下,在 lua 中,if 语句不需要放在方括号中。
if (x > 3) then
功能完全一样
if x > 3 then
编辑: 在您提供的新代码中,当您声明函数时,您将其参数命名为已经存在的变量。通常,您会放入一些您没有用作参数的任意变量。例如
function test(a, b)
print(a + b)
end
然后使用它。
test(1, 2)
或者如果你想使用变量。
var1 = 1
var2 = 2
test(var1, var2)
使用已经存在的变量并不太糟糕,这只是意味着你不能使用它们。在 table、lua 中使用变量可能不太高兴。
所以你有
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
改用类似这样的东西。
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
或者,您可以跳过参数并对其进行硬编码。
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
我不确定这是否是您错误的根源,因为我无法访问合适的计算机来尝试它,但无论如何它都是有用的信息。
当您 load/run Lua 中的文件时,Lua 按顺序查看整个文件一次,因此您检查冲突的行仅发生在 main.lua
的加载中,再也不会被看到了。
按照你现在的代码,它只检查一次敌人和子弹的碰撞
if CheckCollision(enemy.x,enemy.y,enemy.w,enemy.h,bullets.x,bullets.y,bullets.w,bullets.h) then enemy = nil
end
如果你把它放到love.update(dt)
方法中,它会达到你想要的效果。
我想指出,一旦 enemy
设置为 nil
(发生冲突),您将抛出一个关于尝试索引 nil 值的错误,因为您的 enemy
变量不再是 table.
同样值得注意的是,这些行
bullets.x = o.x
bullets.y = o.y
在for循环中
for i, o in ipairs(bullets) do
导致你的子弹行为不当(至少,我假设你不希望他们有这种行为)每次发射新子弹时,它都会被添加到 bullets
table 代码
table.insert(bullets, {
x = player.x,
y = player.y,
dir = direction,
speed = 400
})
这会将每个新的 table 放入 bullets
的 #bullets + 1
(table 的最后一个索引 + 1)索引中。由于您的 for 循环遍历 bullets
table 中的每个项目符号对象,因此发生的最后一个赋值总是在 table 中的最后一个项目符号上。
让我试着解释得更简单一些。
假设玩家发射了两发子弹。第一个子弹发射将调用我之前提到的 table.insert(...)
调用。所以,我们的 bullets
table 看起来像这样
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
}
}
现在,您使用了 ipairs(...)
调用,这意味着我们的 for 循环将只查看 bullets
中的 tables - 聪明的想法。但是它的实现有问题。
-- 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
现在,对于一颗新子弹,它工作正常。随着新子弹的移动,每次更新都会正确更新 bullets.x
和 bullets.y
。但是现在让我们考虑一下我们的玩家发射的第二颗子弹。
bullets
的新造型是这样的
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
}
}
看到这是怎么回事了吗?让我们跳到第一次迭代的 for 循环。
-- 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
然后我们到了第二个项目符号...
-- 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
这就是问题所在。目前循环的编写方式,程序只关心最近发射的子弹,因为它将放在 bullets
table 的最后 - 它被检查并分配给 bullets.x
bullets.y
最后。这会导致碰撞检查只关心最近发射的子弹是否接触到敌人,其他的 none。
有两种方法可以解决这个问题。分别评估每个子弹在碰撞时的位置,然后将宽度和高度添加到它们的 table 中,就像这样
-- 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
这样您只需将碰撞检查器的位置移动到循环内部并更改其参数。
另一种方法是使 class 类 tables 可以是 "instantiated," 其对象的元 tables 指向 class -喜欢 table。更难,但更好的练习,更容易编写方法,而不是为了什么。使对多个玩家、敌人、子弹等的一般检查和评估也变得更加容易。