I have a physics based dragon flight that behaves correctly except when it descends. While diving or slowing the dragon performs a fast left and right roll that looks like a flip flop. updatePhysics runs on RunService.Heartbeat and updateOrientation runs on RunService.PreRender. Increasing angular damping and lowering responsiveness reduced the amplitude but did not remove the oscillation.
function DragonFlightModule:updateOrientation(camera: Camera)
if not self.attachment or self.state ~= 3 then return end
local camCF = camera.CFrame
local lookDir = camCF.LookVector
local angularVel = self.rootPart.AssemblyAngularVelocity
local turnRate = angularVel:Dot(Vector3.yAxis)
local bankAngle = math.clamp(turnRate * BANKING_FACTOR, -math.rad(45), math.rad(45))
local upVec = camCF.UpVector
local targetCF = CFrame.lookAt(Vector3.zero, lookDir, upVec) * CFrame.Angles(0, 0, bankAngle)
self.alignOrientation.CFrame = targetCF
self.orientationCFrame = targetCF
local speed = self.rootPart.AssemblyLinearVelocity.Magnitude
local speedFactor = math.clamp(speed / self.config.FLY_SPEED, 0.5, 1.5)
self.alignOrientation.Responsiveness = ORIENTATION_RESPONSIVENESS * speedFactor
end
function DragonFlightModule:updatePhysics(deltaTime: number)
if self.state ~= 3 or not self.vectorForce then return end
deltaTime = math.clamp(deltaTime or 0.016, 0.001, 0.1)
local physics = self.config.PHYSICS
local vel = self.rootPart.AssemblyLinearVelocity
local speed = vel.Magnitude
local lookDir = self.rootPart.CFrame.LookVector
local baseSpeed = self.config.FLY_SPEED
if self.sprintTakeoffMode then
self.sprintTakeoffTimer = self.sprintTakeoffTimer + deltaTime
local progress = math.min(self.sprintTakeoffTimer / (physics.SPRINT_TAKEOFF_TIME or 1), 1)
baseSpeed = self.config.FLY_SPEED + (self.config.FAST_FLY_SPEED - self.config.FLY_SPEED) * progress
elseif self.sprinting then
baseSpeed = self.config.FAST_FLY_SPEED
elseif self.gliding then
baseSpeed = self.config.GLIDE_SPEED
elseif self.slowing then
if physics.CAN_HOVER then
baseSpeed = physics.HOVER_SPEED or 10
else
baseSpeed = math.max(self.config.MIN_FLY_SPEED * 0.25, 5)
end
end
local pitchFactor = lookDir.Y
local speedMultiplier = 1.0
if pitchFactor < -0.3 then
speedMultiplier = 1 + math.abs(pitchFactor)
elseif pitchFactor > 0.3 then
speedMultiplier = 1 - pitchFactor * 0.2
end
local targetSpeed = math.clamp(
baseSpeed * speedMultiplier,
self.config.MIN_FLY_SPEED,
self.config.MAX_DIVE_SPEED
)
if self.slowing and physics.CAN_HOVER then
-- hover: keep near-zero velocity (slight momentum preservation)
self.targetVelocity = self.velocitySmooth * (physics.MOMENTUM_PRESERVATION or 0.75) * 0.1
self.targetVelocity = Vector3.new(self.targetVelocity.X, 0, self.targetVelocity.Z)
else
self.targetVelocity = lookDir * targetSpeed
end
if pitchFactor > 0.1 then
local verticalBoost = Vector3.yAxis * pitchFactor * targetSpeed * VERTICAL_THRUST_MULTIPLIER * 0.3
self.targetVelocity = self.targetVelocity + verticalBoost
end
if self.slowing and not physics.CAN_HOVER then
self.targetVelocity = (lookDir * targetSpeed) + Vector3.new(0, - (physics.HOVER_SPEED or 10) * 0.5, 0)
end
self.velocitySmooth = self.velocitySmooth:Lerp(
self.targetVelocity,
math.clamp(VELOCITY_SMOOTHING * deltaTime, 0, 1)
)
local accelRate = physics.ACCELERATION
if self.sprintTakeoffMode or self.sprinting then
accelRate = physics.FAST_ACCELERATION
elseif self.slowing then
if speed > physics.QUICK_STOP_THRESHOLD then
accelRate = physics.EMERGENCY_STOP_DECELERATION
else
accelRate = physics.QUICK_STOP_DECELERATION
end
end
local velError = self.velocitySmooth - vel
local desiredAccel = velError * accelRate * 10
local maxAccel = physics.MAX_ACCEL
if desiredAccel.Magnitude > maxAccel then
desiredAccel = desiredAccel.Unit * maxAccel
end
local accelForce = desiredAccel * self.mass
local liftEff = physics.LIFT_EFFICIENCY
local speedRatio = math.clamp(speed / baseSpeed, 0, 1.5)
local pitchLiftFactor = 1 + math.max(0, -pitchFactor * 0.3)
local dynamicLift = self.mass * GRAVITY_FORCE * liftEff * speedRatio * pitchLiftFactor
local liftDir = Vector3.yAxis
if speed > 1 then
local velNorm = vel.Unit
liftDir = (Vector3.yAxis - velNorm * velNorm:Dot(Vector3.yAxis)).Unit
end
local lift = liftDir * dynamicLift
local dragC = physics.DRAG_COEFFICIENT
local drag = Vector3.zero
if speed > 0.1 then
drag = -vel * (dragC * speed * self.mass)
end
local forwardVel = lookDir * vel:Dot(lookDir)
local lateralVel = vel - forwardVel
local lateralDamping = -lateralVel * self.mass * physics.TURN_RADIUS_MULTIPLIER * 2
local angVel = self.rootPart.AssemblyAngularVelocity
local turnPenalty = 0
if angVel.Magnitude > 0.5 then
turnPenalty = angVel.Magnitude * physics.TURN_SPEED_PENALTY
local turnDrag = -vel * (turnPenalty * self.mass * 0.02)
drag = drag + turnDrag
end
local stability = (self.targetVelocity - vel) * self.mass * 0.5
local totalForce = accelForce + lift + drag + lateralDamping + stability
local maxForceMag = self.mass * physics.MAX_FORCE_MAG
if totalForce.Magnitude > maxForceMag then
totalForce = totalForce.Unit * maxForceMag
end
if totalForce ~= totalForce or totalForce.Magnitude > 1e8 then
totalForce = Vector3.new(0, self.mass * GRAVITY_FORCE, 0)
end
self.vectorForce.Force = totalForce
local angDamp = physics.ANGULAR_DAMPING
local alpha = math.clamp(angDamp * deltaTime, 0, 0.5)
self.rootPart.AssemblyAngularVelocity = angVel:Lerp(Vector3.zero, alpha)
local sinceTakeoff = tick() - (self._lastTakeoffTime or 0)
if sinceTakeoff > (TUNABLES.TAKEOFF_GRACE_TIME or 3) then
local origin = self.rootPart.Position
local direction = Vector3.new(0, -1, 0) * (TUNABLES.RAYCAST_DISTANCE or 8)
local params = RaycastParams.new()
params.FilterDescendantsInstances = { self.dragon }
params.FilterType = Enum.RaycastFilterType.Exclude
local result = Workspace:Raycast(origin, direction, params)
if result and result.Instance then
self:stopFlight()
end
end
end
First forum post🙃