FPS Controller
This commit is contained in:
3
demo/assets/editor/editor_theme.tres
Normal file
3
demo/assets/editor/editor_theme.tres
Normal file
@@ -0,0 +1,3 @@
|
||||
[gd_resource type="Theme" load_steps=0 format=3 uid="uid://cdl3vdrwd2dmk"]
|
||||
|
||||
[resource]
|
||||
23
demo/assets/scripts/destructable/camera.gd
Normal file
23
demo/assets/scripts/destructable/camera.gd
Normal file
@@ -0,0 +1,23 @@
|
||||
extends Camera3D
|
||||
|
||||
signal wall_hit(wall_id, position, cutter)
|
||||
|
||||
func shoot_ray() -> Dictionary:
|
||||
var mouse_position = get_viewport().get_mouse_position()
|
||||
var ray_length = 1000
|
||||
var from = project_ray_origin(mouse_position)
|
||||
var to = from + project_ray_normal(mouse_position) * ray_length
|
||||
var space = get_world_3d().direct_space_state
|
||||
var ray_query = PhysicsRayQueryParameters3D.new()
|
||||
ray_query.from = from
|
||||
ray_query.to = to
|
||||
var raycast_result = space.intersect_ray(ray_query)
|
||||
|
||||
return raycast_result
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("left_click"):
|
||||
var query_result = shoot_ray()
|
||||
if query_result:
|
||||
var cutter: PackedVector2Array = Cutter.circleCutter()
|
||||
wall_hit.emit(query_result["collider_id"], query_result["position"], cutter)
|
||||
1
demo/assets/scripts/destructable/camera.gd.uid
Normal file
1
demo/assets/scripts/destructable/camera.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cs057402w7w17
|
||||
24
demo/assets/scripts/destructable/cutters.gd
Normal file
24
demo/assets/scripts/destructable/cutters.gd
Normal file
@@ -0,0 +1,24 @@
|
||||
extends Node
|
||||
|
||||
class_name Cutter
|
||||
|
||||
static func circleCutter(num_sides = 10, perimerter_length = 0.5) -> PackedVector2Array:
|
||||
var line_length: float = perimerter_length/num_sides
|
||||
var line_angle: float = 360.0/num_sides
|
||||
var current_angle: float = 0.0
|
||||
|
||||
var vectors: PackedVector2Array
|
||||
|
||||
for i in num_sides:
|
||||
var vector: Vector2 = Vector2.ZERO
|
||||
|
||||
if i == 0:
|
||||
current_angle += line_angle
|
||||
vector = Vector2(line_length * cos(deg_to_rad(current_angle)), line_length * sin(deg_to_rad(current_angle)))
|
||||
vectors.append(vector)
|
||||
else:
|
||||
current_angle += line_angle
|
||||
vector = Vector2(vectors[i-1].x + line_length * cos(deg_to_rad(current_angle)), vectors[i-1].y + line_length * sin(deg_to_rad(current_angle)))
|
||||
vectors.append(vector)
|
||||
|
||||
return vectors
|
||||
1
demo/assets/scripts/destructable/cutters.gd.uid
Normal file
1
demo/assets/scripts/destructable/cutters.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cd5la8wwjk34w
|
||||
103
demo/assets/scripts/destructable/destructable_wall.gd
Normal file
103
demo/assets/scripts/destructable/destructable_wall.gd
Normal file
@@ -0,0 +1,103 @@
|
||||
extends Node
|
||||
|
||||
@export var outer_polygon: PackedVector2Array
|
||||
@export var inner_polygons: Array[PackedVector2Array]
|
||||
@export var edge_non_fracture: float = 0.1
|
||||
|
||||
@onready var camera: Camera3D = get_node("/root/Node3D/Camera3D")
|
||||
@onready var meshInstance = MeshInstance3D.new()
|
||||
@onready var parentNode = get_node("..")
|
||||
|
||||
var depth: float:
|
||||
set(value):
|
||||
if value > 0:
|
||||
depth = -value
|
||||
depth = value
|
||||
|
||||
var static_body: StaticBody3D
|
||||
|
||||
func _ready() -> void:
|
||||
assert(camera, "camera was not found")
|
||||
assert(len(outer_polygon) > 2, "outer polygon does not meet the required length")
|
||||
|
||||
camera.connect("wall_hit", _handle_hit_signal)
|
||||
|
||||
self.add_child(meshInstance)
|
||||
_draw()
|
||||
|
||||
func _draw():
|
||||
var vector_indexes = GeoPolyTriangulization.triangulate(outer_polygon, inner_polygons)
|
||||
var vectors = []
|
||||
|
||||
vectors.append_array(outer_polygon)
|
||||
for ip in inner_polygons:
|
||||
vectors.append_array(ip)
|
||||
|
||||
var meshGenerator = GeoPolyMesh.new(vector_indexes, vectors)
|
||||
var mesh = meshGenerator.commit_mesh()
|
||||
|
||||
meshInstance.mesh = mesh
|
||||
meshInstance.create_trimesh_collision()
|
||||
static_body = _find_static_body()
|
||||
|
||||
func _re_draw():
|
||||
meshInstance.remove_child(static_body)
|
||||
static_body.queue_free()
|
||||
|
||||
var vector_indexes = GeoPolyTriangulization.triangulate(outer_polygon, inner_polygons)
|
||||
var vectors = []
|
||||
|
||||
vectors.append_array(outer_polygon)
|
||||
for ip in inner_polygons:
|
||||
vectors.append_array(ip)
|
||||
|
||||
var meshGenerator = GeoPolyMesh.new(vector_indexes, vectors)
|
||||
var mesh = meshGenerator.commit_mesh()
|
||||
|
||||
meshInstance.mesh = mesh
|
||||
meshInstance.create_trimesh_collision()
|
||||
static_body = _find_static_body()
|
||||
|
||||
|
||||
func _handle_hit_signal(wall_id: int, position: Vector3, cutter: PackedVector2Array):
|
||||
# validate that the hit was to this wall, if not return
|
||||
if wall_id != static_body.get_instance_id():
|
||||
return
|
||||
|
||||
var hole_position_offset = self.position + position
|
||||
var hole_vector2_offset = Vector2(hole_position_offset.x, hole_position_offset.y)
|
||||
|
||||
# translate the cutter
|
||||
var hole_vectors = Transform2D(0, hole_vector2_offset) * cutter
|
||||
|
||||
_add_hole(hole_vectors)
|
||||
|
||||
_re_draw()
|
||||
|
||||
func _add_hole(new_hole: PackedVector2Array):
|
||||
var values_to_remove = []
|
||||
|
||||
var merged: PackedVector2Array = new_hole
|
||||
for index in range(len(self.inner_polygons)):
|
||||
var result = Geometry2D.merge_polygons(merged, self.inner_polygons[index])
|
||||
|
||||
if len(result) == 1:
|
||||
merged = result[0]
|
||||
values_to_remove.append(self.inner_polygons[index])
|
||||
elif len(result) > 1:
|
||||
# need to check that they have no overlapping area if they do clip
|
||||
var overlap = Geometry2D.intersect_polygons(result[0], result[1])
|
||||
|
||||
if len(overlap) != 0:
|
||||
merged = Geometry2D.clip_polygons(result[0], result[1])[0]
|
||||
values_to_remove.append(self.inner_polygons[index])
|
||||
|
||||
for i in values_to_remove:
|
||||
self.inner_polygons.erase(i)
|
||||
|
||||
inner_polygons.append(merged)
|
||||
|
||||
func _find_static_body():
|
||||
for child in meshInstance.get_children():
|
||||
if child is StaticBody3D:
|
||||
return child
|
||||
@@ -0,0 +1 @@
|
||||
uid://42cwsrh6jyns
|
||||
201
demo/assets/scripts/destructable/geometry/geopolymesh.gd
Normal file
201
demo/assets/scripts/destructable/geometry/geopolymesh.gd
Normal file
@@ -0,0 +1,201 @@
|
||||
extends Node
|
||||
|
||||
class_name GeoPolyMesh
|
||||
|
||||
var surface_array = []
|
||||
|
||||
var verts = PackedVector3Array()
|
||||
var uvs = PackedVector3Array()
|
||||
var normals = PackedVector3Array()
|
||||
var array_mesh = ArrayMesh.new()
|
||||
|
||||
var _v_indexes = []
|
||||
var _v_points = []
|
||||
|
||||
var edges = {}
|
||||
|
||||
func _init(vector_indexes: PackedInt32Array, vector_points: PackedVector2Array, depth: float = -0.1):
|
||||
assert(len(vector_indexes) % 3 == 0 && len(vector_indexes) != 0, "Number of vertex points is not divisible by 3, invalid triangle verticies")
|
||||
surface_array.resize(Mesh.ARRAY_MAX)
|
||||
|
||||
self._v_indexes = vector_indexes
|
||||
|
||||
for point in vector_points:
|
||||
self._v_points.append(Vector3(point.x, point.y, 0))
|
||||
|
||||
self._pre_process_edges()
|
||||
|
||||
self._draw_mesh()
|
||||
self._extrude_mesh(depth)
|
||||
self._draw_sides(depth)
|
||||
|
||||
func calculate_area(mesh_vertices: PackedVector2Array) -> float:
|
||||
var result := 0.0
|
||||
var num_vertices := mesh_vertices.size()
|
||||
|
||||
for q in range(num_vertices):
|
||||
var p = (q - 1 + num_vertices) % num_vertices
|
||||
result += mesh_vertices[q].cross(mesh_vertices[p])
|
||||
|
||||
return result * 0.5
|
||||
|
||||
# edges are any verticies that share only on triangle
|
||||
func _pre_process_edges():
|
||||
var faces = UTIL.chunk_array(self._v_indexes, 3)
|
||||
for vertices in faces:
|
||||
for v_inx in range(len(vertices)):
|
||||
var vertexA = vertices[v_inx]
|
||||
var vertexB = vertices[(v_inx+1) % 3]
|
||||
var min_index = min(vertexA, vertexB)
|
||||
var max_index = max(vertexA, vertexB)
|
||||
|
||||
var edge = self.edges.get([min_index, max_index])
|
||||
|
||||
if !edge:
|
||||
self.edges[[min_index, max_index]] = 1
|
||||
else:
|
||||
self.edges[[min_index, max_index]] += 1
|
||||
|
||||
func get_loops():
|
||||
var unvisted_edges = self.get_outline_edge()
|
||||
var loops = []
|
||||
|
||||
while len(unvisted_edges) != 0:
|
||||
var loop = []
|
||||
loop.append_array(unvisted_edges.pop_back())
|
||||
var v_next = loop.back()
|
||||
while loop[0] != v_next:
|
||||
for ue_ind in range(len(unvisted_edges)):
|
||||
var v1 = unvisted_edges[ue_ind][0]
|
||||
var v2 = unvisted_edges[ue_ind][1]
|
||||
|
||||
if v_next == v1:
|
||||
loop.append(v2)
|
||||
unvisted_edges.pop_at(ue_ind)
|
||||
v_next = loop.back()
|
||||
break
|
||||
elif v_next == v2:
|
||||
loop.append(v1)
|
||||
unvisted_edges.pop_at(ue_ind)
|
||||
v_next = loop.back()
|
||||
break
|
||||
loops.append(loop)
|
||||
|
||||
var vector2_loops = []
|
||||
for loop in loops:
|
||||
var vector2_loop = []
|
||||
for ind in loop:
|
||||
vector2_loop.append(self._v_points[ind])
|
||||
vector2_loops.append(vector2_loop)
|
||||
|
||||
var area = []
|
||||
for vector2_loop in vector2_loops:
|
||||
var result = self.calculate_area(vector2_loop)
|
||||
area.append(result)
|
||||
|
||||
# reorder the loops where outer boundary is the first one
|
||||
var index = 0
|
||||
var index_value = area[0]
|
||||
for ind in range(len(area)):
|
||||
var val = abs(area[ind])
|
||||
if val > index_value:
|
||||
index = ind
|
||||
index_value = val
|
||||
|
||||
var bloop = loops.pop_at(index)
|
||||
var barea = area.pop_at(index)
|
||||
|
||||
loops.push_front(bloop)
|
||||
area.push_front(barea)
|
||||
|
||||
# check for CW or CCW
|
||||
if area[0] > 0: # Outer boundary should be CW, if area is <0 then it is CCW
|
||||
loops[0].reverse()
|
||||
|
||||
for area_index in range(1, len(area)):
|
||||
var a = area[area_index]
|
||||
if a < 0: # all inside loops need to rendered in CCW, if + then it is CW
|
||||
loops[area_index].reverse()
|
||||
|
||||
|
||||
return loops
|
||||
|
||||
func get_outline_edge():
|
||||
var outline_edges = []
|
||||
for e in self.edges:
|
||||
var value = self.edges[e]
|
||||
if value == 1:
|
||||
outline_edges.append(e)
|
||||
return outline_edges
|
||||
|
||||
func _calc_triangle_normal(a: Vector3, b: Vector3, c: Vector3):
|
||||
return ((b-a).cross(c-a)).normalized()
|
||||
|
||||
func _draw_sides(depth: float):
|
||||
var loops = get_loops()
|
||||
|
||||
for l in loops:
|
||||
var outline_edges_ordered = l
|
||||
for outline_vector_ind in range(len(outline_edges_ordered) - 1):
|
||||
var v0 = self._v_points[outline_edges_ordered[outline_vector_ind]]
|
||||
var v1 = self._v_points[outline_edges_ordered[outline_vector_ind + 1]]
|
||||
var v2 = self._v_points[outline_edges_ordered[outline_vector_ind]] + Vector3(0, 0, depth)
|
||||
var v3 = self._v_points[outline_edges_ordered[outline_vector_ind + 1]]+ Vector3(0, 0, depth)
|
||||
|
||||
var n1 = self._calc_triangle_normal(v2, v1, v0)
|
||||
|
||||
self.verts.append_array([v0, v1, v2, v1, v3, v2])
|
||||
self.normals.append_array([n1, n1, n1, n1, n1, n1])
|
||||
|
||||
# front face
|
||||
func _draw_mesh():
|
||||
var vector3A = self._v_points[self._v_indexes[0]]
|
||||
var vector3B = self._v_points[self._v_indexes[1]]
|
||||
var vector3C = self._v_points[self._v_indexes[2]]
|
||||
|
||||
var normal = self._calc_triangle_normal(vector3C, vector3B, vector3A)
|
||||
|
||||
self.verts.append_array([vector3A, vector3B, vector3C])
|
||||
self.normals.append_array([normal, normal, normal])
|
||||
|
||||
# insert each front face into the mesh "clockwise"
|
||||
for index in range(3, len(self._v_indexes) - 1, 3):
|
||||
vector3A = self._v_points[self._v_indexes[index]]
|
||||
vector3B = self._v_points[self._v_indexes[index+1]]
|
||||
vector3C = self._v_points[self._v_indexes[index+2]]
|
||||
|
||||
self.verts.append_array([vector3A, vector3B, vector3C])
|
||||
self.normals.append_array([normal, normal, normal])
|
||||
#
|
||||
## back face
|
||||
func _extrude_mesh(depth: float):
|
||||
var vector3A = self._v_points[self._v_indexes[len(self._v_indexes) - 1]]
|
||||
var vector3B = self._v_points[self._v_indexes[len(self._v_indexes) - 2]]
|
||||
var vector3C = self._v_points[self._v_indexes[len(self._v_indexes) - 3]]
|
||||
|
||||
var normal = self._calc_triangle_normal(vector3C, vector3B, vector3A)
|
||||
vector3A = self._v_points[self._v_indexes[len(self._v_indexes) - 1]] - (normal * Vector3(depth, depth, depth))
|
||||
vector3B = self._v_points[self._v_indexes[len(self._v_indexes) - 2]] - (normal * Vector3(depth, depth, depth))
|
||||
vector3C = self._v_points[self._v_indexes[len(self._v_indexes) - 3]] - (normal * Vector3(depth, depth, depth))
|
||||
|
||||
self.verts.append_array([vector3A, vector3B, vector3C])
|
||||
self.normals.append_array([normal, normal, normal])
|
||||
|
||||
# insert each back face into the mesh "counter-clockwise" extruded
|
||||
for index in range(len(self._v_indexes) - 4, -1, -3):
|
||||
vector3A = self._v_points[self._v_indexes[index]] - (normal * Vector3(depth, depth, depth))
|
||||
vector3B = self._v_points[self._v_indexes[index-1]] - (normal * Vector3(depth, depth, depth))
|
||||
vector3C = self._v_points[self._v_indexes[index-2]] - (normal * Vector3(depth, depth, depth))
|
||||
|
||||
self.verts.append_array([vector3A, vector3B, vector3C])
|
||||
self.normals.append_array([normal, normal, normal])
|
||||
|
||||
func commit_mesh() -> Mesh:
|
||||
surface_array[Mesh.ARRAY_VERTEX] = verts
|
||||
surface_array[Mesh.ARRAY_NORMAL] = normals
|
||||
#surface_array[Mesh.ARRAY_TEX_UV] = uvs
|
||||
|
||||
#print("VERTS: ", self.verts)
|
||||
array_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, surface_array)
|
||||
|
||||
return array_mesh
|
||||
@@ -0,0 +1 @@
|
||||
uid://c61g8fppm366c
|
||||
@@ -0,0 +1,11 @@
|
||||
extends Node
|
||||
|
||||
# provides all the utility functions for generating
|
||||
# a triangulized Polygon with holes
|
||||
class_name GeoPolyTriangulization
|
||||
|
||||
# returns all triangles generated by ear clipping the outer and inner polygons
|
||||
static func triangulate(outer_polygon: PackedVector2Array, inner_polygons: Array[PackedVector2Array]) -> PackedInt32Array:
|
||||
var triag = Triangulization.new()
|
||||
var result = triag.triangulate_with_holes(outer_polygon, inner_polygons)
|
||||
return result
|
||||
@@ -0,0 +1 @@
|
||||
uid://comj14x4uckt2
|
||||
16
demo/assets/scripts/destructable/util.gd
Normal file
16
demo/assets/scripts/destructable/util.gd
Normal file
@@ -0,0 +1,16 @@
|
||||
extends Object
|
||||
|
||||
class_name UTIL
|
||||
|
||||
# chunks array into 'chunk_size' pieces
|
||||
static func chunk_array(arr: Array, chunk_size: int) -> Array:
|
||||
var result_chunks = []
|
||||
var i = 0
|
||||
while i < arr.size():
|
||||
var chunk = arr.slice(i, i + chunk_size)
|
||||
result_chunks.append(chunk)
|
||||
i += chunk_size
|
||||
return result_chunks
|
||||
|
||||
static func vector2_to_vector3(v: Vector2) -> Vector3:
|
||||
return Vector3(v.x, v.y, 0)
|
||||
1
demo/assets/scripts/destructable/util.gd.uid
Normal file
1
demo/assets/scripts/destructable/util.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bjv35mc6440ea
|
||||
62
demo/assets/scripts/player/camera_controller.gd
Normal file
62
demo/assets/scripts/player/camera_controller.gd
Normal file
@@ -0,0 +1,62 @@
|
||||
class_name CameraController extends Node3D
|
||||
|
||||
@export var debug : bool = false
|
||||
@export_category("References")
|
||||
@export var player_controller : PlayerController
|
||||
@export var component_mouse_capture : MouseCaptureComponent
|
||||
@export_category("Camera Settings")
|
||||
@export_group("Camera Tilt")
|
||||
@export_range(-90, -60) var tilt_lower_limit : int = -90
|
||||
@export_range(60, 90) var tilt_upper_limit : int = 90
|
||||
@export_group("Crouch Vertical Movement")
|
||||
@export var crouch_offset : float = 0.0
|
||||
@export var crouch_speed : float = 3.0
|
||||
@export_group("Step Smoothing")
|
||||
@export var step_speed: float = 8.0
|
||||
|
||||
# Step smoothing private values
|
||||
var _target_height: float
|
||||
var _step_smoothing: bool = false
|
||||
|
||||
var offset_height: float
|
||||
|
||||
var _rotation : Vector3
|
||||
|
||||
const DEFAULT_HEIGHT : float = 0.5
|
||||
|
||||
func _ready() -> void:
|
||||
_rotation = player_controller.rotation
|
||||
offset_height = DEFAULT_HEIGHT
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
update_camera_rotation(component_mouse_capture._mouse_input)
|
||||
|
||||
if _step_smoothing:
|
||||
_target_height = lerp(_target_height, 0.0, step_speed * delta)
|
||||
if abs(_target_height) < 0.01:
|
||||
_target_height = 0.0
|
||||
_step_smoothing = false
|
||||
|
||||
position.y = offset_height + _target_height
|
||||
|
||||
func smooth_step(height_change: float):
|
||||
_target_height -= height_change
|
||||
_step_smoothing = true
|
||||
|
||||
func update_camera_rotation(input: Vector2) -> void:
|
||||
_rotation.x += input.y
|
||||
_rotation.y += input.x
|
||||
_rotation.x = clamp(_rotation.x, deg_to_rad(tilt_lower_limit), deg_to_rad(tilt_upper_limit))
|
||||
|
||||
var _player_rotation = Vector3(0.0,_rotation.y,0.0)
|
||||
var _camera_rotation = Vector3(_rotation.x,0.0,0.0)
|
||||
|
||||
transform.basis = Basis.from_euler(_camera_rotation)
|
||||
player_controller.update_rotation(_player_rotation)
|
||||
|
||||
rotation.z = 0.0
|
||||
|
||||
|
||||
func update_camera_height(delta: float, direction: int) -> void:
|
||||
if position.y >= crouch_offset and position.y <= DEFAULT_HEIGHT:
|
||||
position.y = clampf(position.y + (crouch_speed * direction) * delta, crouch_offset, DEFAULT_HEIGHT)
|
||||
1
demo/assets/scripts/player/camera_controller.gd.uid
Normal file
1
demo/assets/scripts/player/camera_controller.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://4ocvqma2qqqy
|
||||
154
demo/assets/scripts/player/camera_effects.gd
Normal file
154
demo/assets/scripts/player/camera_effects.gd
Normal file
@@ -0,0 +1,154 @@
|
||||
class_name CameraEffects extends Camera3D
|
||||
|
||||
@export_category("References")
|
||||
@export var player: PlayerController
|
||||
|
||||
@export_category("Effects")
|
||||
@export var enable_tilt: bool = true
|
||||
@export var enable_fall_kick: bool = true
|
||||
@export var enable_damage_kick: bool = true
|
||||
@export var enable_weapon_kick: bool = true
|
||||
@export var enable_screen_shake: bool = true
|
||||
@export var enable_head_bob: bool = true
|
||||
@export_category("Kick & Recoil")
|
||||
@export_group("Run Tilt")
|
||||
@export var run_pitch: float = 0.1 # Degrees
|
||||
@export var run_roll: float = 0.25 # Degrees
|
||||
@export var max_pitch: float = 1.0 # Degrees
|
||||
@export var max_roll: float = 2.5 # Degrees
|
||||
@export_group("Camera Kick")
|
||||
@export_subgroup("Fall Kick")
|
||||
@export var fall_time: float = 0.3
|
||||
@export_subgroup("Damage Kick")
|
||||
@export var damage_time: float = 0.3
|
||||
@export_subgroup("Weapon Kick")
|
||||
@export var weapon_decay: float = 0.5
|
||||
@export_subgroup("HeadBob")
|
||||
@export_range(0.0, 0.1, 0.001) var bob_pitch: float = 0.05
|
||||
@export_range(0.0, 0.1, 0.001) var bob_roll: float = 0.025
|
||||
@export_range(0.0, 0.04, 0.001) var bob_up: float = 0.005
|
||||
@export_range(3.0, 8.0, 0.1) var bob_frequency: float = 6.0
|
||||
|
||||
# fall kick private vars
|
||||
var _fall_value: float = 0.0
|
||||
var _fall_timer: float = 0.0
|
||||
|
||||
# damage kick private vars
|
||||
var _damage_pitch: float = 0.0
|
||||
var _damage_roll: float = 0.0
|
||||
var _damage_timer: float = 0.0
|
||||
|
||||
# weapon kick private variable
|
||||
var _weapon_kick_angle: Vector3 = Vector3.ZERO
|
||||
|
||||
# screen shake private variables
|
||||
var _screen_shake_tween: Tween
|
||||
|
||||
# head bob private variables
|
||||
var _step_timer: float = 0.0
|
||||
|
||||
const MIN_SCREEN_SHAKE: float = 0.05
|
||||
const MAX_SCREEN_SHAKE: float = 0.5
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
calc_view_offset(delta)
|
||||
|
||||
|
||||
func calc_view_offset(delta: float):
|
||||
assert(player, "player is not attached to camera effects script")
|
||||
|
||||
var offset = Vector3.ZERO
|
||||
var angles = Vector3.ZERO
|
||||
var velocity = player.velocity
|
||||
|
||||
if enable_tilt:
|
||||
var forward = global_transform.basis.z
|
||||
var right = global_transform.basis.x
|
||||
|
||||
var forward_dot = velocity.dot(forward)
|
||||
var forward_tilt = clampf(forward_dot * deg_to_rad(run_pitch), deg_to_rad(-max_pitch), deg_to_rad(max_pitch))
|
||||
|
||||
var right_dot = velocity.dot(right)
|
||||
var side_tilt = clampf(right_dot * deg_to_rad(run_roll), deg_to_rad(-max_roll), deg_to_rad(max_roll))
|
||||
|
||||
angles.z -= side_tilt
|
||||
angles.x += forward_tilt
|
||||
|
||||
if enable_fall_kick:
|
||||
_fall_timer -= delta
|
||||
|
||||
var fall_ratio = max(0.0, _fall_timer / fall_time)
|
||||
var fall_kick_amount = fall_ratio * _fall_value
|
||||
|
||||
offset.y -= fall_kick_amount
|
||||
angles.x -= fall_kick_amount
|
||||
|
||||
if enable_fall_kick:
|
||||
_damage_timer -= delta
|
||||
|
||||
var damage_ratio = max(0.0, _damage_timer / damage_time)
|
||||
angles.x += damage_ratio * _damage_pitch
|
||||
angles.z += damage_ratio * _damage_roll
|
||||
|
||||
if enable_weapon_kick:
|
||||
_weapon_kick_angle = _weapon_kick_angle.move_toward(Vector3.ZERO, weapon_decay * delta)
|
||||
angles += _weapon_kick_angle
|
||||
|
||||
if enable_head_bob:
|
||||
var speed = Vector2(velocity.x, velocity.z).length()
|
||||
|
||||
if speed > 0.1 and player.is_on_floor():
|
||||
_step_timer += delta * (speed / bob_frequency)
|
||||
_step_timer = fmod(_step_timer, 1.0)
|
||||
else:
|
||||
_step_timer = 0.0
|
||||
|
||||
var bob_sin = sin(_step_timer * 2.0 * PI) * 0.5
|
||||
|
||||
var pitch_delta = bob_sin * deg_to_rad(bob_pitch) * speed
|
||||
var roll_delta = bob_sin * deg_to_rad(bob_roll) * speed
|
||||
var height_delta = bob_sin * delta * bob_up
|
||||
|
||||
angles.x -= pitch_delta
|
||||
angles.z -= roll_delta
|
||||
offset.y += height_delta
|
||||
|
||||
self.position = offset
|
||||
self.rotation = angles
|
||||
|
||||
|
||||
func add_fall_kick(fall_strength: float):
|
||||
_fall_value = deg_to_rad(fall_strength)
|
||||
_fall_timer = fall_time
|
||||
|
||||
|
||||
func add_damage_kick(pitch: float, roll: float, source: Vector3):
|
||||
var forward = global_transform.basis.z
|
||||
var right = global_transform.basis.x
|
||||
var direction = global_position.direction_to(source)
|
||||
var forward_dot = direction.dot(forward)
|
||||
var right_dot = direction.dot(right)
|
||||
_damage_pitch = deg_to_rad(pitch) * forward_dot
|
||||
_damage_roll = deg_to_rad(roll) * right_dot
|
||||
_damage_timer = damage_time
|
||||
|
||||
|
||||
func add_weapon_kick(pitch: float, yaw: float, roll: float):
|
||||
_weapon_kick_angle.x += deg_to_rad(pitch)
|
||||
_weapon_kick_angle.y += deg_to_rad(randf_range(-yaw, yaw))
|
||||
_weapon_kick_angle.z += deg_to_rad(randf_range(-roll, roll))
|
||||
|
||||
|
||||
func add_screen_shake(amount: float, seconds: float) -> void:
|
||||
if _screen_shake_tween:
|
||||
_screen_shake_tween.kill()
|
||||
|
||||
_screen_shake_tween = create_tween()
|
||||
_screen_shake_tween.tween_method(update_screen_shake.bind(amount), 0.0, 1.0, seconds).set_ease(Tween.EASE_OUT)
|
||||
|
||||
|
||||
func update_screen_shake(alpha: float, amount: float) -> void:
|
||||
amount = remap(amount, 0.0, 1.0, MIN_SCREEN_SHAKE, MAX_SCREEN_SHAKE)
|
||||
var current_shake_amount = amount * (1.0 - alpha)
|
||||
h_offset = randf_range(-current_shake_amount, current_shake_amount)
|
||||
v_offset = randf_range(-current_shake_amount, current_shake_amount)
|
||||
1
demo/assets/scripts/player/camera_effects.gd.uid
Normal file
1
demo/assets/scripts/player/camera_effects.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cpx1w1k6orwyo
|
||||
13
demo/assets/scripts/player/components/interaction_raycast.gd
Normal file
13
demo/assets/scripts/player/components/interaction_raycast.gd
Normal file
@@ -0,0 +1,13 @@
|
||||
extends RayCast3D
|
||||
|
||||
var current_object
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if is_colliding():
|
||||
var object = get_collider()
|
||||
if object == current_object:
|
||||
return
|
||||
else:
|
||||
current_object = object
|
||||
else:
|
||||
current_object = null
|
||||
@@ -0,0 +1 @@
|
||||
uid://bqeyf8hqbc7xy
|
||||
26
demo/assets/scripts/player/components/mouse_capture.gd
Normal file
26
demo/assets/scripts/player/components/mouse_capture.gd
Normal file
@@ -0,0 +1,26 @@
|
||||
class_name MouseCaptureComponent extends Node
|
||||
|
||||
@export var debug : bool = false
|
||||
@export_category("Mouse Capture Settings")
|
||||
@export var current_mouse_mode : Input.MouseMode = Input.MOUSE_MODE_CAPTURED
|
||||
@export var mouse_sensitivity : float = 0.005
|
||||
|
||||
var _capture_mouse : bool
|
||||
var _mouse_input : Vector2
|
||||
|
||||
|
||||
## Captures the relative mouse movement from the center of the screen
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
_capture_mouse = event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED
|
||||
if _capture_mouse:
|
||||
_mouse_input.x += -event.screen_relative.x * mouse_sensitivity
|
||||
_mouse_input.y += -event.screen_relative.y * mouse_sensitivity
|
||||
if debug:
|
||||
print(_mouse_input)
|
||||
|
||||
func _ready() -> void:
|
||||
Input.mouse_mode = current_mouse_mode
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
_mouse_input = Vector2.ZERO
|
||||
@@ -0,0 +1 @@
|
||||
uid://d0xgg3pig4b6i
|
||||
11
demo/assets/scripts/player/components/state_machine.gd
Normal file
11
demo/assets/scripts/player/components/state_machine.gd
Normal file
@@ -0,0 +1,11 @@
|
||||
class_name PlayerStateMachine extends Node
|
||||
|
||||
@export var debug : bool = false
|
||||
@export_category("References")
|
||||
@export var player_controller : PlayerController
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if player_controller:
|
||||
player_controller.state_chart.set_expression_property("Player Velocity", player_controller.velocity)
|
||||
player_controller.state_chart.set_expression_property("Player Hitting Head", player_controller.crouch_check.is_colliding())
|
||||
player_controller.state_chart.set_expression_property("Looking At: ", player_controller.interaction_raycast.current_object)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cqrpha4131qgx
|
||||
83
demo/assets/scripts/player/components/step_climber.gd
Normal file
83
demo/assets/scripts/player/components/step_climber.gd
Normal file
@@ -0,0 +1,83 @@
|
||||
class_name StepHandlerComponet extends Node
|
||||
|
||||
@export_category("References")
|
||||
@export var player: PlayerController
|
||||
@export_category("Step Settings")
|
||||
@export var surface_threshold: float = 0.3
|
||||
@export var step_height: float = 0.3
|
||||
|
||||
const FEET_ADJUSTED_HEIGHT = 0.05
|
||||
const MIN_MOVEMENT_LENGTH: float = 0.1
|
||||
const MIN_DOT_VALUE: float = 0.5
|
||||
const MIN_STEP_HEIGHT: float = 0.1
|
||||
|
||||
func handle_step_climbing():
|
||||
for i in player.get_slide_collision_count():
|
||||
var collision = player.get_slide_collision(i)
|
||||
if _is_surface_verical(collision):
|
||||
var measured_height = _measure_step_height(collision)
|
||||
if measured_height > MIN_STEP_HEIGHT and measured_height <= step_height and _is_valid_step_direction(collision):
|
||||
player.global_position.y += measured_height
|
||||
player.velocity = player.previous_velocity
|
||||
player.camera.smooth_step(measured_height)
|
||||
|
||||
func _is_valid_step_direction(collision: KinematicCollision3D):
|
||||
var collision_normal = collision.get_normal()
|
||||
var input_dir = player.get_input_direction()
|
||||
var movement_direction = player.transform.basis * Vector3(input_dir.x, 0, input_dir.y)
|
||||
if movement_direction.length() > MIN_MOVEMENT_LENGTH:
|
||||
movement_direction = movement_direction.normalized()
|
||||
var dot_product = movement_direction.dot(-collision_normal)
|
||||
return dot_product > MIN_DOT_VALUE
|
||||
return false
|
||||
|
||||
func _measure_step_height(collision: KinematicCollision3D) -> float:
|
||||
var space_state = player.get_world_3d().direct_space_state
|
||||
var collision_point = collision.get_position()
|
||||
|
||||
var player_feet = _get_player_feet_position()
|
||||
var player_head_y = player.global_position.y + (player.standing_collision.shape.height / 2)
|
||||
|
||||
var ray_start = Vector3(collision_point.x, player_head_y, collision_point.z)
|
||||
var ray_end = Vector3(collision_point.x, player_feet.y, collision_point.z)
|
||||
|
||||
var query = PhysicsRayQueryParameters3D.create(ray_start, ray_end)
|
||||
query.collision_mask = player.collision_mask
|
||||
query.exclude = [player.get_rid()]
|
||||
|
||||
var result = space_state.intersect_ray(query)
|
||||
if result:
|
||||
return result.position.y - player_feet.y
|
||||
|
||||
return 0.0
|
||||
|
||||
func _is_surface_verical(collision: KinematicCollision3D) -> bool:
|
||||
var normal = collision.get_normal()
|
||||
if abs(normal.y) <= surface_threshold:
|
||||
return true
|
||||
|
||||
return _check_collison_surface(collision)
|
||||
|
||||
# additional work to validate
|
||||
func _check_collison_surface(collision: KinematicCollision3D) -> bool:
|
||||
var space_state = player.get_world_3d().direct_space_state
|
||||
var collision_point = collision.get_position()
|
||||
|
||||
var player_feet: Vector3 = _get_player_feet_position()
|
||||
collision_point.y = player_feet.y
|
||||
|
||||
var query = PhysicsRayQueryParameters3D.create(player_feet, collision_point)
|
||||
query.collision_mask = player.collision_mask
|
||||
query.exclude = [player.get_rid()]
|
||||
|
||||
var result = space_state.intersect_ray(query)
|
||||
if result and abs(result.normal.y) <= surface_threshold:
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
func _get_player_feet_position() -> Vector3:
|
||||
var feet_pos = player.global_position
|
||||
feet_pos.y -= player.standing_collision.shape.height / 2
|
||||
feet_pos.y += FEET_ADJUSTED_HEIGHT
|
||||
return feet_pos
|
||||
@@ -0,0 +1 @@
|
||||
uid://b1us8up0jvwjb
|
||||
98
demo/assets/scripts/player/player_controller.gd
Normal file
98
demo/assets/scripts/player/player_controller.gd
Normal file
@@ -0,0 +1,98 @@
|
||||
class_name PlayerController extends CharacterBody3D
|
||||
|
||||
@export var debug: bool = false
|
||||
@export_category("References")
|
||||
@export var camera: CameraController
|
||||
@export var camera_effects: CameraEffects
|
||||
@export var state_chart: StateChart
|
||||
@export var standing_collision: CollisionShape3D
|
||||
@export var crouching_collision: CollisionShape3D
|
||||
@export var crouch_check: ShapeCast3D
|
||||
@export var interaction_raycast: RayCast3D
|
||||
@export var step_handler: StepHandlerComponet
|
||||
@export_category("Movement Settings")
|
||||
@export_group("Easing")
|
||||
@export var acceleration: float = 0.2
|
||||
@export var deceleration: float = 0.5
|
||||
@export_group("Speed")
|
||||
@export var default_speed: float = 7.0
|
||||
@export var sprint_speed: float = 3.0
|
||||
@export var crouch_speed: float = -5.0
|
||||
@export_category("Jump Settings")
|
||||
@export var jump_velocity: float = 5.0
|
||||
@export var fall_velocity_threashold: float = -5.0
|
||||
|
||||
var _input_dir: Vector2 = Vector2.ZERO
|
||||
var _movement_velocity: Vector3 = Vector3.ZERO
|
||||
var sprint_modifier: float = 0.0
|
||||
var crouch_modifier: float = 0.0
|
||||
var speed: float = 0.0
|
||||
var current_fall_velocity: float
|
||||
var previous_velocity: Vector3
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
previous_velocity = self.velocity
|
||||
|
||||
if not is_on_floor():
|
||||
velocity += get_gravity() * delta
|
||||
|
||||
var speed_modifier = sprint_modifier + crouch_modifier
|
||||
speed = default_speed + speed_modifier
|
||||
|
||||
_input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_backward")
|
||||
var current_velocity = Vector2(_movement_velocity.x, _movement_velocity.z)
|
||||
var direction = (transform.basis * Vector3(_input_dir.x, 0, _input_dir.y)).normalized()
|
||||
|
||||
if direction:
|
||||
current_velocity = lerp(current_velocity, Vector2(direction.x, direction.z) * speed, acceleration)
|
||||
else:
|
||||
current_velocity = current_velocity.move_toward(Vector2.ZERO, deceleration)
|
||||
|
||||
_movement_velocity = Vector3(current_velocity.x, velocity.y, current_velocity.y)
|
||||
|
||||
velocity = _movement_velocity
|
||||
|
||||
move_and_slide()
|
||||
|
||||
if is_on_floor():
|
||||
step_handler.handle_step_climbing()
|
||||
|
||||
func get_input_direction() -> Vector2:
|
||||
return _input_dir
|
||||
|
||||
func update_rotation(rotation_input) -> void:
|
||||
global_transform.basis = Basis.from_euler(rotation_input)
|
||||
|
||||
|
||||
func sprint() -> void:
|
||||
sprint_modifier = sprint_speed
|
||||
|
||||
|
||||
|
||||
func walk() -> void:
|
||||
sprint_modifier = 0.0
|
||||
|
||||
|
||||
func stand() -> void:
|
||||
crouch_modifier = 0.0
|
||||
standing_collision.disabled = false
|
||||
crouching_collision.disabled = true
|
||||
|
||||
|
||||
func crouch() -> void:
|
||||
crouch_modifier = crouch_speed
|
||||
standing_collision.disabled = true
|
||||
crouching_collision.disabled = false
|
||||
|
||||
|
||||
func jump() -> void:
|
||||
velocity.y += jump_velocity
|
||||
|
||||
|
||||
func check_fall_speed() -> bool:
|
||||
if current_fall_velocity < fall_velocity_threashold:
|
||||
current_fall_velocity = 0.0
|
||||
return true
|
||||
|
||||
current_fall_velocity = 0.0
|
||||
return false
|
||||
1
demo/assets/scripts/player/player_controller.gd.uid
Normal file
1
demo/assets/scripts/player/player_controller.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://caf5bdhu67k5q
|
||||
11
demo/assets/scripts/player/states/airborne_state.gd
Normal file
11
demo/assets/scripts/player/states/airborne_state.gd
Normal file
@@ -0,0 +1,11 @@
|
||||
extends PlayerState
|
||||
|
||||
|
||||
func _on_airborne_state_physics_processing(_delta: float) -> void:
|
||||
if player_controller.is_on_floor():
|
||||
if player_controller.check_fall_speed():
|
||||
player_controller.camera_effects.add_fall_kick(2.0)
|
||||
|
||||
player_controller.state_chart.send_event("onGrounded")
|
||||
|
||||
player_controller.current_fall_velocity += player_controller.velocity.y
|
||||
1
demo/assets/scripts/player/states/airborne_state.gd.uid
Normal file
1
demo/assets/scripts/player/states/airborne_state.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://u17omde74n8t
|
||||
10
demo/assets/scripts/player/states/base_state.gd
Normal file
10
demo/assets/scripts/player/states/base_state.gd
Normal file
@@ -0,0 +1,10 @@
|
||||
class_name PlayerState extends Node
|
||||
|
||||
@export var debug : bool = false
|
||||
|
||||
var player_controller : PlayerController
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if %StateMachine and %StateMachine is PlayerStateMachine:
|
||||
player_controller = %StateMachine.player_controller
|
||||
1
demo/assets/scripts/player/states/base_state.gd.uid
Normal file
1
demo/assets/scripts/player/states/base_state.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://yeyejbcbr1da
|
||||
12
demo/assets/scripts/player/states/crouching_state.gd
Normal file
12
demo/assets/scripts/player/states/crouching_state.gd
Normal file
@@ -0,0 +1,12 @@
|
||||
extends PlayerState
|
||||
|
||||
|
||||
func _on_crouching_state_physics_processing(delta: float) -> void:
|
||||
player_controller.camera.update_camera_height(delta, -1)
|
||||
|
||||
if not Input.is_action_pressed("crouch") and player_controller.is_on_floor() and not player_controller.crouch_check.is_colliding():
|
||||
player_controller.state_chart.send_event("onStanding")
|
||||
|
||||
|
||||
func _on_crouching_state_entered() -> void:
|
||||
player_controller.crouch()
|
||||
1
demo/assets/scripts/player/states/crouching_state.gd.uid
Normal file
1
demo/assets/scripts/player/states/crouching_state.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bnmnicp5nu3u2
|
||||
10
demo/assets/scripts/player/states/grounded_state.gd
Normal file
10
demo/assets/scripts/player/states/grounded_state.gd
Normal file
@@ -0,0 +1,10 @@
|
||||
extends PlayerState
|
||||
|
||||
|
||||
func _on_grounded_state_physics_processing(delta: float) -> void:
|
||||
if Input.is_action_just_pressed("jump") and player_controller.is_on_floor():
|
||||
player_controller.jump()
|
||||
player_controller.state_chart.send_event("onAirborne")
|
||||
|
||||
if not player_controller.is_on_floor():
|
||||
player_controller.state_chart.send_event("onAirborne")
|
||||
1
demo/assets/scripts/player/states/grounded_state.gd.uid
Normal file
1
demo/assets/scripts/player/states/grounded_state.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dk8ukj487ryhj
|
||||
6
demo/assets/scripts/player/states/idle_state.gd
Normal file
6
demo/assets/scripts/player/states/idle_state.gd
Normal file
@@ -0,0 +1,6 @@
|
||||
extends PlayerState
|
||||
|
||||
|
||||
func _on_idle_state_processing(delta: float) -> void:
|
||||
if player_controller and player_controller._input_dir.length() > 0:
|
||||
player_controller.state_chart.send_event("onMoving")
|
||||
1
demo/assets/scripts/player/states/idle_state.gd.uid
Normal file
1
demo/assets/scripts/player/states/idle_state.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://d03p2engkhyjc
|
||||
6
demo/assets/scripts/player/states/moving_state.gd
Normal file
6
demo/assets/scripts/player/states/moving_state.gd
Normal file
@@ -0,0 +1,6 @@
|
||||
extends PlayerState
|
||||
|
||||
|
||||
func _on_moving_state_physics_processing(delta: float) -> void:
|
||||
if player_controller._input_dir.length() == 0 and player_controller.velocity.length() < 0.5:
|
||||
player_controller.state_chart.send_event("onIdle")
|
||||
1
demo/assets/scripts/player/states/moving_state.gd.uid
Normal file
1
demo/assets/scripts/player/states/moving_state.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dc5dk6fyv83gt
|
||||
10
demo/assets/scripts/player/states/sprinting_state.gd
Normal file
10
demo/assets/scripts/player/states/sprinting_state.gd
Normal file
@@ -0,0 +1,10 @@
|
||||
extends PlayerState
|
||||
|
||||
|
||||
func _on_sprinting_state_processing(delta: float) -> void:
|
||||
if not Input.is_action_pressed("sprint"):
|
||||
player_controller.state_chart.send_event("onWalking")
|
||||
|
||||
|
||||
func _on_sprinting_state_entered() -> void:
|
||||
player_controller.sprint()
|
||||
1
demo/assets/scripts/player/states/sprinting_state.gd.uid
Normal file
1
demo/assets/scripts/player/states/sprinting_state.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b40akfam2xgt
|
||||
12
demo/assets/scripts/player/states/standing_state.gd
Normal file
12
demo/assets/scripts/player/states/standing_state.gd
Normal file
@@ -0,0 +1,12 @@
|
||||
extends PlayerState
|
||||
|
||||
|
||||
func _on_standing_state_physics_processing(delta: float) -> void:
|
||||
player_controller.camera.update_camera_height(delta, 1)
|
||||
|
||||
if Input.is_action_pressed("crouch") and player_controller.is_on_floor():
|
||||
player_controller.state_chart.send_event("onCrouching")
|
||||
|
||||
|
||||
func _on_standing_state_entered() -> void:
|
||||
player_controller.stand()
|
||||
1
demo/assets/scripts/player/states/standing_state.gd.uid
Normal file
1
demo/assets/scripts/player/states/standing_state.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c1eixxgwl2exy
|
||||
10
demo/assets/scripts/player/states/walking_state.gd
Normal file
10
demo/assets/scripts/player/states/walking_state.gd
Normal file
@@ -0,0 +1,10 @@
|
||||
extends PlayerState
|
||||
|
||||
|
||||
func _on_walking_state_processing(delta: float) -> void:
|
||||
if Input.is_action_pressed("sprint"):
|
||||
player_controller.state_chart.send_event("onSprinting")
|
||||
|
||||
|
||||
func _on_walking_state_entered() -> void:
|
||||
player_controller.walk()
|
||||
1
demo/assets/scripts/player/states/walking_state.gd.uid
Normal file
1
demo/assets/scripts/player/states/walking_state.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bstd2nopn6ur
|
||||
8
demo/assets/scripts/system/game_manager.gd
Normal file
8
demo/assets/scripts/system/game_manager.gd
Normal file
@@ -0,0 +1,8 @@
|
||||
class_name GameManager extends Node
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("dev_exit"):
|
||||
get_tree().quit()
|
||||
if event.is_action_pressed("dev_reload"):
|
||||
get_tree().reload_current_scene()
|
||||
1
demo/assets/scripts/system/game_manager.gd.uid
Normal file
1
demo/assets/scripts/system/game_manager.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cfyv1loyjisg3
|
||||
69
demo/assets/scripts/ui/reticle_draw.gd
Normal file
69
demo/assets/scripts/ui/reticle_draw.gd
Normal file
@@ -0,0 +1,69 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
|
||||
@export var radius: float = 30.0 : set = set_crosshair_radius
|
||||
@export var thickness: float = 1.0 : set = set_crosshair_thickness
|
||||
@export var color: Color = Color.WHITE : set = set_crosshair_color
|
||||
@export var gap_angle: float = 45.0 : set = set_crosshair_gap_angle
|
||||
@export var segments: int = 32 : set = set_crosshair_segments
|
||||
|
||||
func _draw():
|
||||
draw_circle_crosshair()
|
||||
|
||||
func draw_circle_crosshair():
|
||||
var gap_rad = deg_to_rad(gap_angle)
|
||||
|
||||
var arc_segments = [
|
||||
# Bottom-right quadrant
|
||||
[gap_rad / 2, PI / 2 - gap_rad / 2],
|
||||
# Bottom-left quadrant
|
||||
[PI / 2 + gap_rad / 2, PI - gap_rad / 2],
|
||||
# Top-left quadrant
|
||||
[PI + gap_rad / 2, 3 * PI / 2 - gap_rad / 2],
|
||||
# Top-right quadrant
|
||||
[3 * PI / 2 + gap_rad / 2, 2 * PI - gap_rad / 2]
|
||||
]
|
||||
for arc in arc_segments:
|
||||
var start_angle = arc[0]
|
||||
var end_angle = arc[1]
|
||||
|
||||
var points = []
|
||||
var angle_step = (end_angle - start_angle) / segments
|
||||
|
||||
for i in range(segments + 1):
|
||||
var angle = start_angle + i * angle_step
|
||||
var point = Vector2(radius * cos(angle), radius * sin(angle))
|
||||
points.append(point)
|
||||
|
||||
if points.size() > 1:
|
||||
draw_polyline(points, color, thickness, true)
|
||||
|
||||
|
||||
func update_crosshair():
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func set_crosshair_radius(new_radius):
|
||||
radius = new_radius
|
||||
update_crosshair()
|
||||
|
||||
|
||||
func set_crosshair_color(new_color):
|
||||
color = new_color
|
||||
update_crosshair()
|
||||
|
||||
|
||||
func set_crosshair_thickness(new_thickness):
|
||||
thickness = new_thickness
|
||||
update_crosshair()
|
||||
|
||||
|
||||
func set_crosshair_gap_angle(new_gap_angle):
|
||||
gap_angle = new_gap_angle
|
||||
update_crosshair()
|
||||
|
||||
|
||||
func set_crosshair_segments(new_segments):
|
||||
segments = new_segments
|
||||
update_crosshair()
|
||||
1
demo/assets/scripts/ui/reticle_draw.gd.uid
Normal file
1
demo/assets/scripts/ui/reticle_draw.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://yxv4rdoktnrl
|
||||
BIN
demo/assets/textures/t_floormetal1.png
Normal file
BIN
demo/assets/textures/t_floormetal1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.9 KiB |
41
demo/assets/textures/t_floormetal1.png.import
Normal file
41
demo/assets/textures/t_floormetal1.png.import
Normal file
@@ -0,0 +1,41 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://doknmohl75xnp"
|
||||
path.s3tc="res://.godot/imported/t_floormetal1.png-42986c698f9d8b89446ac25d30252b9c.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/textures/t_floormetal1.png"
|
||||
dest_files=["res://.godot/imported/t_floormetal1.png-42986c698f9d8b89446ac25d30252b9c.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
BIN
demo/assets/textures/texture.png
Normal file
BIN
demo/assets/textures/texture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
41
demo/assets/textures/texture.png.import
Normal file
41
demo/assets/textures/texture.png.import
Normal file
@@ -0,0 +1,41 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dwhdp7spars2b"
|
||||
path.s3tc="res://.godot/imported/texture.png-59546a6b6ae00ae598d09228d4c66fd2.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/textures/texture.png"
|
||||
dest_files=["res://.godot/imported/texture.png-59546a6b6ae00ae598d09228d4c66fd2.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
Reference in New Issue
Block a user