Passing through an object which has physics

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,

  1. Stop any pending timers or transitions (you’ll need references for those when you create them)
  2. Remove physics body, if any.
  3. Remove display object.
  4. 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:

  1. 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 )
  1. 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. :slightly_smiling_face:

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.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.