Ok, I started working on a pinball game. And I’m having issues with physics (again). It appears to me as if it’s buggy but it could be me.
Right now I just have a ball and a flipper and the behavior is off. I need this to work perfectly before I commit to this project so hopefully someone can help me out.
-- ball.script
local gravity = -981 -- Gravity force
local ball_mass = 1 -- Mass of the ball
function init(self)
self.velocity = vmath.vector3(0, 0, 0) -- Initial velocity of the ball
self.position = go.get_position() -- Get the initial position of the ball
end
function on_input(self, action_id, action)
-- Input handling is not required for the ball
end
function update(self, dt)
-- Apply gravity to the ball
self.velocity.y = self.velocity.y + gravity * dt / ball_mass
-- Update the ball's position based on its velocity
self.position = self.position + self.velocity * dt
-- Set the new position of the ball
go.set_position(self.position)
end
function on_message(self, message_id, message, sender)
if message_id == hash("contact_point_response") then
-- Simple collision response
-- Reflect the velocity vector based on the normal of the collision
local normal = message.normal
if normal and normal.x and normal.y then
self.velocity = self.velocity - 2 * vmath.dot(self.velocity, normal) * normal
-- If the collision was with the flipper, add the flipper's velocity
if sender == hash("/right_flipper") then
local flipper_velocity = go.get("/right_flipper", "velocity")
self.velocity = self.velocity + flipper_velocity
elseif sender == hash("/left_flipper") then
local flipper_velocity = go.get("/left_flipper", "velocity")
self.velocity = self.velocity + flipper_velocity
end
end
end
end
-- flipper.script
go.property("flipper_side", "left") -- Set this to "left" or "right" in the editor for each flipper
local flipper_speed = 400 -- Speed at which the flipper moves
local max_rotation = math.rad(60) -- Maximum rotation angle in radians
function init(self)
msg.post(".", "acquire_input_focus") -- Acquire input focus to receive input events
self.rotation = go.get_rotation() -- Get the initial rotation of the flipper
self.active = false -- Is the flipper active (moving)?
end
function on_input(self, action_id, action)
if self.flipper_side == "left" and action_id == hash("left_flipper") then
self.active = action.pressed
elseif self.flipper_side == "right" and action_id == hash("right_flipper") then
self.active = action.pressed
end
end
function update(self, dt)
if self.active then
local rotation_change = flipper_speed * dt
if self.flipper_side == "left" then
self.rotation.z = math.min(self.rotation.z + rotation_change, max_rotation)
else
self.rotation.z = math.max(self.rotation.z - rotation_change, -max_rotation)
end
go.set_rotation(self.rotation)
end
end
And this is what I get:
What am I doing wrong? Shouldn’t this be consistent? And the ball should never pass through the flipper? All objects are kinematic. I’m not using dynamic physics.
The dynamic physics aren’t very good in defold. My last game the reactions were very very very slow. But also I am making this a roguelike game so I need to be able to control all aspects.
In regards to the flipper…I didn’t even do anything with it yet. Thanks for pointing that out. That is just so I can use the same GO and identify it with a property for controlling it.
ah…yes the /right_flipper. I was in between going from 2 game objects to one via property. But either way it gives the same physics. The error won’t throw unless I try to activate the flippers and as you can see I’m not even using the flippers other than them being stationary.
Thank you for reading this and pointing those out.
@britzl
BTW: I made the ball dynamic and the flippers static just to see if that made a difference. And this is just janky. WHat the heck am I doing wrong? I know defold wouldn’t allow a physics engine like this in the wild, but I have no clue what to do to fix this to make it 100% reliable/consistent.
Thanks for the tip. I just removed my code. Not sure how that would change dynamic objects, but it did. And now my old issues from a previous game are back. Look how it slows down as it falls. Shouldn’t is speed up or at least remain constant as it falls? Velocity means it should be moving faster. But if the engine is saying it really has not moved far enough I think it would at least be moving the same speed not actually slow down as it does with dynamic objects.
I fixed my code and I’m using kinematic objects and it works as intended. But I really would like to know why the dynamic object with no code doesn’t work as expected. My first game I made felt “off” and people are saying it’s too slow. So I think this is the issue. Resolving this dynamic gravity issue will hopefully fix my first game to feel/play better.
Thank you
This is using kinematic objects and my code setting the gravity and response.
-- ball.script
local gravity = -981 -- Gravity force
local ball_mass = 1 -- Mass of the ball
function init(self)
self.velocity = vmath.vector3(0, 0, 0) -- Initial velocity of the ball
self.position = go.get_position() -- Get the initial position of the ball
self.collision_response = vmath.vector3(0, 0, 0) -- New variable to store the collision response
end
function on_input(self, action_id, action)
-- Input handling is not required for the ball
end
function fixed_update(self, dt)
-- Apply gravity to the ball
self.velocity.y = self.velocity.y + gravity * dt / ball_mass
-- Apply the collision response
self.velocity = self.velocity + self.collision_response
self.collision_response = vmath.vector3(0, 0, 0) -- Reset the collision response
-- Update the ball's position based on its velocity
self.position = self.position + self.velocity * dt
-- Set the new position of the ball
go.set_position(self.position)
end
function on_message(self, message_id, message, sender)
if message_id == hash("contact_point_response") then
-- Simple collision response
-- Reflect the velocity vector based on the normal of the collision
local normal = message.normal
if normal and normal.x and normal.y then
local reflection = self.velocity - 2 * vmath.dot(self.velocity, normal) * normal
-- If the collision was with the flipper, add the flipper's velocity
if sender == hash("/flipper") then
local flipper_velocity = go.get("/flipper", "velocity")
reflection = reflection + flipper_velocity
end
-- Store the collision response
self.collision_response = reflection - self.velocity
end
end
end
The issue is caused by the units used by the physics engine:
Setting your gravity to -900 and your scale to 0.01 will give you a satisfying result:
The ball is now 40 * 0.01 = 0.4 meters in diameter and the gravity is 900 * 0.01 = 9 m/s2.
The reason why there’s a difference between 2D and 3D is that you’re working with completely different scales. In a 3D game you can for instance have things that are 1 unit wide, for instance these dungeon pieces by Kay Lousberg:
When you position those walls and floor pieces you position them 1 unit apart, but when rendered in 3D using a perspective camera they obviously show as a few hundred pixels wide, depending on your camera settings.
In a 2D game you might have tiles in a tilemap that are 64x64 pixels and a player character that is 100x300 pixels. If 1 pixel translates to 1 meter in the physics world you obviously need to apply a scale to get the objects down to a size that the physics engine is designed for.
When I did “physics simulation” with manually moving objects, I found that collision detection was more accurate when using one trigger and one kinematic collision object instead of two collision objects. In this case, I took the collision point and the normal using a raycast.
Thanks I’ll give that a shot. I wish they would update the physics engine to be more responsive. But maybe it’s a bridge too far. I’m not a physics dev so I’m not sure why it’s hard to ensure the collisions are processed in a timely manner.