Skip to content

Commit 10b84af

Browse files
lawnjellyCalinou
authored andcommitted
Add 3D physics interpolation demo
Demonstrate first person shooter and third person shooter cameras.
1 parent 0d6b772 commit 10b84af

18 files changed

+717
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8

3d/physics_interpolation/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Physics Interpolation
2+
3+
This demo showcases [physics interpolation](https://docs.godotengine.org/en/stable/tutorials/physics/interpolation/index.html)
4+
in 3D with varying camera modes (first person, third person, fixed perspective).
5+
This is also known as *fixed timestep interpolation*.
6+
7+
Physics interpolation makes motion appear smooth regardless of the rendered
8+
framerate and physics ticks per second configured in the Project Settings.
9+
There are some caveats related to its usage though, such as increased latency
10+
and potential issues with teleporting objects. It's recommended to go
11+
through the
12+
[documentation](https://docs.godotengine.org/en/stable/tutorials/physics/interpolation/physics_interpolation_introduction.html)
13+
when enabling physics interpolation in a project.
14+
15+
Physics interpolation is enabled by default in this project. Press <kbd>T</kbd>
16+
to toggle it while the demo is running. This allows you to see the impact on
17+
smoothness of physics interpolation.
18+
19+
Language: GDScript
20+
21+
Renderer: Compatibility
22+
23+
## Screenshots
24+
25+
![Screenshot](screenshots/physics_interpolation.webp)

3d/physics_interpolation/box.tscn

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[gd_scene load_steps=4 format=3 uid="uid://cvgxw8nq67xxm"]
2+
3+
[sub_resource type="BoxShape3D" id="BoxShape3D_pq8q7"]
4+
5+
[sub_resource type="BoxMesh" id="BoxMesh_pyidc"]
6+
7+
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_oq5cr"]
8+
albedo_color = Color(0.5544661, 0.39379695, 0.15444939, 1)
9+
10+
[node name="Box" type="RigidBody3D"]
11+
mass = 10.0
12+
13+
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
14+
shape = SubResource("BoxShape3D_pq8q7")
15+
16+
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
17+
mesh = SubResource("BoxMesh_pyidc")
18+
surface_material_override/0 = SubResource("StandardMaterial3D_oq5cr")

3d/physics_interpolation/bullet.gd

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
extends RigidBody3D
2+
3+
const APPEARANCE_LIFE = 1.0
4+
const MAX_LIFE = 100.0
5+
6+
var _life := 0.0
7+
var _enabled := false
8+
9+
10+
func _ready() -> void:
11+
$CollisionShape3D.disabled = true
12+
13+
14+
func _physics_process(_delta: float) -> void:
15+
if not _enabled:
16+
$CollisionShape3D.disabled = false
17+
_enabled = true
18+
19+
20+
_life += 1
21+
22+
var life_left := MAX_LIFE - _life
23+
24+
var appearance_fract := minf(float (_life) / float (APPEARANCE_LIFE), 1.0)
25+
var fract := float(life_left) / float(MAX_LIFE)
26+
fract *= appearance_fract
27+
28+
fract = maxf(fract, 0.0001)
29+
30+
$Scaler.scale = Vector3.ONE * fract
31+
32+
if _life >= MAX_LIFE:
33+
queue_free()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://dohdjr8ilotkr

3d/physics_interpolation/bullet.tscn

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
[gd_scene load_steps=14 format=3 uid="uid://2r5wc1n0ybju"]
2+
3+
[ext_resource type="Script" uid="uid://dohdjr8ilotkr" path="res://bullet.gd" id="1_v7oki"]
4+
[ext_resource type="Texture2D" uid="uid://b70s43gn8c3kx" path="res://spark_particle.png" id="2_v8qja"]
5+
6+
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_v8qja"]
7+
bounce = 0.61
8+
9+
[sub_resource type="SphereShape3D" id="SphereShape3D_rtl8c"]
10+
radius = 0.2
11+
12+
[sub_resource type="Gradient" id="Gradient_v8qja"]
13+
colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0)
14+
15+
[sub_resource type="GradientTexture1D" id="GradientTexture1D_t4vbm"]
16+
gradient = SubResource("Gradient_v8qja")
17+
18+
[sub_resource type="Curve" id="Curve_v8qja"]
19+
_data = [Vector2(0, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
20+
point_count = 2
21+
22+
[sub_resource type="CurveTexture" id="CurveTexture_t4vbm"]
23+
curve = SubResource("Curve_v8qja")
24+
25+
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_v7oki"]
26+
direction = Vector3(0, 1, 0)
27+
initial_velocity_max = 1.0
28+
gravity = Vector3(0, 1, 0)
29+
scale_min = 0.19999999
30+
scale_curve = SubResource("CurveTexture_t4vbm")
31+
color_ramp = SubResource("GradientTexture1D_t4vbm")
32+
33+
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_t4vbm"]
34+
transparency = 1
35+
blend_mode = 1
36+
shading_mode = 0
37+
vertex_color_use_as_albedo = true
38+
albedo_color = Color(2.3357503, 3.2944162, 3.2944162, 1)
39+
albedo_texture = ExtResource("2_v8qja")
40+
texture_filter = 5
41+
42+
[sub_resource type="QuadMesh" id="QuadMesh_rtl8c"]
43+
material = SubResource("StandardMaterial3D_t4vbm")
44+
size = Vector2(0.2, 0.2)
45+
46+
[sub_resource type="SphereMesh" id="SphereMesh_v7oki"]
47+
radial_segments = 24
48+
rings = 12
49+
50+
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_v7oki"]
51+
albedo_color = Color(0.43164876, 0.5731902, 0.5013925, 1)
52+
metallic = 1.0
53+
54+
[node name="Bullet" type="RigidBody3D"]
55+
collision_layer = 2
56+
physics_material_override = SubResource("PhysicsMaterial_v8qja")
57+
script = ExtResource("1_v7oki")
58+
59+
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
60+
shape = SubResource("SphereShape3D_rtl8c")
61+
62+
[node name="GPUParticles3D" type="GPUParticles3D" parent="."]
63+
amount = 64
64+
transform_align = 1
65+
process_material = SubResource("ParticleProcessMaterial_v7oki")
66+
draw_pass_1 = SubResource("QuadMesh_rtl8c")
67+
68+
[node name="Scaler" type="Node3D" parent="."]
69+
transform = Transform3D(0.001, 0, 0, 0, 0.001, 0, 0, 0, 0.001, 0, 0, 0)
70+
71+
[node name="MeshInstance3D" type="MeshInstance3D" parent="Scaler"]
72+
transform = Transform3D(0.4, 0, 0, 0, 0.4, 0, 0, 0, 0.4, 0, 0, 0)
73+
mesh = SubResource("SphereMesh_v7oki")
74+
skeleton = NodePath("../..")
75+
surface_material_override/0 = SubResource("StandardMaterial3D_v7oki")
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
[remap]
2+
3+
importer="texture"
4+
type="CompressedTexture2D"
5+
uid="uid://c5avn6uhdr88"
6+
path.s3tc="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.s3tc.ctex"
7+
metadata={
8+
"imported_formats": ["s3tc_bptc"],
9+
"vram_texture": true
10+
}
11+
12+
[deps]
13+
14+
source_file="res://icon.png"
15+
dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.s3tc.ctex"]
16+
17+
[params]
18+
19+
compress/mode=2
20+
compress/high_quality=false
21+
compress/lossy_quality=0.7
22+
compress/uastc_level=0
23+
compress/rdo_quality_loss=0.0
24+
compress/hdr_compression=1
25+
compress/normal_map=0
26+
compress/channel_pack=0
27+
mipmaps/generate=true
28+
mipmaps/limit=-1
29+
roughness/mode=0
30+
roughness/src_normal=""
31+
process/channel_remap/red=0
32+
process/channel_remap/green=1
33+
process/channel_remap/blue=2
34+
process/channel_remap/alpha=3
35+
process/fix_alpha_border=true
36+
process/premult_alpha=false
37+
process/normal_map_invert_y=false
38+
process/hdr_as_srgb=false
39+
process/hdr_clamp_exposure=false
40+
process/size_limit=0
41+
detect_3d/compress_to=0

3d/physics_interpolation/icon.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
[remap]
2+
3+
importer="texture"
4+
type="CompressedTexture2D"
5+
uid="uid://cb05v27t8vx5t"
6+
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
7+
metadata={
8+
"vram_texture": false
9+
}
10+
11+
[deps]
12+
13+
source_file="res://icon.svg"
14+
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
15+
16+
[params]
17+
18+
compress/mode=0
19+
compress/high_quality=false
20+
compress/lossy_quality=0.7
21+
compress/uastc_level=0
22+
compress/rdo_quality_loss=0.0
23+
compress/hdr_compression=1
24+
compress/normal_map=0
25+
compress/channel_pack=0
26+
mipmaps/generate=false
27+
mipmaps/limit=-1
28+
roughness/mode=0
29+
roughness/src_normal=""
30+
process/channel_remap/red=0
31+
process/channel_remap/green=1
32+
process/channel_remap/blue=2
33+
process/channel_remap/alpha=3
34+
process/fix_alpha_border=true
35+
process/premult_alpha=false
36+
process/normal_map_invert_y=false
37+
process/hdr_as_srgb=false
38+
process/hdr_clamp_exposure=false
39+
process/size_limit=0
40+
detect_3d/compress_to=0
41+
svg/scale=1.0
42+
editor/scale_with_editor_scale=false
43+
editor/convert_colors_with_editor_theme=false

3d/physics_interpolation/player.gd

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
extends CharacterBody3D
2+
3+
const MOUSE_SENSITIVITY = 2.5
4+
const CAMERA_SMOOTH_SPEED = 10.0
5+
const MOVE_SPEED = 3.0
6+
const FRICTION = 10.0
7+
const JUMP_VELOCITY = 8.0
8+
const BULLET_SPEED = 9.0
9+
10+
var _yaw := 0.0
11+
var _pitch := 0.0
12+
var _dir := Vector3(sin(_yaw), 0, cos(_yaw))
13+
var _prox := 3
14+
15+
var _tps_look_from := Vector3()
16+
17+
enum CameraType {
18+
CAM_FIXED,
19+
CAM_FPS,
20+
CAM_TPS,
21+
}
22+
23+
var _bullet_scene: PackedScene = load("res://bullet.tscn")
24+
var _cam_type := CameraType.CAM_FIXED
25+
26+
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
27+
28+
29+
func _ready() -> void:
30+
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
31+
$Rig/Camera_TPS.top_level = true
32+
cycle_camera_type()
33+
34+
35+
func _input(event: InputEvent) -> void:
36+
if event is InputEventMouseMotion:
37+
_yaw -= event.screen_relative.x * MOUSE_SENSITIVITY * 0.001
38+
_pitch += event.screen_relative.y * MOUSE_SENSITIVITY * 0.002
39+
_pitch = clamp(_pitch, -PI, PI)
40+
$Rig.rotation = Vector3(0, _yaw, 0)
41+
42+
43+
func _update_camera(delta: float) -> void:
44+
_dir.x = sin(_yaw)
45+
_dir.z = cos(_yaw)
46+
47+
$Rig/Head.rotation = Vector3(_pitch * -0.5, 0, 0)
48+
49+
match _cam_type:
50+
CameraType.CAM_TPS:
51+
var target: Vector3 = $Rig/Head.get_global_transform_interpolated().origin
52+
var pos := target
53+
pos.x += _dir.x * _prox
54+
pos.z += _dir.z * _prox
55+
pos.y += 2.0 + (_pitch * (0.2 * _prox))
56+
57+
var offset: Vector3 = pos - _tps_look_from
58+
var l: float = offset.length()
59+
60+
var tps_cam_speed: float = CAMERA_SMOOTH_SPEED * delta
61+
if (l > tps_cam_speed):
62+
offset *= tps_cam_speed / l
63+
_tps_look_from += offset
64+
65+
$Rig/Camera_TPS.look_at_from_position(_tps_look_from, target, Vector3(0, 1, 0))
66+
67+
68+
func cycle_camera_type() -> void:
69+
match _cam_type:
70+
CameraType.CAM_FIXED:
71+
_cam_type = CameraType.CAM_FPS
72+
$Rig/Head/Camera_FPS.make_current()
73+
CameraType.CAM_FPS:
74+
_cam_type = CameraType.CAM_TPS
75+
$Rig/Camera_TPS.make_current()
76+
CameraType.CAM_TPS:
77+
_cam_type = CameraType.CAM_FIXED
78+
get_node("../Camera_Fixed").make_current()
79+
80+
# Hide body in FPS view (but keep shadow casting to improve spatial awareness).
81+
if _cam_type == CameraType.CAM_FPS:
82+
$Rig/Mesh_Body.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_SHADOWS_ONLY
83+
else:
84+
$Rig/Mesh_Body.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_ON
85+
86+
87+
func _process(delta: float) -> void:
88+
if Input.is_action_just_pressed(&"cycle_camera_type"):
89+
cycle_camera_type()
90+
91+
if Input.is_action_just_pressed(&"toggle_physics_interpolation"):
92+
get_tree().physics_interpolation = not get_tree().physics_interpolation
93+
94+
if Input.is_action_just_pressed(&"fire"):
95+
var bullet: RigidBody3D = _bullet_scene.instantiate()
96+
var transform_3d: Transform3D = $Rig/Head/Fire_Origin.get_global_transform_interpolated()
97+
bullet.position = transform_3d.origin
98+
var bul_dir: Vector3 = transform_3d.basis[2].normalized()
99+
bullet.linear_velocity = bul_dir * -BULLET_SPEED
100+
get_parent().add_child(bullet)
101+
bullet.reset_physics_interpolation()
102+
103+
bullet.position -= bul_dir * (1.0 - Engine.get_physics_interpolation_fraction())
104+
105+
106+
# If we pressed reset, or too far from the origin... move back to origin.
107+
if Input.is_action_just_pressed(&"reset_position") or position.length() > 10.0:
108+
position = Vector3(0, 1, 0)
109+
velocity = Vector3()
110+
reset_physics_interpolation()
111+
_yaw = 0.0
112+
_pitch = 0.0
113+
$Rig.rotation = Vector3(0, _yaw, 0)
114+
115+
if Input.is_action_just_pressed(&"jump") and is_on_floor():
116+
velocity.y += JUMP_VELOCITY
117+
118+
_update_camera(delta)
119+
120+
121+
func _physics_process(delta: float) -> void:
122+
var move := Vector3()
123+
124+
var input: Vector2 = Input.get_vector(&"move_left", &"move_right", &"move_forward", &"move_backward") * MOVE_SPEED
125+
move.x = input.x
126+
move.z = input.y
127+
128+
# Apply gravity.
129+
move.y -= gravity * delta
130+
131+
# Apply mouse rotation to the move.
132+
move = move.rotated(Vector3(0, 1, 0), _yaw)
133+
134+
velocity += move
135+
136+
move_and_slide()
137+
138+
# Apply friction to horizontal motion in a tick rate-independent manner.
139+
var friction_delta := exp(-FRICTION * delta)
140+
velocity = Vector3(velocity.x * friction_delta, velocity.y, velocity.z * friction_delta)

0 commit comments

Comments
 (0)