Hey, i’m still an amateur at making games. Right now i’m working at making a 2D Hack n Slash kind of game. My problem is i don’t how to make my hero to pass through an enemy. I use physics on both of the hero and the enemy, i already make the .isSensor false but it is still not passing through the enemy, and i already use .isBodyActive which make it possible but the enemy is not having a physics anymore. Can somebody help me? Thank you so much for the help.
If you do want the objects to be able to pass through each other, then you want to set isSensor to true. Sensors only detect collisions, but they don’t actually collide with other objects.
You’ll likely need to give your enemies multi-element bodies, see:
And more about multi-element collisions:
You’ll need to create at least 2 elements for the enemy’s physics body, each element with their own collision filter. One being the enemy’s “shape” that can collide with the player, but not with the level. The other being the enemy’s “feet”, or just the same “shape” as before, and this would collide with the level, but not the player. You can make the element that can collide with the player into a sensor, so the player can pass through the enemy. The “feet” shape then keeps the enemy a physical body that keeps them in the level.
You can read about collision filtering here:
Thanks bro, it really helps!
Also i want to ask something but not about physics. I have an “attack” sequence of a sprite where if the loopcount = 0, it will run all 4 frames, but when i turn it to loopcount = 1, the last frame is skipped. I already tried to delay the idle sequence before its done attacking but it doesn’t help. There are 4 types of enemy but only this sprite has this kind of problem.
If I understood correctly, 4 animated objects are setup the same way, but only one of them has the issue? Maybe the sprite sheet setup is off?
Would you be able to isolate the code in question to further troubleshoot?
Yes, i setup the sheet frame per frame.
local buto_sheet = graphics.newImageSheet( "assets/enemy/Buto_Ijo-Sheet.png", {
frames = {
--idle
{
x = 1, y = 60,
width = 150,height = 105
},
{
x = 208, y = 60,
width = 150,height = 105
},
{
x = 414, y = 60,
width = 150,height = 105
},
{
x = 624, y = 60,
width = 150,height = 105
},
--walk
{
x = 1, y = 220,
width = 155, height = 110
},
{
x = 198, y = 220,
width = 155, height = 110
},
{
x = 416, y = 220,
width = 158, height = 110
},
{
x = 618, y = 220,
width = 150, height = 109
},
--attack
{
x = 30, y = 390,
width = 104, height = 100
},
{
x = 220, y = 375,
width = 136, height = 115
},
{
x = 410, y = 335,
width = 166, height = 155
},
{
x = 680, y = 335,
width = 156, height = 155
},
}
})
local buto_sequenceData = {
{ name = "idle", frames = { 1, 2, 3, 4 }, time = 700, loopCount= 0 },
{ name = "walk", frames = { 5, 6, 7, 8 } , time = 800, loopCount = 0 },
{ name = "attack", frames = {9, 10, 11, 12}, time = 700, loopCount = 1}
}
Enemy = display.newSprite( parent,buto_sheet, buto_sequenceData )
Enemy.x, Enemy.y = x, y
Enemy.anchorY = 1
Enemy:setSequence( "idle" )
Enemy:play()
just like what i said, the animation is good if the loopcount = 0, but once the loopcount = 1 the last frame is skipped. The animation which has problem is the “attack” sequence
I also just tested with what I could on my end and couldn’t replicate the issue
If you can replicate the issue on a separate project and posted here we can take a closer look.
I don’t know how to post a project in this forum. Instead i’m gonna give you the sprite that i used.
I copied and pasted your code, used your sprite sheet; everything is working fine for me.
Are you changing sequence after attack is completed? The attack sequence by itself plays all 4 frames.
Side note, the sprite sheet already has the frames aligned so that you don’t need to specify the frames, thus you can use this instead:
local options = {
width = 836/4, -- sheet width / total columns
height = 492/3, -- sheet height / total rows
numFrames = 12 -- columns * rows
}
local buto_sheet = graphics.newImageSheet( "assets/enemy/Buto_Ijo-Sheet.png", options )
I didn’t now i can make options like that, thanks for that!
Well basically i change the sequence to idle after attacking. So, my simple enemy logic is when it’s near the hero, he will get “idle” sequence then after the attacking cd reach 0, he will attack after the attack I change it again to idle. The way I checked if the enemy is done attacking or not, I use this logic.
if value.sequence == "attack" then
if not value.isPlaying then
timer.performWithDelay( 200, value:setSequence("idle") )
timer.performWithDelay(200, value:play())
end
end
I put this logic in enterFrame(). It works with the other enemy, but this one always skipping the 4th frame.
OK, now that makes sense. The logic will trigger as soon as 4th frame kicks in and therefore will be changed before it’s even visually seen.
First, a fix for your timers:
timer.performWithDelay( 200, function() value:setSequence("idle") end )
timer.performWithDelay( 200, function() value:play() end )
or
timer.performWithDelay( 200, function()
value:setSequence("idle")
value:play()
end )
Reason being, function should be passed as a variable, and it will be called when the specified time has been completed. If you add ()
to your function (per original code) it’ll call the function right away. Since your functions have parameters then the above would be the way to do it.
I don’t know whether that will fix your issue or not, however, you can still go about it using a sprite event listener; in this case it’s whatever works best for you.
Because of the way the listener works, you would still need a timer as you have done, however, if you simply repeat the last frame in your sequence you’ll get the same effect but without the need to use timers:
local function spriteListener( event )
local sprite = event.target -- "event.target" references the sprite
if ( event.phase == "ended" ) then
if sprite.sequence == "attack" then -- this assumes it has a sequence with this name
sprite:setSequence( "idle" ) -- set to new sequence
sprite:play() -- start playing the sequence
end
end
end
local buto_sequenceData = {
{ name = "idle", frames = { 1, 2, 3, 4 }, time = 700, loopCount= 0 },
{ name = "walk", frames = { 5, 6, 7, 8 } , time = 800, loopCount = 0 },
-- notice repeated extra frame at the end; time may need to be adjusted
{ name = "attack", frames = {9, 10, 11, 12, 12}, time = 700, loopCount = 1}
}
Enemy = display.newSprite( buto_sheet, buto_sequenceData )
Enemy.x, Enemy.y = 100,150
Enemy.anchorY = 1
Enemy:addEventListener("sprite", spriteListener)
Enemy:setSequence( "attack" )
Enemy:play()
Sorry for the very late response, your suggestion really help.
I’ve been doing other things lately, now that i’m back at this project again. Now there’s a new problem occurs, when i remove enemies from the table, it got into an error when the enemy died in the middle of an attack animation. I mark the enemy.isDead to true first before removing them from the table, and there’s also an “if” which check if all the enemies in the table is dead then i emptied the table.
local enemies_count = 5
local temp_enemies_count = 0
local clear = true
local dead_enemies = 0
local function enterFrame(event)
for index, value in ipairs(enemies) do
if value.isDead == false then
clear = false
end
end
if (math.floor(math.floor(math.abs(hero.x))/1000)%2) == 0 and math.floor(math.floor(math.abs(hero.x))/1000) ~= 0 then
--enemy
if #enemies < enemies_count and temp_enemies_count < enemies_count then
temp_enemies_count = temp_enemies_count + 1
musuh = display.newRect(world, hero.x+500, _CY, 68,32)
musuh = enemy.new(musuh, "kuyang")
table.insert(enemies, musuh)
end
print("enemy count " .. temp_enemies_count)
print("dead " .. dead_enemies)
print("enemy in table " .. #enemies)
else
temp_enemies_count = 0
end
--remove enemy
for index, value in ipairs(enemies) do
if value.isDead and dead_enemies < #enemies then
dead_enemies = dead_enemies + 1
end
end
if dead_enemies == temp_enemies_count then
print(json.prettify( enemies ))
dead_enemies = 0
for i = #enemies,1,-1 do
table.remove(enemies,i)
end
clear = true
end
print(clear)
for index, value in ipairs(enemies) do
--enemy movement
if value.isDead == false then
local vx, vy = value:getLinearVelocity()
local direction = hero.x - value.x
local left, right = 0, 0
if direction < 0 then
value.flip = -0.133
left = -value.acceleration
elseif direction > 0 then
value.flip = 0.133
right = value.acceleration
end
local dx = left+right
if (dx < 0 and vx > -value.max and value.x > hero.x) or (dx > 0 and vx < value.max and value.x < hero.x )then
if value.name == "buto" then
if not ((value.x > hero.x-120 and value.x < hero.x ) or (value.x < hero.x+120 and value.x > hero.x ))then
value:applyForce(dx or 0, 0, value.x, value.y)
end
else
if not ((value.x > hero.x-100 and value.x < hero.x ) or (value.x < hero.x+100 and value.x > hero.x ))then
value:applyForce(dx or 0, 0, value.x, value.y)
end
end
end
value.xScale = math.min(1, math.max(value.xScale + value.flip, -1))
--enemy attack
--print(value.attackTimer)
if value.attackTimer <= 0 then
if value.name == "buto" then
if ( value.x > hero.x-120 and value.x < hero.x ) or (value.x < hero.x+120 and value.x > hero.x ) then
value:attack()
end
else
if ( value.x > hero.x-100 and value.x < hero.x ) or (value.x < hero.x+100 and value.x > hero.x ) then
value:attack()
end
end
end
--print(value.x .. " - " .. hero.x .. " - " .. vx )
if value.sequence == "attack" and value then
if not value.isPlaying then
timer.performWithDelay( 100, function()
if value then
value:setSequence("idle")
value:play()
end
end )
end
end
if vx == 0 then
--print("lol")
if value.sequence == "walk" and value then
timer.performWithDelay( 100,function ()
value:setSequence("idle")
value:play()
end )
end
end
if vx ~= 0 then
--print("ok")
if value.sequence == "idle" and value then
value:setSequence("walk")
value:play()
end
end
value.attackTimer = value.attackTimer - 1
end
end
end
Sorry if its messy, i’m still learning in this kind of work. I really appreciate your help!
On which line are you getting an error, and what is the actual error code?
Removing a sprite in the middle of an animation by itself doesn’t cause errors.
From that code block, one thing I see that can cause problems is the call for timers on “attack” and “walk” sequence; since they are called 100 MS later it’s possible the object no longer exists by then.
For a clean removal of an object,
- Stop any pending timers or transitions (you’ll need references for those when you create them)
- Remove physics body, if any.
- Remove display object.
- Set object reference to
nil
EDIT:
Also, this code is prone to be called multiple times because it’s inside enterFrame and “walk” sequence won’t change for another 100 MS:
if vx == 0 then
--print("lol")
if value.sequence == "walk" and value then
timer.performWithDelay( 100,function ()
value:setSequence("idle")
value:play()
end )
end
end
if value.sequence == "attack" and value then
if not value.isPlaying then
timer.performWithDelay( 100, function()
if value then
value:setSequence("idle")
value:play()
end
end )
end
end
i got error in there to be more specific in here
value:setSequence("idle")
the error said value is nil
Makes sense. The issue is most likely what I mentioned above.
One workaround would be:
- When creating a timer, create a handle for it:
value.timer = timer.performWithDelay( 100, function()
if value then
value:setSequence("idle")
value:play()
end
end )
- When removing the object, first cancel the timer:
for i = #enemies,1,-1 do
if enemies[i].timer then
timer.cancel(enemies[i].timer)
end
table.remove(enemies,i)
end
Canceling the timer helps, but now i stumble another problem. Everytime i tried to use
hero:removeSelf()
my enterFrame listener in hero.lua still on, eventhought i already put
function Hero:finalize()
Hero:removeEventListener( "collision" )
Runtime:removeEventListener( "enterFrame", enterFrame )
Runtime:removeEventListener( "key", key )
end
i call the hero:removeSelf() inside
function scene:hide( event )
if ( event.phase == "will" ) then
hero:removeSelf()
--world = nil
elseif ( event.phase == "did" ) then
Runtime:removeEventListener("enterFrame", enterFrame)
end
end
i’m very sorry if my reply takes to long, and always asking a new question again and again. its because i’m still new here and try to learn solar2d
Probably better to have opened a new topic as this issue is not relevant to the previous or original topic.
I’m not sure whether hero:removeSelf()
triggers Hero:finalize()
; you’ll need to print out info to better debug your code.
On a side note, having enterFrame
and key
listeners in the Hero module doesn’t sound like a good setup, and you have another enterFrame
in the scene; you probably should work out a way to use the listener in the scene.
This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.