Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PhysicsServer2/3D::space_step() to step physics simulation manually #76462

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Daylily-Zeleen
Copy link
Contributor

@Daylily-Zeleen Daylily-Zeleen commented Apr 26, 2023

Implement #1373
In #2821, it seems like the topic is quite big, but I think this pr can solve it.

Here is a simple demonstrate video of rollbackable/recordable physics simulation create by this pr:

rollbackable_simulation.mp4

But this pr is not perfect, there has two points which confuse me (I'm not familiar with multi-threads and GodotPhysics), and I mark them at below.

Here is rollback demo:
physics_rollback_test.zip

@Daylily-Zeleen Daylily-Zeleen changed the title Add space_step() to step physics simulate manually Add PhysicsSrver2/3D::space_step() to step physics simulation manually Apr 26, 2023
@Daylily-Zeleen Daylily-Zeleen force-pushed the daylily-zeleen/physics_space_step branch 2 times, most recently from b769061 to 2419c05 Compare April 26, 2023 05:47
void GodotPhysicsServer3D::space_flush_queries(RID p_space) {
// TODO::
// Like _update_shapes(), to provide controllability for developers, flushing_queries flag should active as a member of space and check it for each space.
// But I'm not sure about that, I am not familiar with multi-threads and the architecture of GodotPhysics.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second point which confuse me.

// TODO::
// May be let pending_shape_update_list as a member of GodotSpaces3D and update shapes by themselves.
// To avoid effecting Spces which are handled by developer (for lockstep/rollback netcode, it is particularly sensitive).
// If it is unecessary, call _update_shapes() directly.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first point which confuse me.

@Daylily-Zeleen Daylily-Zeleen force-pushed the daylily-zeleen/physics_space_step branch from 2419c05 to 48553ed Compare April 26, 2023 05:55
@Daylily-Zeleen Daylily-Zeleen marked this pull request as ready for review April 26, 2023 05:56
@Daylily-Zeleen Daylily-Zeleen requested review from a team as code owners April 26, 2023 05:56
@Chaosus Chaosus added this to the 4.1 milestone Apr 26, 2023
@Daylily-Zeleen Daylily-Zeleen force-pushed the daylily-zeleen/physics_space_step branch 2 times, most recently from ffbacd8 to e6573c3 Compare April 28, 2023 08:56
@Lcbx
Copy link
Contributor

Lcbx commented May 14, 2023

Hello @Daylily-Zeleen,
for thread-safety, you might want to call the functions sync() and end_sync() of the PhysicsServer classes
an example of usage is in the function Main::iteration() in file main/main.cpp

Saw your PR when making mine ; yours has more general usage but might need some tweaks.

@Daylily-Zeleen
Copy link
Contributor Author

for thread-safety, you might want to call the functions sync() and end_sync() of the PhysicsServer classes
an example of usage is in the function Main::iteration() in file main/main.cpp

In my opinion, the PhysicsSpace which be stepped by step() is handled by developers, to run in which thread, sync or not, are controled by developers themselves.

Instead of call sync() and end_sync() in step(), expose spcas_sync(RID p_space) and space_end_sync(RID p_space) may be better.

I need people, who familiar with godot physics and server design, to instruct me.

@akien-mga akien-mga changed the title Add PhysicsSrver2/3D::space_step() to step physics simulation manually Add PhysicsServer2/3D::space_step() to step physics simulation manually Jun 19, 2023
@akien-mga akien-mga modified the milestones: 4.1, 4.2 Jun 19, 2023
@Az-qianchen
Copy link

When will this feature be merged? I need this functionality to assist me in implementing my network synchronization.

@AThousandShips
Copy link
Member

AThousandShips commented Sep 13, 2023

@Az-qianchen it hasn't been reviewed yet, so there's no real timeframe for this being merged at the moment, the questions of the OP about some details would need to be resolved as well by someone experienced with the physics

If you can test it and give your results and comments it would help the process

servers/physics_2d/godot_physics_server_2d.cpp Outdated Show resolved Hide resolved
servers/physics_3d/godot_physics_server_3d.cpp Outdated Show resolved Hide resolved
servers/physics_3d/godot_physics_server_3d.cpp Outdated Show resolved Hide resolved
servers/physics_3d/godot_physics_server_3d.cpp Outdated Show resolved Hide resolved
servers/physics_2d/godot_physics_server_2d.cpp Outdated Show resolved Hide resolved
servers/physics_2d/godot_physics_server_2d.cpp Outdated Show resolved Hide resolved
@Az-qianchen
Copy link

2023-09-13.180402.mp4

I am not sure if the way I use is correct, but when I simulation is positive, I can get the correct result. When the value is negative, the result seems to have an error.

@Daylily-Zeleen
Copy link
Contributor Author

Daylily-Zeleen commented Sep 13, 2023

When the value is negative, the result seems to have an error.

How to step a physics space is not this pr's work.

I guess your purpose of passing a negative time to space_step() is to roll the physics space back to the previous state (for example, 0.1 second before).
This cannot be achieved through space_step(), this method is to simulate a physical space forward, you can passing a negative delta time to simulate, but it just simulate forward by a negative delta time.

The right way to implement rollback feature is store your physics objects' state, and just restore them when you want to roll back.
Typically, you should create a independent physics space to realize your physics simulation instead of using default space, then use space_step() to setp this space, to make your physics simulation under your control fully.

@Az-qianchen
Copy link

Az-qianchen commented Sep 14, 2023

you should create an independent physics space to realize

I encountered some obstacles when creating a physical space. Do I need to copy all PhysicsBody3D objects to the new physical space, and all objects must be StaticBody3D to perform space_step() on a single object and obtain the correct collision?
Is there other shortcuts?

@Daylily-Zeleen
Copy link
Contributor Author

Daylily-Zeleen commented Sep 14, 2023

Do I need to copy all PhysicsBody3D objects to the new physical space,

If your physics object are created as a node (CollisionObject2/3D or Joint2/3D) and want to move them from default space to your own space, please use PhysicsServer2/3D.xxx_set_space(), you don't need to copy them.

and all objects must be StaticBody3D to perform space_step() on a single object and obtain the correct collision?

There has not limit the type of physics objet.


I uploaded a testing project for reference.

Here is a rollback demo:
physics_rollback_test.zip

@Az-qianchen
Copy link

There has not limit the type of physics objet.

I may not be very clear. In fact, I hope to make a Step Physics Simulation in a certain object in the scene, but in order to get the correct interaction collision, I seem to need to copy a static scene in the simulation space.

Or turn PhysicsBody into staticbody in the original scene to prevent being simulated together

The following is the code that I tried to transfer the copy to the new physical space, but I still did not think about how to deal with the problem of retaining PhysicsBody and avoiding being simulated together.

class_name SubSpace3D
extends Node

@export var node_mirror: Node

var space_rid: RID
var space_rid_new: RID

func _ready() -> void:
# Body is to obtain the default space RID,maybe there is a better way?
	var Body = StaticBody3D.new()
	add_child(Body)
	space_rid = PhysicsServer3D.body_get_space(Body.get_rid())
	space_rid_new = PhysicsServer3D.space_create()

# Configure is a singleton to facilitate data calls from other locations
	Configure.space_rid = space_rid
	Configure.space_rid_new = space_rid_new

# Copy the basic data of the scene
	create(space_rid, space_rid_new)
	PhysicsServer3D.space_set_active(space_rid_new, true)
	Body.free()

# Nodes that need to be copied
	_update_space(node_mirror)

func _update_space(node:Node):
	var node_array: Array[Node]

	if node is RigidBody3D:
		node_array.push_back(node)

	for child in node.get_children():
		_update_space(child)

	for collisionobject in node_array:
		var node_new := collisionobject.duplicate()
		node.add_child(node_new)
		PhysicsServer3D.body_set_space(node_new.get_rid(),space_rid_new)

func _enter_tree():
	get_tree().connect("node_added",_node_added)
func _exit_tree():
	get_tree().disconnect("node_added",_node_added)

func _node_added(node:Node):
	await ready
	if is_ancestor_of(node) and node is CollisionObject3D:
		PhysicsServer3D.body_set_space(node.get_rid(),space_rid_new)

func set_gravity(space, new_space):
	var gravity: float = PhysicsServer3D.area_get_param(space, PhysicsServer3D.AREA_PARAM_GRAVITY)
	PhysicsServer3D.area_set_param(new_space, PhysicsServer3D.AREA_PARAM_GRAVITY, gravity)

func set_gravity_vector(space, new_space):
	var gravity_vector: Vector3 = PhysicsServer3D.area_get_param(space, PhysicsServer3D.AREA_PARAM_GRAVITY_VECTOR)
	PhysicsServer3D.area_set_param(new_space, PhysicsServer3D.AREA_PARAM_GRAVITY_VECTOR, gravity_vector)

func set_linear_damp(space, new_space):
	var linear_damp: float = PhysicsServer3D.area_get_param(space, PhysicsServer3D.AREA_PARAM_LINEAR_DAMP)
	PhysicsServer3D.area_set_param(new_space, PhysicsServer3D.AREA_PARAM_LINEAR_DAMP, linear_damp)

func set_angular_damp(space, new_space):
	var angular_damp:float = PhysicsServer3D.area_get_param(space, PhysicsServer3D.AREA_PARAM_ANGULAR_DAMP)
	PhysicsServer3D.area_set_param(new_space, PhysicsServer3D.AREA_PARAM_ANGULAR_DAMP, angular_damp)

func get_direct_space_state(_active):
	return PhysicsServer3D.space_get_direct_state(_active)

func create(space, new_space):
	set_gravity(space, new_space)
	set_gravity_vector(space, new_space)
	set_linear_damp(space, new_space)
	set_angular_damp(space, new_space)

@Daylily-Zeleen
Copy link
Contributor Author

@Az-qianchen Sorry, I don't think I can help you solve this specific application scenarios.

After reading your code, I found that you set the space_rid_new to actived. I'm not sure about your situation, but I think I need to clearify something here:
A actived physics space will be stepped automatically by the main loop, if you want to control by yourself completely, you should ensure the physics space is Inactivated.

@akien-mga
Copy link
Member

This is mostly pending on a review by the @godotengine/physics team to assess whether this change is good conceptually, and will work well with the various physics backends (GodotPhysics, Jolt, Rapier, etc.).

And we're in feature freeze for Godot 4.2 so at the earliest this would be merged for 4.3.

@tedbarth
Copy link

I'm working on a networked game in Godot 4.x, which would depend on this PR for client-side prediction, and this doesn't seem like too big a change to the codebase. (144 LOC?)

I'm in the same boat. Currently I am sticking with the MR's source branch built locally.

@monitz87

This comment was marked as off-topic.

@AThousandShips
Copy link
Member

Please don't bump without contributing significant new information. Use the 👍 reaction button on the first post instead.

@Daylily-Zeleen
Copy link
Contributor Author

@monitz87

Why is that? Are there any side effects from using the default space if I set it as inactive beforehand?

I think there have not something can be called "side effects" if you use default space.
"Create a independent physics space" means that all things happen in this space are under your control.
If you using default physics space you may overlook somethings which are brought by engine detail implementation and unexpected. This may not be easy to understand, I can‘t provide specific examples for it.

@Daylily-Zeleen Daylily-Zeleen force-pushed the daylily-zeleen/physics_space_step branch from c90de98 to 70d29a0 Compare December 2, 2023 13:47
@Daylily-Zeleen Daylily-Zeleen force-pushed the daylily-zeleen/physics_space_step branch from 70d29a0 to 7c8abda Compare December 2, 2023 13:50
@RadiantUwU
Copy link
Contributor

Is there anything that's holding this from being merged? I'm really looking forward for using this!!

@AThousandShips
Copy link
Member

It has to be reviewed before it can be merged 🙂

@RadiantUwU
Copy link
Contributor

It has to be reviewed before it can be merged 🙂

Cool.... when is it getting reviewed?

@RadiantUwU
Copy link
Contributor

Tested on Linux/X11 on the latest 4.3 build, works as expected.

@AThousandShips
Copy link
Member

Cool.... when is it getting reviewed?

When someone has the time to do so, please be patient, this is a major feature and it's the holidays 🙂

@nongvantinh
Copy link
Contributor

I just cherry-pick this PR with the latest changes from the master branch at 907db8e, and everything works fine. Could someone from the physics team verify this PR before the 4.3 release? This feature has been eagerly awaited, along with #84665, for all networking games.

@tedbarth
Copy link

tedbarth commented Feb 23, 2024

Here a C# demo of how I implement rollback/replay for my Rollback Netcode. Stepping API seems to work fine! 👍

In this demo the steps to set back the state are hardcoded to 10. In the real code this is determined dynamically from the incoming messages per iteration. This here is just for demonstration and debugging. Any objections? False positive?

public partial class Demo : RigidBody2D {
  private const int Steps = 10;
  private readonly Queue<SimulationState> _history = new Queue<SimulationState>(Steps);
  private Rid _space2d;

  public override void _EnterTree() {
    _space2d = GetViewport().FindWorld2D().Space;
    PhysicsServer2D.SpaceSetActive(_space2d, false);
  }

  public override void _PhysicsProcess(double delta) {
    // For debugging only advance physics when button is pressed
    if (!Input.IsKeyPressed(Key.U)) {
      return;
    }

    // Add entry to history (physics state is that from previous iteration, but delta is the current one.
    // We do this here, for having everything together what is needed in an replay iteration
    Transform2D transform = PhysicsServer2D.BodyGetState(
        GetRid(),
        PhysicsServer2D.BodyState.Transform)
      .AsTransform2D();
    Vector2 linearVelocity = PhysicsServer2D.BodyGetState(
      GetRid(),
      PhysicsServer2D.BodyState.LinearVelocity).AsVector2();
    _history.Enqueue(new SimulationState(delta, transform, linearVelocity));
    if (_history.Count > Steps) {
      _history.Dequeue();
    }

    // Rollback
    if (_history.Count > 0) {
      var simulationState = _history.Peek();
      PhysicsServer2D.BodySetState(
        GetRid(),
        PhysicsServer2D.BodyState.Transform,
        simulationState.Transform2D);
      PhysicsServer2D.BodySetState(
        GetRid(),
        PhysicsServer2D.BodyState.LinearVelocity,
        simulationState.LinearVelocity);
    }

    // Replay
    foreach (SimulationState state in _history) {
      ApplyCentralForce(Vector2.Up * 200f); // Same action in every iteration

      PhysicsServer2D.SpaceFlushQueries(_space2d);
      PhysicsServer2D.SpaceStep(_space2d, (float)state.DeltaToNextIteration);
    }
  }
}

internal class SimulationState(double deltaToNextIteration, Transform2D transform2D, Vector2 linearVelocity) {
  public readonly double DeltaToNextIteration = deltaToNextIteration;
  public readonly Transform2D Transform2D = transform2D;
  public readonly Vector2 LinearVelocity = linearVelocity;
}

@grazianobolla

This comment was marked as off-topic.

@Clonkex
Copy link

Clonkex commented Feb 23, 2024

My review is that this PR has worked perfectly in everything I've done with it in my own networked game. If we're willing to merge a slightly-wonky PR then this one is probably ok as-in, however there remain a couple of small potential issues that I'm aware of:

  • Events may not correctly trigger again when resimulating (i.e. things like area triggers may not work more than once). Untested. I'm sure I saw someone else point this out but I can't seem to find it so maybe it was found to not be the case.
  • I think it should be calling update_shapes directly instead of whatever it's doing now. See my review.

@Gabooot
Copy link

Gabooot commented Feb 25, 2024

Area signals seem to work. See attached demonstration.
area_test (copy).zip

@monitz87
Copy link

monitz87 commented Feb 25, 2024

  • Events may not correctly trigger again when resimulating (i.e. things like area triggers may not work more than once). Untested. I'm sure I saw someone else point this out but I can't seem to find it so maybe it was found to not be the case.

@Clonkex do you mean this comment? Because that is still very much an issue with this implementation. I mean, the implementation itself does what it should do, but it's not enough for resimulation unless we find a way to save/load the state of the physics engine (and nodes such as Area2D which also keep internal state)

@Gabooot
Copy link

Gabooot commented Feb 25, 2024

  • Events may not correctly trigger again when resimulating (i.e. things like area triggers may not work more than once). Untested. I'm sure I saw someone else point this out but I can't seem to find it so maybe it was found to not be the case.

@Clonkex do you mean this comment? Because that is still very much an issue with this implementation. I mean, the implementation itself does what it should do, but it's not enough for resimulation unless we find a way to save/load the state of the physics engine (and nodes such as Area2D which also keep internal state)

Another thing that is missing is a good way to instantly generate collision state. AFAICT, if I manually set the transform of a body (e.g. from an authoritative server) the state of Area3D is inaccurate until I run space_step. After running space_step, physics state should be accurate but there isn't an unproblematic way to run a physics step directly after a manual update.

@tedbarth
Copy link

tedbarth commented Feb 25, 2024

  • Events may not correctly trigger again when resimulating (i.e. things like area triggers may not work more than once). Untested. I'm sure I saw someone else point this out but I can't seem to find it so maybe it was found to not be the case.

@Clonkex do you mean this comment? Because that is still very much an issue with this implementation. I mean, the implementation itself does what it should do, but it's not enough for resimulation unless we find a way to save/load the state of the physics engine (and nodes such as Area2D which also keep internal state)

I just realized that my rollback netcode scenario also suffers from a lack of possibility to reset the internal state: I may register impulses/forces in the game mechanics for one tick and after some Milliseconds, when I normally want to space_step forward the physics, I realize I actually want to reset it instead, to rollback to a state of just arrived (outdated) messages. Of course I don't want to space_step forward (which would clear all demands) while I already know that this state is obsolete and will be replaced with another after inserting the incoming change and replaying to the real now.
If I don't clear the impulses/forces (or what ever is resolved at first when space_step is called) before rolling back the impulses/forces are applied twice: For the space_step right after rollback and the real now space_step after replaying up until the real now.

On an unrelated note: There are still too many TODOs in the merge request's code.

@Clonkex
Copy link

Clonkex commented Feb 26, 2024

  • Events may not correctly trigger again when resimulating (i.e. things like area triggers may not work more than once). Untested. I'm sure I saw someone else point this out but I can't seem to find it so maybe it was found to not be the case.

@Clonkex do you mean this comment? Because that is still very much an issue with this implementation. I mean, the implementation itself does what it should do, but it's not enough for resimulation unless we find a way to save/load the state of the physics engine (and nodes such as Area2D which also keep internal state)

Yes, that's the one!

@Daylily-Zeleen
Copy link
Contributor Author

  • Events may not correctly trigger again when resimulating (i.e. things like area triggers may not work more than once). Untested. I'm sure I saw someone else point this out but I can't seem to find it so maybe it was found to not be the case.

Emit signals /events are implemented in CollisionObject nodes, it should be handled in another pr.

To provide a way to resimulate CollisionObject nodes, I think we can add two virtual method virtual CollisionStateXD CollisionObjectXD::get_state() const; and virtual void CollisionObjectXD::set_state(const Ref<CollisionStateXD>& p_state) const, and override them in child classes.

@hislittlecuzin
Copy link

hislittlecuzin commented Apr 15, 2024

Implement #1373 In #2821, it seems like the topic is quite big, but I think this pr can solve it.

Here is a simple demonstrate video of rollbackable/recordable physics simulation create by this pr:

rollbackable_simulation.mp4
But this pr is not perfect, there has two points which confuse me (I'm not familiar with multi-threads and GodotPhysics), and I mark them at below.

Here is rollback demo: physics_rollback_test.zip

Here is a C# version of the GD script using List<> instead of Godot.Collections.Array<>
https://pastebin.com/uxkxR9Ph
Seems to be working quite well.
If you want a steam key for my commercial game that'll be using this, tell me what your discord is.

I have high hopes now that it's working in my networked project even though my netcode needs touching up.

EDIT:
Ignore the Godot.Collections.Array<> that I didn't convert to List<> haha

@monitz87
Copy link

  • Events may not correctly trigger again when resimulating (i.e. things like area triggers may not work more than once). Untested. I'm sure I saw someone else point this out but I can't seem to find it so maybe it was found to not be the case.

Emit signals /events are implemented in CollisionObject nodes, it should be handled in another pr.

To provide a way to resimulate CollisionObject nodes, I think we can add two virtual method virtual CollisionStateXD CollisionObjectXD::get_state() const; and virtual void CollisionObjectXD::set_state(const Ref<CollisionStateXD>& p_state) const, and override them in child classes.

Assuming these methods would get/set both the internal state of the Node and the related object in the physics server, this would be a good way of handling the matter.

It wouldn't be complete without adding the corresponding PhysicsServer2D low level methods to get/set state that is currently not available (like the list of areas and bodies that entered a specific area for example)

@monitz87
Copy link

Implement #1373 In #2821, it seems like the topic is quite big, but I think this pr can solve it.
Here is a simple demonstrate video of rollbackable/recordable physics simulation create by this pr:
rollbackable_simulation.mp4
But this pr is not perfect, there has two points which confuse me (I'm not familiar with multi-threads and GodotPhysics), and I mark them at below.
Here is rollback demo: physics_rollback_test.zip

Here is a C# version of the GD script using List<> instead of Godot.Collections.Array<> https://pastebin.com/uxkxR9Ph Seems to be working quite well. If you want a steam key for my commercial game that'll be using this, tell me what your discord is.

I have high hopes now that it's working in my networked project even though my netcode needs touching up.

EDIT: Ignore the Godot.Collections.Array<> that I didn't convert to List<> haha

Don't want to beat a dead horse here, but I strongly recommend that you read this comment if you haven't. I'm not sure about your specific netcode implementation, but if you're doing rollback, you're most likely going to have those issues with this PR.

The comment mentions signals, but even if you're not using them, polling for overlapping areas/bodies will still fail to resimulate in certain scenarios. Other things like move_and_slide are also prone to produce different results on resimulation due to these limitations.

Once again, this is not to say that this PR isn't useful, or that it shouldn't be merged. Quite the contrary. It's just not sufficient for "full" rollback (i.e. being able to go back to a previous state and reproducing the exact same results in every scenario)

@hislittlecuzin
Copy link

Don't want to beat a dead horse here, but I strongly recommend that you read this comment if you haven't. I'm not sure about your specific netcode implementation, but if you're doing rollback, you're most likely going to have those issues with this PR.

The comment mentions signals, but even if you're not using them, polling for overlapping areas/bodies will still fail to resimulate in certain scenarios. Other things like move_and_slide are also prone to produce different results on resimulation due to these limitations.

Once again, this is not to say that this PR isn't useful, or that it shouldn't be merged. Quite the contrary. It's just not sufficient for "full" rollback (i.e. being able to go back to a previous state and reproducing the exact same results in every scenario)

huh...
short of writing my own physics engine which I contemplated and deemed not going to happen, maybe someone will come up with a solution.

@monitz87
Copy link

Don't want to beat a dead horse here, but I strongly recommend that you read this comment if you haven't. I'm not sure about your specific netcode implementation, but if you're doing rollback, you're most likely going to have those issues with this PR.
The comment mentions signals, but even if you're not using them, polling for overlapping areas/bodies will still fail to resimulate in certain scenarios. Other things like move_and_slide are also prone to produce different results on resimulation due to these limitations.
Once again, this is not to say that this PR isn't useful, or that it shouldn't be merged. Quite the contrary. It's just not sufficient for "full" rollback (i.e. being able to go back to a previous state and reproducing the exact same results in every scenario)

huh... short of writing my own physics engine which I contemplated and deemed not going to happen, maybe someone will come up with a solution.

Well, if your game is 2D, you can use sg-physics-2d, a GDextension that implements an alternative "physics engine". It uses fixed point math under the hood and it's fully deterministic. I use quotation marks because it doesn't replace Godot physics, but rather offers its own set of nodes and a similar API for collision detection and movement.

You can check it out here if you're interested. The README does a good job of explaining how it works, what it can do and what it can't.

@hislittlecuzin
Copy link

Don't want to beat a dead horse here, but I strongly recommend that you read this comment if you haven't. I'm not sure about your specific netcode implementation, but if you're doing rollback, you're most likely going to have those issues with this PR.
The comment mentions signals, but even if you're not using them, polling for overlapping areas/bodies will still fail to resimulate in certain scenarios. Other things like move_and_slide are also prone to produce different results on resimulation due to these limitations.
Once again, this is not to say that this PR isn't useful, or that it shouldn't be merged. Quite the contrary. It's just not sufficient for "full" rollback (i.e. being able to go back to a previous state and reproducing the exact same results in every scenario)

huh... short of writing my own physics engine which I contemplated and deemed not going to happen, maybe someone will come up with a solution.

Well, if your game is 2D, you can use sg-physics-2d, a GDextension that implements an alternative "physics engine". It uses fixed point math under the hood and it's fully deterministic. I use quotation marks because it doesn't replace Godot physics, but rather offers its own set of nodes and a similar API for collision detection and movement.

You can check it out here if you're interested. The README does a good job of explaining how it works, what it can do and what it can't.

Unfortunately my project is 3D.

Looks like CharacterBody3D does not interface with PhysicsServer3D.BodyGetState(body, (PhysicsServer3D.BodyState)body_state);

I hope this thread has an answer or else I'm going to write some C++ haha.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet