GDScript physics :p

master
Marc Gilleron 2017-08-27 15:54:21 +02:00
parent 1f58edfe9b
commit 3892d546e0
7 changed files with 455 additions and 38 deletions

121
project/box_physics.gd Normal file
View File

@ -0,0 +1,121 @@
const EPSILON = 0.001
# Gets the transformed vector for moving a box and slide.
# This algorithm is free from tunnelling for axis-aligned movement,
# except in some high-speed diagonal cases or huge size differences:
# For example, if a box is fast enough to have a diagonal motion jumping from A to B,
# it will pass through C if that other box is the only other one:
#
# o---o
# | A |
# o---o
# o---o
# | C |
# o---o
# o---o
# | B |
# o---o
#
# TODO one way to fix this would be to try a "hot side" projection instead
#
static func get_motion(box, motion, other_boxes):
# The bounding box is expanded to include it's estimated version at next update.
# This also makes the algorithm tunnelling-free
var expanded_box = expand_with_vector(box, motion)
var colliding_boxes = []
for other in other_boxes:
if expanded_box.intersects(other):
colliding_boxes.append(other)
if colliding_boxes.size() == 0:
return motion
#print("Colliding: ", colliding_boxes.size())
var new_motion = motion
for other in colliding_boxes:
new_motion.y = calculate_y_offset(other, box, new_motion.y)
box.position.y += new_motion.y
for other in colliding_boxes:
new_motion.x = calculate_x_offset(other, box, new_motion.x)
box.position.x += new_motion.x
for other in colliding_boxes:
new_motion.z = calculate_z_offset(other, box, new_motion.z)
box.position.z += new_motion.z
return new_motion
static func expand_with_vector(box, v):
if v.x > 0:
box.size.x += v.x
elif v.x < 0:
box.position.x += v.x
box.size.x -= v.x
if v.y > 0:
box.size.y += v.y
elif v.y < 0:
box.position.y += v.y
box.size.y -= v.y
if v.z > 0:
box.size.z += v.z
elif v.z < 0:
box.position.z += v.z
box.size.z -= v.z
return box
static func calculate_z_offset(box, other, motion_z):
if other.end.y <= box.position.y || other.position.y >= box.end.y:
return motion_z
if other.end.x <= box.position.x || other.position.x >= box.end.x:
return motion_z
if motion_z > 0.0 and other.end.z <= box.position.z:
var off = box.position.z - other.end.z - EPSILON
if off < motion_z:
motion_z = off
if motion_z < 0.0 and other.position.z >= box.end.z:
var off = box.end.z - other.position.z + EPSILON
if off > motion_z:
motion_z = off
return motion_z
static func calculate_x_offset(box, other, motion_x):
if other.end.z <= box.position.z || other.position.z >= box.end.z:
return motion_x
if other.end.y <= box.position.y || other.position.y >= box.end.y:
return motion_x
if motion_x > 0.0 and other.end.x <= box.position.x:
var off = box.position.x - other.end.x - EPSILON
if off < motion_x:
motion_x = off
if motion_x < 0.0 and other.position.x >= box.end.x:
var off = box.end.x - other.position.x + EPSILON
if off > motion_x:
motion_x = off
return motion_x
static func calculate_y_offset(box, other, motion_y):
if other.end.z <= box.position.z || other.position.z >= box.end.z:
return motion_y
if other.end.x <= box.position.x || other.position.x >= box.end.x:
return motion_y
if motion_y > 0.0 and other.end.y <= box.position.y:
var off = box.position.y - other.end.y - EPSILON
if off < motion_y:
motion_y = off
if motion_y < 0.0 and other.position.y >= box.end.y:
var off = box.end.y - other.position.y + EPSILON
if off > motion_y:
motion_y = off
return motion_y
static func box_from_center_extents(center, extents):
return Rect3(center - extents, 2.0*extents)

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=8 format=2]
[gd_scene load_steps=9 format=2]
[ext_resource path="res://character_controller.gd" type="Script" id=1]
[ext_resource path="res://mouse_look.gd" type="Script" id=2]
@ -15,56 +15,64 @@ height = 0.85
flags_transparent = false
flags_unshaded = false
flags_vertex_lighting = false
flags_on_top = false
flags_use_point_size = false
flags_fixed_size = false
vertex_color_use_as_albedo = true
vertex_color_is_srgb = false
params_diffuse_mode = 0
params_specular_mode = 0
params_blend_mode = 0
params_cull_mode = 0
params_depth_draw_mode = 0
params_line_width = 2.0
params_point_size = 1.0
params_billboard_mode = 0
params_grow = false
params_use_alpha_scissor = false
albedo_color = Color( 1, 1, 1, 1 )
specular_mode = 0
specular_color = Color( 0.1, 0.1, 0.1, 1 )
specular_metalness = 0.1
specular_roughness = 0.0
metallic = 0.0
metallic_specular = 0.5
metallic_texture_channel = 0
roughness = 0.0
roughness_texture_channel = 0
emission_enabled = false
normal_enabled = false
rim_enabled = false
clearcoat_enabled = false
anisotropy_enabled = false
ao_enabled = false
height_enabled = false
depth_enabled = false
subsurf_scatter_enabled = false
refraction_enabled = false
detail_enabled = false
uv1_scale = Vector2( 1, 1 )
uv1_offset = Vector2( 0, 0 )
uv2_scale = Vector2( 1, 1 )
uv2_offset = Vector2( 0, 0 )
uv1_scale = Vector3( 1, 1, 1 )
uv1_offset = Vector3( 0, 0, 0 )
uv1_triplanar = false
uv1_triplanar_sharpness = 1.0
uv2_scale = Vector3( 1, 1, 1 )
uv2_offset = Vector3( 0, 0, 0 )
uv2_triplanar = false
uv2_triplanar_sharpness = 1.0
[sub_resource type="CubeMesh" id=3]
size = Vector3( 0.8, 1.8, 0.8 )
subdivide_width = 0
subdivide_height = 0
subdivide_depth = 0
[node name="CharacterAvatar" type="KinematicBody"]
input_ray_pickable = true
input_capture_on_drag = false
shape_count = 1
shapes/0/shape = SubResource( 1 )
shapes/0/transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 )
shapes/0/trigger = false
collision_layer = 3
collision_mask = 1
collide_with/static = true
collide_with/kinematic = true
collide_with/rigid = true
collide_with/character = true
collision/margin = 0.001
collision/safe_margin = 0.001
script = ExtResource( 1 )
speed = 5.0
gravity = 9.8
gravity = 40.0
jump_force = 8.0
head = NodePath("Camera")
terrain = null
@ -73,8 +81,7 @@ terrain = null
transform = Transform( 1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0, 0 )
shape = SubResource( 1 )
trigger = false
_update_shape_index = 0
disabled = false
[node name="Camera" type="Camera" parent="."]
@ -89,11 +96,14 @@ cull_mask = 1048575
environment = null
h_offset = 0.0
v_offset = 0.0
doppler/tracking = 0
script = ExtResource( 2 )
_sections_unfolded = [ "Transform" ]
sensitivity = 0.3
min_angle = -90
max_angle = 90
capture_mouse = true
distance = 4.0
[node name="interaction" type="Node" parent="."]
@ -103,10 +113,16 @@ cursor_material = SubResource( 2 )
[node name="debug_label" type="Label" parent="."]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_right = 379.0
margin_bottom = 325.0
rect_pivot_offset = Vector2( 0, 0 )
rect_clip_content = false
mouse_filter = 2
size_flags_horizontal = 1
size_flags_vertical = 0
percent_visible = 1.0
lines_skipped = 0
@ -131,10 +147,26 @@ shadow_color = Color( 0, 0, 0, 1 )
shadow_bias = 0.1
shadow_contact = 0.0
shadow_max_distance = 0.0
shadow_reverse_cull_face = false
editor_only = false
omni_range = 10.0
omni_attenuation = 2.54912
omni_shadow_mode = 1
omni_shadow_detail = 1
[node name="MeshInstance" type="MeshInstance" parent="."]
layers = 1
material_override = null
cast_shadow = 1
extra_cull_margin = 0.0
use_in_baked_light = false
lod_min_distance = 0.0
lod_min_hysteresis = 0.0
lod_max_distance = 0.0
lod_max_hysteresis = 0.0
mesh = SubResource( 3 )
skeleton = NodePath("..")
material/0 = null

View File

@ -1,5 +1,7 @@
extends KinematicBody
const BoxPhysics = preload("res://box_physics.gd")
export var speed = 5.0
export var gravity = 9.8
export var jump_force = 5.0
@ -43,13 +45,19 @@ func _fixed_process(delta):
_velocity.z = motor.z
_velocity.y -= gravity * delta
if _grounded and Input.is_key_pressed(KEY_SPACE):
#if _grounded and Input.is_key_pressed(KEY_SPACE):
if Input.is_key_pressed(KEY_SPACE):
_velocity.y = jump_force
_grounded = false
#_grounded = false
var motion = _velocity * delta
var rem = move(motion)
motion = move_with_box_physics(motion)
assert(delta > 0)
_velocity = motion / delta
#var rem = move(motion)
# TODO Fix it, obsolete code
# if is_colliding():
@ -64,3 +72,60 @@ func _fixed_process(delta):
# else:
# _grounded = false
#get_node("debug").set_text("Grounded=" + str(_grounded))
# TODO There is room for optimization, but I'll leave it like this for now, it doesn't cause any lag
func move_with_box_physics(motion):
var debug3d = get_node("../Debug3D")
var pos = get_translation()
var box = BoxPhysics.box_from_center_extents(pos, Vector3(0.4, 0.9, 0.4))
var expanded_box = BoxPhysics.expand_with_vector(box, motion)
debug3d.draw_wire_box(expanded_box, Color(0,1,0,1))
var potential_boxes = []
# Collect collisions with the terrain
if has_node(terrain):
var voxel_terrain = get_node(terrain)
var voxels = voxel_terrain.get_storage()
var min_x = int(floor(expanded_box.position.x))
var min_y = int(floor(expanded_box.position.y))
var min_z = int(floor(expanded_box.position.z))
var max_x = int(ceil(expanded_box.end.x))
var max_y = int(ceil(expanded_box.end.y))
var max_z = int(ceil(expanded_box.end.z))
var x = min_x
var y = min_y
var z = min_z
while z < max_z:
while y < max_y:
while x < max_x:
var voxel_type = voxels.get_voxel(x,y,z, 0)
if voxel_type != 0:
var voxel_box = Rect3(Vector3(x,y,z), Vector3(1,1,1))
potential_boxes.append(voxel_box)
debug3d.draw_wire_box(voxel_box)
x += 1
x = min_x
y += 1
y = min_y
z += 1
motion = BoxPhysics.get_motion(box, motion, potential_boxes)
move(motion)
return motion

109
project/debug3d.gd Normal file
View File

@ -0,0 +1,109 @@
extends MeshInstance
var _boxes = []
var _colors = []
var _mesh = null
# Who said "use ImmediateGeometry" node?
func draw_wire_box(box, color=Color(1,1,1,1)):
_boxes.append(box)
_colors.append(color)
func _fixed_process(delta):
if _mesh == null:
_mesh = ArrayMesh.new()
mesh = _mesh
if _mesh.get_surface_count() != 0:
_mesh.surface_remove(0)
var positions = PoolVector3Array()
var colors = PoolColorArray()
var indices = PoolIntArray()
for i in range(0, _boxes.size()):
var box = _boxes[i]
var color = _colors[i]
var vi = positions.size()
var pos = box.position
var end = box.end
var x0 = pos.x
var y0 = pos.y
var z0 = pos.z
var x1 = end.x
var y1 = end.y
var z1 = end.z
positions.append_array([
Vector3(x0, y0, z0),
Vector3(x1, y0, z0),
Vector3(x1, y0, z1),
Vector3(x0, y0, z1),
Vector3(x0, y1, z0),
Vector3(x1, y1, z0),
Vector3(x1, y1, z1),
Vector3(x0, y1, z1)
])
colors.append_array([
color,
color,
color,
color,
color,
color,
color,
color
])
indices.append_array([
indices.append(vi),
indices.append(vi+1),
indices.append(vi+1),
indices.append(vi+2),
indices.append(vi+2),
indices.append(vi+3),
indices.append(vi+3),
indices.append(vi),
indices.append(vi+4),
indices.append(vi+5),
indices.append(vi+5),
indices.append(vi+6),
indices.append(vi+6),
indices.append(vi+7),
indices.append(vi+7),
indices.append(vi+4),
indices.append(vi),
indices.append(vi+4),
indices.append(vi+1),
indices.append(vi+5),
indices.append(vi+2),
indices.append(vi+6),
indices.append(vi+3),
indices.append(vi+7)
])
if positions.size() != 0:
var arrays = []
# TODO Use ArrayMesh.ARRAY_MAX
arrays.resize(9)
arrays[ArrayMesh.ARRAY_VERTEX] = positions
arrays[ArrayMesh.ARRAY_COLOR] = colors
arrays[ArrayMesh.ARRAY_INDEX] = indices
_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_LINES, arrays)
_boxes.clear()
_colors.clear()

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=16 format=2]
[gd_scene load_steps=18 format=2]
[ext_resource path="res://terrain_material.tres" type="Material" id=1]
[ext_resource path="res://terrain_marerial_transparent.tres" type="Material" id=2]
@ -7,6 +7,7 @@
[ext_resource path="res://character_avatar.tscn" type="PackedScene" id=5]
[ext_resource path="res://axes.tscn" type="PackedScene" id=6]
[ext_resource path="res://profiling_gui.gd" type="Script" id=7]
[ext_resource path="res://debug3d.gd" type="Script" id=8]
[sub_resource type="ProceduralSky" id=1]
@ -195,6 +196,52 @@ subdivide_width = 0
subdivide_height = 0
subdivide_depth = 0
[sub_resource type="SpatialMaterial" id=9]
flags_transparent = false
flags_unshaded = true
flags_vertex_lighting = false
flags_on_top = false
flags_use_point_size = false
flags_fixed_size = false
vertex_color_use_as_albedo = true
vertex_color_is_srgb = false
params_diffuse_mode = 0
params_specular_mode = 0
params_blend_mode = 0
params_cull_mode = 0
params_depth_draw_mode = 0
params_line_width = 1.0
params_point_size = 1.0
params_billboard_mode = 0
params_grow = false
params_use_alpha_scissor = false
albedo_color = Color( 1, 1, 1, 1 )
metallic = 0.0
metallic_specular = 0.5
metallic_texture_channel = 0
roughness = 0.0
roughness_texture_channel = 0
emission_enabled = false
normal_enabled = false
rim_enabled = false
clearcoat_enabled = false
anisotropy_enabled = false
ao_enabled = false
depth_enabled = false
subsurf_scatter_enabled = false
refraction_enabled = false
detail_enabled = false
uv1_scale = Vector3( 1, 1, 1 )
uv1_offset = Vector3( 0, 0, 0 )
uv1_triplanar = false
uv1_triplanar_sharpness = 1.0
uv2_scale = Vector3( 1, 1, 1 )
uv2_offset = Vector3( 0, 0, 0 )
uv2_triplanar = false
uv2_triplanar_sharpness = 1.0
_sections_unfolded = [ "Emission", "Flags", "Vertex Color" ]
[node name="Node" type="Node"]
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
@ -205,7 +252,7 @@ environment = SubResource( 2 )
provider = SubResource( 3 )
voxel_library = SubResource( 6 )
view_distance = 64
view_distance = 32
viewer_path = NodePath("")
generate_collisions = true
material/0 = ExtResource( 1 )
@ -221,11 +268,11 @@ _sections_unfolded = [ "material" ]
[node name="Grid" type="MeshInstance" parent="."]
visible = false
layers = 1
material_override = SubResource( 7 )
cast_shadow = 1
extra_cull_margin = 0.0
visible_in_all_rooms = false
use_in_baked_light = false
lod_min_distance = 0.0
lod_min_hysteresis = 0.0
@ -251,6 +298,7 @@ shadow_color = Color( 0, 0, 0, 1 )
shadow_bias = 0.1
shadow_contact = 0.0
shadow_max_distance = 100.0
shadow_reverse_cull_face = false
editor_only = false
directional_shadow_mode = 2
directional_shadow_split_1 = 0.1
@ -262,17 +310,17 @@ _sections_unfolded = [ "Directional Shadow", "Light", "Shadow" ]
[node name="CharacterAvatar" parent="." instance=ExtResource( 5 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -75.9996, 22.9488, 0 )
collision/safe_margin = 0.001
speed = 4.0
gravity = 0.0
jump_force = 9.0
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 25, 17 )
terrain = NodePath("../VoxelTerrain")
[node name="axes" parent="." instance=ExtResource( 6 )]
[node name="ProfilingInfo" type="Label" parent="."]
anchor_left = 0.0
anchor_top = 0.0
anchor_right = 0.0
anchor_bottom = 0.0
margin_top = 20.0
margin_right = 104.0
margin_bottom = 34.0
@ -293,7 +341,6 @@ layers = 1
material_override = null
cast_shadow = 1
extra_cull_margin = 0.0
visible_in_all_rooms = false
use_in_baked_light = false
lod_min_distance = 0.0
lod_min_hysteresis = 0.0
@ -304,4 +351,20 @@ skeleton = NodePath("..")
material/0 = ExtResource( 1 )
_sections_unfolded = [ "material" ]
[node name="Debug3D" type="MeshInstance" parent="."]
layers = 1
material_override = SubResource( 9 )
cast_shadow = 1
extra_cull_margin = 0.0
use_in_baked_light = false
lod_min_distance = 0.0
lod_min_hysteresis = 0.0
lod_max_distance = 0.0
lod_max_hysteresis = 0.0
mesh = null
skeleton = NodePath("..")
script = ExtResource( 8 )
_sections_unfolded = [ "Geometry" ]

View File

@ -5,12 +5,15 @@ export var sensitivity = 0.4
export var min_angle = -90
export var max_angle = 90
export var capture_mouse = true
export var distance = 5.0
var _yaw = 0
var _pitch = 0
var _offset = Vector3()
func _ready():
_offset = get_translation()
if capture_mouse:
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
@ -21,6 +24,14 @@ func _input(event):
if capture_mouse:
# Capture the mouse
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
if event.button_index == BUTTON_WHEEL_UP:
distance = max(distance-1, 0)
update_rotations()
elif event.button_index == BUTTON_WHEEL_DOWN:
distance = max(distance+1, 0)
update_rotations()
elif event is InputEventMouseMotion:
if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED || not capture_mouse:
@ -39,8 +50,7 @@ func _input(event):
_pitch = min_angle+e
# Apply rotations
set_rotation(Vector3(0, deg2rad(_yaw), 0))
rotate(get_transform().basis.x.normalized(), -deg2rad(_pitch))
update_rotations()
elif event is InputEventKey:
if event.pressed:
@ -54,5 +64,10 @@ func _input(event):
print("Position: ", pos, ", Forward: ", fw)
func update_rotations():
set_translation(Vector3())
set_rotation(Vector3(0, deg2rad(_yaw), 0))
rotate(get_transform().basis.x.normalized(), -deg2rad(_pitch))
set_translation(get_transform().basis.z * distance + _offset)

View File

@ -1,14 +1,26 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=3
[application]
run/main_scene="res://main.tscn"
name="Voxel Game"
main_scene="res://main.tscn"
icon="res://icon.png"
[input]
action1=[ InputEvent(MBUTTON,1) ]
action2=[ InputEvent(MBUTTON,2) ]
action1=[ Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":1,"pressed":false,"doubleclick":false,"script":null)
]
action2=[ Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":2,"pressed":false,"doubleclick":false,"script":null)
]
[layer_names]