Fish swimming around a planet

Thanks @geminisquishgames . Yes, I am kind of wobbling towards a tiny-water-planet simulation. I do want the fish to swim about, eat and rest, flee and school, etc.

This thread started with the basics, floating and moving forward. I have been learning more with my own experiments and some of what other posters said.

Not sure if using RigidBodies et al. is KISS or not. My thinking is that I can let Godot’s C++ move things around (which has to be faster than gdscript) and only swoop-in now and then to alter the fish’s state now and then.

I’ll keep working and pushing my project and the Gitlab link will remain here for the future.

PS - Do you have any thoughts on the state param to the _integrate_forces func? i.e. a question I asked just a few replies up.

Yes, I can tell you that state is the state of the physics body type you are polling for. When you say, “What is confusing is that RigidBody3D has most of the same methods, so why do we need the state param?” you are kinda right.

The idea is to do your physics, applying torque, force, or anything that moves the Rigid body in _physics_process(). _integrate_forces is sort of done in there for you and is used for more low level control over physics. Stuff you will need later once you understand the Vector math and physics needed for more complex stuff that isn’t right out of the box.

You really don’t need to use _integrate_forces at all because physics are taken care of for you in the _physics_process() method, so I wouldn’t use the _integrate_forces() method but for specific things.

An example of KISS would be to do it all without physics, no bodies, just move objects around with math, and prevent something from moving given it’s distance from something else, not a sphere shape. Then when you realize your limitations, slowly build upon that with experience. Later you’ll know where you need something and where you don’t with time.

Try a small experiment. You have your sphere at world origin (Vector3.ZERO or 0,0,0). Now outside of the sphere, in the World node, make an empty Node3D at origin at the center of the sphere/planet but not a child of it. Put your fish (just a mesh, no phys body needed) in that as a child. Move the fish itself, not the invisible node, up on his local Y axis so it is just above the sphere. Now rotate the empty node fish parent in it’s global axis by hand in the editor. This will move the fish around the sphere and keep his -Y or down vector pointing to it, since the fish is bound to it’s parent rotating. Changing the parent’s rotation makes him move, turn, and strafe… while changing his local Y position changes his distance to the sphere (or rather his parent that is also at origin) .

Of course, this is pretty dumb but if you had several of these instanced, all with their own empty pivot, going in different directions, heights, and speeds, it would simulate the movement of fish around a small planet. Orbital bodies even. it isn’t a physical but a visual simulation. Of course you run into problems where simply going rotation.x += 1 doesn’t behave like proper spherical traversing or orbital movement. For one, it’ll be way to fast the further away it is, so you either have to compensate, use math and distances to design your own collisions and avoidance, or figure that you now need to scrap the idea and use the physics tools that help you do that.

So the idea is simple, kinda stupid, but it just works and should help teach you the math and geometry without the more complex physics mixed in, which is just another layer of vector math on top of the Trig and Geometry. Then when you run into a problem or limitation, that is when you try to learn something that overcomes that problem, or maybe become intuitively inspired to solve it or try something different that might help you build that intuition. Maybe. I feel like you approach problems more experimentally and may not like your hand held as much as you just need someone to explain a thing or two while you experiment. There are a hundred different ways to any one thing, which works best is sometimes moot, what is optimal is usually the main programming problem, but what gets the job done and gets you paid is more the reality… or not paid but finished so you can move on maybe.

1 Like

Thank you Gemini! I have some time and will try this.

Yes, I am an old hand at coding etc. and have been messing with Godot for close to two years now. It’s still funny how many concepts there are (in game dev) where I am reset to a shiny baby newb! :baby:

Your “visual” fish system is well described. I had thought vaguely about doing something like that (or even using path following) but I thought I’d try these new-fangled physics things. It was good enough for Newton :face_with_hand_over_mouth:

There certainly are many ways to do one thing. I always imagine how you’d do it on an classic console. They had very little to work with and no fancy pre-built systems to make development happen faster. But when you need them they are really nice to have.

I had to sandbox experiment a lot with it’s physics. Godot Physics are a bit weird, a lot of people switch to Jolt for 3D. I had to switch to Rapier for 2D as they don’t consider mass at the right time in the physics loop, and it’s taking them a long time to implement features like fluid and decent soft bodies. Good luck and hopefully it all clicks well for you.

1 Like

A progress update and a small tutorial

I have mostly solved my initial problems and will post a short explainer here.

Setup

Node tree
%com is a Marker3D so I can play around with the center of mass. (This can also be done in the Inspector, but it’s hard to use.)
Fish1 is the scene with the fish model. I have positioned it:

  1. Pointing right along the negative Z axis.
  2. With the fish’s head on the origin of the scene.

For the %com to work, do this:

func _ready():
    center_of_mass = %com.position

Forces

Think of a force as a game of pool. The cue-tip always aims at the green ball—the origin.
(Unless you use the second param in the _force arguments, but that’s up to you.)

Thus, to make the fish turn left or right (or up or down) you would angle your cue-stick and impact the red ball. This force imparts torque (fancy rotation) and also a forward movement!

If you hit the red ball (The center of mass) with the cue, then you get only movement, not rotation.

Here is some sample code:

# Swims directly forward
func swim(delta):
	var fwd = -basis.z * delta * speed * mass
	apply_central_force(fwd) # Force applied to origin!

# Swims towards target
func closely_follow_target(target:Vector3):
	var F : Vector3 = target - position
	apply_force(F) # Force applied to origin!

Fins Up!

The big problem I was having was to keep the fish oriented in a gravity sphere (provided by an Area3D). I tried using the *_torque methods but they are too Lovecraftian. I was on the verge of giving up when I discovered the whole pool-cue thing and got the fishes turning without explicit torque methods!

All that to say: I tried using torque to turn the fish so the fin would always point ‘up’ against local gravity. Very random such fail.

However! There is a special func in RigidBody3D called _integrate_forces which is what I am now using. The point of it, afaict, is to let you do hacky bad things which physics would frown upon while not breaking the overall simulation.

So, in that func, I eventually boiled the code down to this:
Node Tree for ref

func _integrate_forces(state: PhysicsDirectBodyState3D) -> void:
	# frame skip so we do not overdo this work
	const fskip1 := 4
	var frames := Engine.get_physics_frames()
	if frames % fskip1 == 0:
		_keep_oriented(state, fskip1)

func _keep_oriented(state:PhysicsDirectBodyState3D, fskip1:int):
	# TODO ERROR: The target vector and up vector can't be parallel to each other.
	var up = -state.total_gravity
	# The up vector can't be zero in looking_at, so make it anything really.
	if up.is_zero_approx():
		up = position - %fishspace.position

	var b := state.transform.basis
	var look := b.looking_at(-basis.z, up)
	state.transform.basis = b.slerp(
		look.orthonormalized(), # solves an error msg in slerp
		state.step * fskip1
	).orthonormalized()

This forces the basis of the RigidBody3D to gradually rotate to look forwards and upwards. The effect is that the fish will roll and keep their fins up!

Conclusion

I’ll keep this post updated as I hack on.
HTH.
:bat:

1 Like

You’re behaving like a child @geminisquishgames. You can just say if you’re unhappy with my help. There’s no need to necro a topic just so you can wrap your disgust for me in fake kindness. You don’t want to conduct yourself in that way – not here, not anywhere.

“RELATED” POST