[SOLVED] Why a ground(bottom hit) and ceil in the same tilemap gives the same (0,-1,0) normal?

Hi, I am learning collision. I am trying to create a top down game. I am using Defold 1.2.179, since my graphic card is too old to support current version.

I set my player as kinematic object and whole wall and ground’s tilemap as static ( Sci-fi Interior tiles | OpenGameArt.org).

I read Platformer Defold tutorial platformer game code. Although ti does not perfectly suits to my need, I think I can use

local function handle_obstacle_contact(self, normal, distance)
    -- project the correction vector onto the contact normal
    -- (the correction vector is the 0-vector for the first contact point)
    local proj = vmath.dot(self.correction, normal)
    -- calculate the compensation we need to make for this contact point
    local comp = (distance - proj) * normal
    -- add it to the correction vector
    self.correction = self.correction + comp
    -- apply the compensation to the player character
    go.set_position(go.get_position() + comp)
    -- check if the normal points enough up to consider the player standing on the ground
    -- (0.7 is roughly equal to 45 degrees deviation from pure vertical direction)
    if normal.y > 0.7 then
        self.ground_contact = true
    end
    -- project the velocity onto the normal
    proj = vmath.dot(self.velocity, normal)
    -- if the projection is negative, it means that some of the velocity points towards the contact point
    if proj < 0 then
        -- remove that component in that case
        self.velocity = self.velocity - proj * normal
    end
end

part (without velocity etc.)

I am using simple movements code :

function handle_movement(self, action_id, action) 

	if action_id == up then
		if action.pressed then
			self.active_keys["up"] = true	
			self.alongVector=vmath.vector3(0,1,0)
			--change state to walk/run etc.
			self:set_movement((self.toggle_run=="w") and layered.MOVEMENT.WALK or layered.MOVEMENT.RUN)
		elseif action.released then
			self.active_keys["up"] = nil
			check_move_idle(self)	
		end	
	elseif action_id == down then
		if action.pressed then
			self.active_keys["down"] = true	
			self.alongVector=vmath.vector3(0,-1,0) 
			--change state to walk/run etc.
			self:set_movement((self.toggle_run=="w") and layered.MOVEMENT.WALK or layered.MOVEMENT.RUN)
		elseif action.released then
			self.active_keys["down"] = nil
			check_move_idle(self)
		end
	elseif action_id == left then
		if action.pressed then
			self.active_keys["left"] = true	
			self.alongVector=vmath.vector3(-1,0,0) 
			--change state to walk/run etc.
			self:set_movement((self.toggle_run=="w") and layered.MOVEMENT.WALK or layered.MOVEMENT.RUN)
		elseif action.released then
			self.active_keys["left"] = nil
			check_move_idle(self)	
		end
	elseif action_id == right then
		if action.pressed then
			self.active_keys["right"] = true	
			self.alongVector=vmath.vector3(1,0,0) 
			--change state to walk/run etc.
			self:set_movement((self.toggle_run=="w") and layered.MOVEMENT.WALK or layered.MOVEMENT.RUN)
		elseif action.released then
			self.active_keys["right"] = nil
			check_move_idle(self)
		end
		-- standing state changes 
	elseif action_id == standup then
		if action.pressed then
			self.active_keys["standup"] = true
		elseif action.released then
			self.active_keys["standup"] = nil
		end
	--else 
		--handle_menu_requests(self, action_id, action)
	end
	
	-- print("move ",self.movement_state, self.move, direction)
end

This is the first scene ( house ) :slight_smile:

In the current state, I have position such that, I have contact with two walls, both at the bottom and at one the left at the same time, no diffusion, just touch. No problem here. But when I press down key, player tries to go down self.alongVector changed to (0,-1,0).

on_message function I got normal vector as (0,-1,0)!

and in here

when I tried to go up, collision with ceil wall gave again normal as (0,-1,0) !

  1. why both ceil and botton collisions gave the same normal? because of tilemap - tilesource? How can I distinguish bottom-top collisions? or simply do I need to acc.to my movement code?

  2. how can I reconfigure the movement code to in the example tutorial to prevent wall diffusions and stop player when collide with obstable? Thanks.

Ok, I found a solution as :

local function handle_obstacle_contact(self, normal, distance)
	if  distance ~= 0 and distance >0 then -- mean penetrated into collision box 
		-- means diffusion occured , self.diffused/penetrated is true
		if math.abs(normal.y) > 0.7 then
			if self.move.y > 0 then -- if while moving upward
				self.alongVector= vmath.vector3(0,-1,0)
				self.speed=distance
		
			elseif self.move.y < 0 then -- if while moving down
				self.alongVector= vmath.vector3(0,1,0)
				self.speed=distance
			end	
		end

		if math.abs(normal.x) > 0.7 then -- if while moving right, there is a wall at right
			if self.move.x > 0 then
				self.alongVector= vmath.vector3(-1,0,0)
				self.speed=distance
			elseif self.move.x <0 then -- if while moving left, there is a wall at left 
				self.alongVector= vmath.vector3(1,0,0)
				self.speed=distance
			end
		end
	end
end

which is purely based on penetration amount… I used my working movement code too.. But there is a continuing problem : although while player moving (move.y and move.x is greater than zero) code works as expected. But when after first hit and move.x and move.y is zero, than pushing up,down,right left keys cause that player slowly penetrated into walls… so not solved completely yet! :frowning: Any suggestion? my movement update code is like that in multi layered FSM :

-- Movement behaviors
M.MOVEMENT_STATES = {
	[M.MOVEMENT.IDLE] = {
		enter = function(self) 
			self.speed=0
		end,
		update = function(self, dt) 
			self.alongVector=vmath.vector3(0,0,0)
		
			self.move = vmath.vector3(0,0,0) -- reset to zero
			if self.stamina < 100 and self.stamina >=0 then
				self.stamina = self.stamina + self.staminaIncreaseFactor 
			else 
				self.stamina = 100
			end 
		end,
		exit = function(self) 
		end
	},
	[M.MOVEMENT.WALK] = {
		enter = function(self)
			self.speed=self.walk_speed * self.speedPostureFactor
			
		end,
		update = function(self, dt)
			
			self.move = vmath.vector3(
			self.speed*self.alongVector.x, 
			self.speed*self.alongVector.y,
			self.speed*self.alongVector.z)
			
			local pos = go.get_position()
			pos = pos + self.move * dt
			go.set_position(pos)
		end,
		exit = function(self) 
		end
	},
	[M.MOVEMENT.RUN] = {
		enter = function(self)
			self.speed=self.run_speed * self.speedPostureFactor
		end,
		update = function(self, dt)
			if self.stamina >0 then
				self.stamina = self.stamina - self.staminaDecreaseFactor
				
				self.move = vmath.vector3(
				self.speed*self.alongVector.x,
				self.speed*self.alongVector.y,
				self.speed*self.alongVector.z)
				
				local pos = go.get_position()
				pos = pos + self.move * dt
				go.set_position(pos)
			else
				
				self.toggle_run = "w"
				self:set_movement(M.MOVEMENT.WALK)
				
			end
		end,
		exit = function(self) 
		end
	}
}

Thanks.

I think finally I solved perfectly below, code does what it intended :

local function handle_obstacle_contact(self, normal, distance)	
	if distance >0 then -- mean penetrated into collision box -- no need  distance ~= 0 I deleted

		-- means diffusion occured , self.diffused/penetrated is true
		if math.abs(normal.y) > 0.7 then
			if self.move.y > 0 then -- if while moving upward
				go.set_position(go.get_position()+vmath.vector3(0,-distance , 0))
	
			elseif self.move.y < 0 then -- if while moving down
				go.set_position(go.get_position()+vmath.vector3(0,distance , 0))

			else 
				--FIXED print("NO MOVE, but cont. penetration")
				go.set_position(go.get_position()+vmath.vector3(0,normal.y*(distance), 0))
			end	
		end

		if math.abs(normal.x) > 0.7 then -- if while moving right, there is a wall at right
			if self.move.x > 0 then
				go.set_position(go.get_position()+vmath.vector3(-distance,0, 0))
	
			elseif self.move.x < 0 then -- if while moving left, there is a wall at left 
				go.set_position(go.get_position()+vmath.vector3(distance,0, 0))

			else 
				--FIXED print("NO MOVE, but cont. penetration")
				go.set_position(go.get_position()+vmath.vector3(normal.x*(distance), 0,0))
			end
		end
	end
end

Thanks for everyone who interested.

1 Like

Ok, today I noticed that, when my player touched to two walls/obstacles at the same time, then, “jitter” or small shaking on my player occurs. To prevent this I asked chatgpt ( :smiley: ) to solve according to my own movement logic and it sends me jitter free correction code , I want to share it with you all : (similar to in defold tutorial page above but without self.velocity or dt calculations) :

local function handle_obstacle_contact(self, normal, distance)
    if distance > 0 then

        -- current penetration vector
        local pen_vec = normal * distance

        -- project accumulated correction onto this penetration
        local proj = vmath.dot(self.correction, pen_vec) / vmath.dot(pen_vec, pen_vec)

        -- only proceed if we have not already fully corrected
        if proj < 1 then
            -- remaining movement we still need to correct
            local comp = pen_vec * (1 - proj)

            -- move out of collision
            go.set_position(go.get_position() + comp)

            -- add to accumulated correction
            self.correction = self.correction + comp
        end
    end
end

But with small addition codes :

-- add please below code parts to proper positions (init, update and on_message functions)

function init(self)
    self.correction = vmath.vector3()
end

function update(self, dt)
    -- reset correction at start of frame
    self.correction = vmath.vector3()

    -- apply movement from input
    local pos = go.get_position()
    pos = pos + self.alongVector * self.move * dt
    go.set_position(pos)
end

function on_message(self, message_id, message, sender)
    if message_id == hash("contact_point_response") then
        handle_obstacle_contact(self, message.normal, message.distance)
    end
end

Then you have a perfect jitter free movement code. Thanks :D.