My objective was to make a ship that is floating on water; walkable on deck; and its motion is based on dynamic water waves. Yet it should be cheap to compute, not taking too much frame-rate.
Breakdown
- I wrote the same wave function used for vertex displacement in ocean shader in GDScript in Ocean.gd file (file dealing with ocean parameters and handling ocean shader as well).
- In boat scene, I added 4 Marker3D nodes to assign the position where I am going to call the wave function.
- In for loop, all those positions are used to get wave height at that point, and the values are used to apply force on the boat RigidBody3D.
- It makes the boat float on water and move with the waves.
- To make performance better and make the ship walkable, the rigid body shape used for boat is a simple shape such as box, placed near the water surface; however, a static body, consisting of actual shape of boat/ship is made the child of this rigid body. So player can walk on static body without making any disturbance to rigid-body calculation which is kept simple by the use of simple box shape. And the static body, being the child of rigid-body moves with it.
- All other things such as forward thrust, left or right movement is achieved by applying force to rigid body.
Full Code
extends RigidBody3D
class_name BoatBody3D
@export var float_force := 1.0
@export var water_drag := 0.05
@export var water_angular_drag := 0.05
@onready var gravity: float = Vector3(0, -9.8, 0)
@onready var probes = $ProbeContainer.get_children()
var submerged := false
var is_docked: bool = false
@onready var ocean : Ocean = get_tree().get_nodes_in_group("ocean")[0]
@export var max_thrust_force: float = 1024
@export var max_steering: float = 128
var steering: float = 0 # steering rudder angle in radians
var thrust_force: float = 0 # forward thrust force in Newtons
# Called when the node enters the scene tree for the first time.
func _ready():
add_to_group("boats")
func _physics_process(delta):
submerged = false
for p in probes:
var depth = ocean.get_water_level(p.global_position) - p.global_position.y
if depth > 0:
submerged = true
apply_force(Vector3.UP * float_force * gravity * depth, p.global_position - global_position)
apply_central_force(-self.global_transform.basis.z.normalized() * Vector3(1, 0, 1) * thrust_force)
apply_torque(Vector3.UP * steering)
##apply_torque(-global_transform.basis.z.normalized() * steering * 0.05) # for sideways motion
reset_forces()
func _integrate_forces(state: PhysicsDirectBodyState3D):
if submerged:
state.linear_velocity *= 1 - water_drag
state.angular_velocity *= 1 - water_angular_drag
func thrust():
if not is_docked:
thrust_force = max_thrust_force
func steer_right():
if not is_docked:
steering = -PI * max_steering
func steer_left():
if not is_docked:
steering = PI * max_steering
func reset_forces():
thrust_force = 0
steering = 0
Thank you for reading <3