Skip to content

Commit 69fbb2c

Browse files
authored
feat: sdk physics impulse and force (#7044)
# Pull Request Description [protocol PR](decentraland/protocol#363) [js-toolchain PR](decentraland/js-sdk-toolchain#1338) [sdk-test-scene PR](decentraland/sdk7-test-scenes#49) this PR implements Explorer part of Impulse/Force applied by the SDK scene [ref how-to wiki](https://github.com/decentraland/unity-explorer/blob/dev/docs/how-to-implement-new-sdk-components.md) ## What does this PR change? - add component handling logic - extract debug from the core logic system for char velocity - expose new settings to the char controller ## Test Instructions [QA epic](#7262) [sdk-test-scene PR](decentraland/sdk7-test-scenes#49) to test this feature go to `flutterecho.dcl.eth` where there are different zones for applying force/impulse
1 parent cdd2468 commit 69fbb2c

45 files changed

Lines changed: 670 additions & 174 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Explorer/Assets/DCL/Character/CharacterMotion/Animation/AnimationStatesLogic.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@ public static void Execute(float dt,
2323
in GlideState glideState)
2424
{
2525
bool isGrounded = rigidTransform is { IsGrounded: true, IsOnASteepSlope: false } || rigidTransform.IsStuck;
26-
2726
Vector3 velocity = rigidTransform.MoveVelocity.Velocity;
28-
float verticalVelocity = rigidTransform.GravityVelocity.y + velocity.y;
27+
float verticalVelocity = rigidTransform.GravityVelocity.y + velocity.y + rigidTransform.ExternalVelocity.y;
2928

3029
bool jumpTriggered = jumpState.JumpCount > animationComponent.States.JumpCount;
3130
bool glidingTriggered = glideState.Value == GlideStateValue.OPENING_PROP && animationComponent.States.GlideState != GlideStateValue.OPENING_PROP;

Explorer/Assets/DCL/Character/CharacterMotion/Components/CharacterRigidTransform.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using UnityEngine;
1+
using UnityEngine;
22

33
namespace DCL.CharacterMotion.Components
44
{
@@ -48,6 +48,20 @@ public class CharacterRigidTransform
4848
// This flag is set when the rigidTransform is between 2 slopes
4949
public bool IsStuck;
5050

51+
// Net external force acting on the character (persistent while source is active, managed by the writer's lifecycle)
52+
public Vector3 ExternalForce;
53+
54+
// Computed acceleration from ExternalForce (a = F / mass), per-frame
55+
public Vector3 ExternalAcceleration;
56+
57+
// Pending external impulse to apply this frame (cleared after applying)
58+
// Impulse = instant velocity change: Δv = J / mass
59+
public Vector3 ExternalImpulse;
60+
61+
// External velocity from impulses, forces (jump pads, knockbacks, wind, etc.)
62+
// Decays over time via drag/friction
63+
public Vector3 ExternalVelocity;
64+
5165
public struct MovementVelocity
5266
{
5367
// Current sideways velocity

Explorer/Assets/DCL/Character/CharacterMotion/Settings/CharacterControllerSettings.asset

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,7 @@ MonoBehaviour:
172172
<HeadIKRemotePlayersDistance>k__BackingField: 10
173173
<JumpPadForce>k__BackingField: 50
174174
<AnimationSpeed>k__BackingField: 1
175+
<CharacterMass>k__BackingField: 1
176+
<ExternalEnvDrag>k__BackingField: 1.5
177+
<ExternalGroundFriction>k__BackingField: 4
178+
<MaxExternalVelocity>k__BackingField: 50

Explorer/Assets/DCL/Character/CharacterMotion/Settings/CharacterControllerSettings.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using UnityEngine;
1+
using UnityEngine;
22
using UnityEngine.Serialization;
33

44
namespace DCL.CharacterMotion.Settings
@@ -108,5 +108,11 @@ public class CharacterControllerSettings : ScriptableObject, ICharacterControlle
108108
[field: SerializeField] public float HeadIKRemotePlayersDistance { get; set; } = 10;
109109
[field: SerializeField] [field: Header("Cheat/Debug/Misc")] public float JumpPadForce { get; private set; } = 50f;
110110
[field: SerializeField] public float AnimationSpeed { get; private set; } = 1;
111+
112+
[field: Header("Impulse / External Velocity")]
113+
[field: SerializeField] public float CharacterMass { get; set; } = 1f;
114+
[field: SerializeField] public float ExternalEnvDrag { get; set; } = 0.5f;
115+
[field: SerializeField] public float ExternalGroundFriction { get; set; } = 4f;
116+
[field: SerializeField] public float MaxExternalVelocity { get; set; } = 50f;
111117
}
112118
}

Explorer/Assets/DCL/Character/CharacterMotion/Settings/ICharacterControllerSettings.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,5 +94,11 @@ public interface ICharacterControllerSettings
9494
float WallSlideDetectionDistance { get; }
9595
float WallSlideMaxMoveSpeedMultiplier { get; }
9696
float StepOffset { get; set; }
97+
98+
// Impulse / External Velocity settings
99+
float CharacterMass { get; set; }
100+
float ExternalEnvDrag { get; set; }
101+
float ExternalGroundFriction { get; set; }
102+
float MaxExternalVelocity { get; set; }
97103
}
98104
}

Explorer/Assets/DCL/Character/CharacterMotion/Settings/OverridableCharacterControllerSettings.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using DCL.SDKComponents.AvatarLocomotion.Components;
1+
using DCL.SDKComponents.AvatarLocomotion.Components;
22
using DCL.SDKComponents.AvatarLocomotion.Systems;
33
using UnityEngine;
44

@@ -366,6 +366,30 @@ public float StepOffset
366366
set => impl.StepOffset = value;
367367
}
368368

369+
public float CharacterMass
370+
{
371+
get => impl.CharacterMass;
372+
set => impl.CharacterMass = value;
373+
}
374+
375+
public float ExternalEnvDrag
376+
{
377+
get => impl.ExternalEnvDrag;
378+
set => impl.ExternalEnvDrag = value;
379+
}
380+
381+
public float ExternalGroundFriction
382+
{
383+
get => impl.ExternalGroundFriction;
384+
set => impl.ExternalGroundFriction = value;
385+
}
386+
387+
public float MaxExternalVelocity
388+
{
389+
get => impl.MaxExternalVelocity;
390+
set => impl.MaxExternalVelocity = value;
391+
}
392+
369393
public float HeadIKWeightChangeSpeed
370394
{
371395
get => impl.HeadIKWeightChangeSpeed;
Lines changed: 19 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
1-
using Arch.Core;
1+
using Arch.Core;
22
using Arch.System;
33
using Arch.SystemGroups;
44
using Arch.SystemGroups.DefaultSystemGroups;
5+
using DCL.AvatarRendering.DemoScripts.Components;
56
using DCL.Character.CharacterMotion.Components;
67
using DCL.Character.CharacterMotion.Velocity;
78
using DCL.CharacterCamera;
9+
using DCL.CharacterMotion;
810
using DCL.CharacterMotion.Components;
911
using DCL.CharacterMotion.Settings;
10-
using DCL.DebugUtilities;
11-
using DCL.DebugUtilities.UIBindings;
1212
using DCL.Time.Systems;
1313
using ECS.Abstract;
1414
using ECS.LifeCycle.Components;
1515
using UnityEngine;
16-
using DCL.AvatarRendering.DemoScripts.Components;
1716
using DCL.CharacterMotion.Utils;
1817

19-
namespace DCL.CharacterMotion.Systems
18+
namespace DCL.Character.CharacterMotion.Systems
2019
{
2120
/// <summary>
2221
/// Entry point to calculate everything that affects character's velocity
@@ -27,107 +26,17 @@ public partial class CalculateCharacterVelocitySystem : BaseUnityLoopSystem
2726
{
2827
private SingleInstanceEntity camera;
2928
private SingleInstanceEntity fixedTick;
30-
private SingleInstanceEntity entitySettings;
31-
32-
private readonly ElementBinding<float> cameraRunFov = new (0);
33-
private readonly ElementBinding<float> walkSpeed = new (0);
34-
private readonly ElementBinding<float> jogSpeed = new (0);
35-
private readonly ElementBinding<float> runSpeed = new (0);
36-
private readonly ElementBinding<float> jogJumpHeight = new (0);
37-
private readonly ElementBinding<float> runJumpHeight = new (0);
38-
private readonly ElementBinding<float> jumpHold = new (0);
39-
private readonly ElementBinding<float> jumpHoldGravity = new (0);
40-
private readonly ElementBinding<float> gravity = new (0);
41-
private readonly ElementBinding<float> airAcc = new (0);
42-
private readonly ElementBinding<float> maxAirAcc = new (0);
43-
private readonly ElementBinding<float> airDrag = new (0);
44-
private readonly ElementBinding<float> stopTime = new (0);
45-
46-
private readonly ElementBinding<int> airJumpCount = new (0);
47-
private readonly ElementBinding<float> airJumpHeight = new (0);
48-
private readonly ElementBinding<float> airJumpDelay = new (0);
49-
private readonly ElementBinding<float> airJumpGravityDuringDelay = new (0);
50-
private readonly ElementBinding<float> cooldownBetweenJumps = new (0);
51-
private readonly ElementBinding<float> airJumpImpulse = new (0);
52-
53-
public CalculateCharacterVelocitySystem(World world, IDebugContainerBuilder debugBuilder) : base(world)
54-
{
55-
debugBuilder.TryAddWidget("Locomotion: Base")?
56-
.AddFloatField("Camera Run FOV", cameraRunFov)
57-
.AddFloatField("Walk Speed", walkSpeed)
58-
.AddFloatField("Jog Speed", jogSpeed)
59-
.AddFloatField("Run Speed", runSpeed)
60-
.AddFloatField("Jog Jump Height", jogJumpHeight)
61-
.AddFloatField("Run Jump Height", runJumpHeight)
62-
.AddFloatField("Jump Hold Time", jumpHold)
63-
.AddFloatField("Jump Hold Gravity Scale", jumpHoldGravity)
64-
.AddFloatField("Gravity", gravity)
65-
.AddFloatField("Air Acceleration", airAcc)
66-
.AddFloatField("Max Air Acceleration", maxAirAcc)
67-
.AddFloatField("Air Drag", airDrag)
68-
.AddFloatField("Grounded Stop Time", stopTime);
69-
70-
debugBuilder.TryAddWidget("Locomotion: Air Jumping")?
71-
.AddControl( new DebugConstLabelDef("Air Jump Count"), new DebugIntFieldDef(airJumpCount) )
72-
.AddFloatField("Height", airJumpHeight)
73-
.AddFloatField("Delay", airJumpDelay)
74-
.AddFloatField("Gravity during Delay", airJumpGravityDuringDelay)
75-
.AddFloatField("Cooldown", cooldownBetweenJumps)
76-
.AddFloatField("Direction Change Impulse", airJumpImpulse);
77-
}
29+
30+
public CalculateCharacterVelocitySystem(World world) : base(world) { }
7831

7932
public override void Initialize()
8033
{
8134
camera = World.CacheCamera();
8235
fixedTick = World.CachePhysicsTick();
83-
entitySettings = World.CacheCharacterSettings();
84-
85-
ICharacterControllerSettings settings = entitySettings.GetCharacterSettings(World);
86-
cameraRunFov.Value = settings.CameraFOVWhileRunning;
87-
walkSpeed.Value = settings.WalkSpeed;
88-
jogSpeed.Value = settings.JogSpeed;
89-
runSpeed.Value = settings.RunSpeed;
90-
jogJumpHeight.Value = settings.JogJumpHeight;
91-
runJumpHeight.Value = settings.RunJumpHeight;
92-
jumpHold.Value = settings.LongJumpTime;
93-
jumpHoldGravity.Value = settings.LongJumpGravityScale;
94-
gravity.Value = settings.Gravity;
95-
airAcc.Value = settings.AirAcceleration;
96-
maxAirAcc.Value = settings.MaxAirAcceleration;
97-
airDrag.Value = settings.AirDrag;
98-
stopTime.Value = settings.StopTimeSec;
99-
airJumpCount.Value = settings.AirJumpCount;
100-
airJumpHeight.Value = settings.AirJumpHeight;
101-
airJumpDelay.Value = settings.AirJumpDelay;
102-
airJumpGravityDuringDelay.Value = settings.AirJumpGravityDuringDelay;
103-
cooldownBetweenJumps.Value = settings.CooldownBetweenJumps;
104-
airJumpImpulse.Value = settings.AirJumpDirectionChangeImpulse;
10536
}
10637

10738
protected override void Update(float t)
10839
{
109-
ICharacterControllerSettings settings = entitySettings.GetCharacterSettings(World);
110-
111-
settings.CameraFOVWhileRunning = cameraRunFov.Value;
112-
settings.WalkSpeed = walkSpeed.Value;
113-
settings.JogSpeed = jogSpeed.Value;
114-
settings.RunSpeed = runSpeed.Value;
115-
settings.JogJumpHeight = jogJumpHeight.Value;
116-
settings.RunJumpHeight = runJumpHeight.Value;
117-
settings.LongJumpTime = jumpHold.Value;
118-
settings.LongJumpGravityScale = jumpHoldGravity.Value;
119-
settings.Gravity = gravity.Value;
120-
settings.AirAcceleration = airAcc.Value;
121-
settings.MaxAirAcceleration = maxAirAcc.Value;
122-
settings.AirDrag = airDrag.Value;
123-
settings.StopTimeSec = stopTime.Value;
124-
settings.AirJumpCount = airJumpCount.Value;
125-
settings.AirJumpHeight = airJumpHeight.Value;
126-
settings.AirJumpDelay = airJumpDelay.Value;
127-
settings.AirJumpGravityDuringDelay = airJumpGravityDuringDelay.Value;
128-
settings.CooldownBetweenJumps = cooldownBetweenJumps.Value;
129-
settings.AirJumpDirectionChangeImpulse = airJumpImpulse.Value;
130-
13140
ResolveVelocityQuery(World, t, fixedTick.GetPhysicsTickComponent(World).Tick, in camera.GetCameraComponent(World));
13241
ResolveRandomAvatarVelocityQuery(World, t, fixedTick.GetPhysicsTickComponent(World).Tick, in camera.GetCameraComponent(World));
13342
}
@@ -213,19 +122,29 @@ private void ResolveAvatarVelocity([Data] float dt,
213122
// Apply velocity multiplier based on walls
214123
ApplyWallSlide.Execute(ref rigidTransform, characterController, in settings);
215124

216-
// Apply vertical velocity
125+
// External forces must run before gravity so ExternalAcceleration.y is available
126+
ApplyExternalForce.Execute(settings, ref rigidTransform, dt);
127+
128+
// Vertical velocity (jump + gravity with effective gravity from external forces)
217129
ApplyJump.Execute(settings, ref rigidTransform, ref jumpState, ref jumpInput, in movementInput, viewerForward, viewerRight, physicsTick, dt);
218130
ApplyGravity.Execute(settings, ref rigidTransform, jumpState, in jumpInput, physicsTick, dt);
219131
ApplyGliding.Execute(settings, in rigidTransform, jumpState, in jumpInput, ref glideState, physicsTick, dt);
220132

221-
ApplyAirDrag.Execute(settings, ref rigidTransform, dt);
133+
// External impulses must run after gravity so it nullify gravity velocity.y
134+
ApplyExternalImpulse.Execute(settings, ref rigidTransform);
135+
136+
// Drag
137+
ApplyHorizontalAirDrag.Execute(settings, ref rigidTransform, dt);
138+
ApplyExternalVelocityDragAndClamp.Execute(settings, ref rigidTransform, dt);
222139

140+
// Rotation
223141
if (cameraComponent.Mode == CameraMode.FirstPerson)
224142
ApplyFirstPersonRotation.Execute(ref rigidTransform, in cameraComponent);
225143
else
226144
ApplyThirdPersonRotation.Execute(ref rigidTransform, in movementInput);
227145

228-
ApplyConditionalRotation.Execute(ref rigidTransform, in settings);
146+
if (settings.EnableCharacterRotationBySlope)
147+
ApplySlopeConditionalRotation.Execute(ref rigidTransform, in settings);
229148
}
230149
}
231150
}

Explorer/Assets/DCL/Character/CharacterMotion/Systems/CalculateSpeedLimitSystem.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,12 @@
33
using Arch.SystemGroups;
44
using Arch.SystemGroups.DefaultSystemGroups;
55
using DCL.AvatarRendering.DemoScripts.Components;
6-
using DCL.Character.CharacterMotion.Components;
7-
using DCL.CharacterCamera;
6+
using DCL.Character.CharacterMotion.Systems;
87
using DCL.CharacterMotion.Components;
98
using DCL.CharacterMotion.Settings;
109
using DCL.CharacterMotion.Utils;
1110
using DCL.Time.Systems;
1211
using ECS.Abstract;
13-
using ECS.LifeCycle.Components;
14-
using System;
15-
using UnityEngine;
1612

1713
namespace DCL.CharacterMotion.Systems
1814
{

0 commit comments

Comments
 (0)