feat: refine character animation and gameplay UI
This commit is contained in:
parent
63a5009b33
commit
54e1b8538d
14 changed files with 314 additions and 55 deletions
Binary file not shown.
Binary file not shown.
BIN
art/source/characters/base/netfishing_character_source.glb
Normal file
BIN
art/source/characters/base/netfishing_character_source.glb
Normal file
Binary file not shown.
|
|
@ -0,0 +1,45 @@
|
|||
[remap]
|
||||
|
||||
importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://3uq4cxiomttt"
|
||||
path="res://.godot/imported/netfishing_character_source.glb-9c98ee44b142c5b3d06f0838b7d2c9c4.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/source/characters/base/netfishing_character_source.glb"
|
||||
dest_files=["res://.godot/imported/netfishing_character_source.glb-9c98ee44b142c5b3d06f0838b7d2c9c4.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
nodes/root_type=""
|
||||
nodes/root_name=""
|
||||
nodes/root_script=null
|
||||
mesh_library/use_node_names_as_mesh_names=false
|
||||
array_mesh/deduplicate_surfaces=true
|
||||
nodes/apply_root_scale=true
|
||||
nodes/root_scale=1.0
|
||||
nodes/import_as_skeleton_bones=false
|
||||
nodes/use_name_suffixes=true
|
||||
nodes/use_node_type_suffixes=true
|
||||
meshes/ensure_tangents=true
|
||||
meshes/generate_lods=true
|
||||
meshes/create_shadow_meshes=true
|
||||
meshes/light_baking=1
|
||||
meshes/lightmap_texel_size=0.2
|
||||
meshes/force_disable_compression=false
|
||||
skins/use_named_skins=true
|
||||
animation/import=true
|
||||
animation/fps=30
|
||||
animation/trimming=false
|
||||
animation/remove_immutable_tracks=true
|
||||
animation/import_rest_as_RESET=false
|
||||
import_script/path=""
|
||||
materials/extract=0
|
||||
materials/extract_format=0
|
||||
materials/extract_path=""
|
||||
_subresources={}
|
||||
gltf/naming_version=2
|
||||
gltf/embedded_image_handling=1
|
||||
gltf/texture_map_mode=1
|
||||
|
|
@ -29,6 +29,7 @@ enum LineMode {
|
|||
@export_range(0.0, 3.0, 0.01) var maximum_slack: float = 0.65
|
||||
@export_range(2, 20, 1) var sag_interpolation_count: int = 8
|
||||
@export_range(0.1, 30.0, 0.1) var line_transition_speed: float = 7.0
|
||||
@export_range(0.002, 0.05, 0.001) var line_thickness: float = 0.016
|
||||
|
||||
@export_category("Cast")
|
||||
@export_range(0.1, 3.0, 0.05) var cast_travel_duration: float = 0.65
|
||||
|
|
@ -417,10 +418,46 @@ func _update_line(delta: float) -> void:
|
|||
_line_mesh.clear_surfaces()
|
||||
if rendered_points.size() < 2:
|
||||
return
|
||||
_line_mesh.surface_begin(Mesh.PRIMITIVE_LINES)
|
||||
for point_index: int in range(1, rendered_points.size()):
|
||||
_line_mesh.surface_add_vertex(to_local(rendered_points[point_index - 1]))
|
||||
_line_mesh.surface_add_vertex(to_local(rendered_points[point_index]))
|
||||
_draw_line_ribbon(rendered_points)
|
||||
|
||||
|
||||
func _draw_line_ribbon(points: PackedVector3Array) -> void:
|
||||
var camera: Camera3D = get_viewport().get_camera_3d()
|
||||
if camera == null:
|
||||
_line_mesh.surface_begin(Mesh.PRIMITIVE_LINE_STRIP)
|
||||
for point: Vector3 in points:
|
||||
_line_mesh.surface_add_vertex(to_local(point))
|
||||
_line_mesh.surface_end()
|
||||
return
|
||||
|
||||
var half_thickness: float = line_thickness * 0.5
|
||||
_line_mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLES)
|
||||
for point_index: int in range(1, points.size()):
|
||||
var start: Vector3 = points[point_index - 1]
|
||||
var end: Vector3 = points[point_index]
|
||||
var segment: Vector3 = end - start
|
||||
if segment.length_squared() <= 0.000001:
|
||||
continue
|
||||
var to_camera: Vector3 = (
|
||||
camera.global_position - start.lerp(end, 0.5)
|
||||
)
|
||||
var side: Vector3 = segment.normalized().cross(to_camera.normalized())
|
||||
if side.length_squared() <= 0.000001:
|
||||
side = segment.normalized().cross(Vector3.UP)
|
||||
if side.length_squared() <= 0.000001:
|
||||
side = segment.normalized().cross(Vector3.RIGHT)
|
||||
side = side.normalized() * half_thickness
|
||||
|
||||
var start_left: Vector3 = to_local(start - side)
|
||||
var start_right: Vector3 = to_local(start + side)
|
||||
var end_right: Vector3 = to_local(end + side)
|
||||
var end_left: Vector3 = to_local(end - side)
|
||||
_line_mesh.surface_add_vertex(start_left)
|
||||
_line_mesh.surface_add_vertex(start_right)
|
||||
_line_mesh.surface_add_vertex(end_right)
|
||||
_line_mesh.surface_add_vertex(start_left)
|
||||
_line_mesh.surface_add_vertex(end_right)
|
||||
_line_mesh.surface_add_vertex(end_left)
|
||||
_line_mesh.surface_end()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ roughness = 0.45
|
|||
|
||||
[sub_resource type="StandardMaterial3D" id="LineMaterial"]
|
||||
shading_mode = 0
|
||||
cull_mode = 2
|
||||
albedo_color = Color(0.9, 0.9, 0.82, 1)
|
||||
|
||||
[sub_resource type="CylinderMesh" id="CastTargetMesh"]
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ extends Node3D
|
|||
const WaterSurfaceMotionType = preload(
|
||||
"res://world/water_surface_motion.gd"
|
||||
)
|
||||
const LINE_THICKNESS: float = 0.016
|
||||
|
||||
signal return_completed
|
||||
|
||||
|
|
@ -38,6 +39,7 @@ func setup(owning_player: Player) -> void:
|
|||
_line.mesh = _line_mesh
|
||||
var line_material := StandardMaterial3D.new()
|
||||
line_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
line_material.cull_mode = BaseMaterial3D.CULL_DISABLED
|
||||
line_material.albedo_color = Color(0.9, 0.9, 0.82, 1.0)
|
||||
_line.material_override = line_material
|
||||
add_child(_line)
|
||||
|
|
@ -269,10 +271,50 @@ func _redraw_line() -> void:
|
|||
if tip == null:
|
||||
return
|
||||
_line_mesh.clear_surfaces()
|
||||
_line_mesh.surface_begin(Mesh.PRIMITIVE_LINE_STRIP)
|
||||
_line_mesh.surface_add_vertex(to_local(tip.global_position))
|
||||
var midpoint: Vector3 = tip.global_position.lerp(_target, 0.5)
|
||||
midpoint.y -= 0.12
|
||||
_line_mesh.surface_add_vertex(to_local(midpoint))
|
||||
_line_mesh.surface_add_vertex(to_local(_target))
|
||||
_draw_line_ribbon(PackedVector3Array([
|
||||
tip.global_position,
|
||||
midpoint,
|
||||
_target,
|
||||
]))
|
||||
|
||||
|
||||
func _draw_line_ribbon(points: PackedVector3Array) -> void:
|
||||
var camera: Camera3D = get_viewport().get_camera_3d()
|
||||
if camera == null:
|
||||
_line_mesh.surface_begin(Mesh.PRIMITIVE_LINE_STRIP)
|
||||
for point: Vector3 in points:
|
||||
_line_mesh.surface_add_vertex(to_local(point))
|
||||
_line_mesh.surface_end()
|
||||
return
|
||||
|
||||
var half_thickness: float = LINE_THICKNESS * 0.5
|
||||
_line_mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLES)
|
||||
for point_index: int in range(1, points.size()):
|
||||
var start: Vector3 = points[point_index - 1]
|
||||
var end: Vector3 = points[point_index]
|
||||
var segment: Vector3 = end - start
|
||||
if segment.length_squared() <= 0.000001:
|
||||
continue
|
||||
var to_camera: Vector3 = (
|
||||
camera.global_position - start.lerp(end, 0.5)
|
||||
)
|
||||
var side: Vector3 = segment.normalized().cross(to_camera.normalized())
|
||||
if side.length_squared() <= 0.000001:
|
||||
side = segment.normalized().cross(Vector3.UP)
|
||||
if side.length_squared() <= 0.000001:
|
||||
side = segment.normalized().cross(Vector3.RIGHT)
|
||||
side = side.normalized() * half_thickness
|
||||
|
||||
var start_left: Vector3 = to_local(start - side)
|
||||
var start_right: Vector3 = to_local(start + side)
|
||||
var end_right: Vector3 = to_local(end + side)
|
||||
var end_left: Vector3 = to_local(end - side)
|
||||
_line_mesh.surface_add_vertex(start_left)
|
||||
_line_mesh.surface_add_vertex(start_right)
|
||||
_line_mesh.surface_add_vertex(end_right)
|
||||
_line_mesh.surface_add_vertex(start_left)
|
||||
_line_mesh.surface_add_vertex(end_right)
|
||||
_line_mesh.surface_add_vertex(end_left)
|
||||
_line_mesh.surface_end()
|
||||
|
|
|
|||
|
|
@ -752,7 +752,7 @@ func _update_aim() -> void:
|
|||
func _raycast_from_pointer() -> Dictionary:
|
||||
if _local_player == null or not is_instance_valid(_local_player):
|
||||
return {}
|
||||
var camera: Camera3D = _local_player.get_gameplay_camera()
|
||||
var camera: Camera3D = _local_player.get_active_gameplay_camera()
|
||||
if camera == null or not camera.current:
|
||||
return {}
|
||||
var from: Vector3 = camera.project_ray_origin(_pointer_screen_position)
|
||||
|
|
@ -1684,7 +1684,9 @@ func _is_static_surface(value: Variant) -> bool:
|
|||
|
||||
func _surface_tangent(normal: Vector3) -> Vector3:
|
||||
var camera: Camera3D = (
|
||||
_local_player.get_gameplay_camera() if _local_player != null else null
|
||||
_local_player.get_active_gameplay_camera()
|
||||
if _local_player != null
|
||||
else null
|
||||
)
|
||||
var candidate: Vector3 = (
|
||||
camera.global_basis.x if camera != null else Vector3.RIGHT
|
||||
|
|
|
|||
131
player/player.gd
131
player/player.gd
|
|
@ -55,6 +55,14 @@ const CHARACTER_POCKET_IDLE_IDLE_ANIMATION: StringName = &"pocket_idle_idle"
|
|||
const CHARACTER_POCKET_IDLE_SHOW_ANIMATION: StringName = &"pocket_idle_show"
|
||||
const CHARACTER_POCKET_SHOW_SHOW_ANIMATION: StringName = &"pocket_show_show"
|
||||
const CHARACTER_POCKET_SHOW_IDLE_ANIMATION: StringName = &"pocket_show_idle"
|
||||
const CHARACTER_POCKET_SIT_IDLE_IDLE_ANIMATION: StringName = &"pocket_sit_idle_idle"
|
||||
const CHARACTER_POCKET_SIT_IDLE_SHOW_ANIMATION: StringName = &"pocket_sit_idle_show"
|
||||
const CHARACTER_POCKET_SIT_SHOW_SHOW_ANIMATION: StringName = &"pocket_sit_show_show"
|
||||
const CHARACTER_POCKET_SIT_SHOW_IDLE_ANIMATION: StringName = &"pocket_sit_show_idle"
|
||||
const CHARACTER_POCKET_WALKING_IDLE_IDLE_ANIMATION: StringName = &"pocket_walking_idle_idle"
|
||||
const CHARACTER_POCKET_WALKING_IDLE_SHOW_ANIMATION: StringName = &"pocket_walking_idle_show"
|
||||
const CHARACTER_POCKET_WALKING_SHOW_SHOW_ANIMATION: StringName = &"pocket_walking_show_show"
|
||||
const CHARACTER_POCKET_WALKING_SHOW_IDLE_ANIMATION: StringName = &"pocket_walking_show_idle"
|
||||
const CHARACTER_FISHING_ANIMATION: StringName = &"fishing"
|
||||
const CHARACTER_FISHING_SIT_ANIMATION: StringName = &"fishing_sit"
|
||||
const CHARACTER_FIGHTING_ANIMATION: StringName = &"fighting"
|
||||
|
|
@ -239,8 +247,8 @@ class ShowcaseCameraSnapshot:
|
|||
|
||||
@export_category("Movement")
|
||||
@export var walk_speed: float = 2.25
|
||||
@export var sprint_speed: float = 7.2
|
||||
@export var sneak_speed: float = 1.8
|
||||
@export var sprint_speed: float = 4.5
|
||||
@export var sneak_speed: float = 1.125
|
||||
@export var slow_walk_speed: float = 2.9
|
||||
@export_range(0.1, 20.0, 0.1) var jump_velocity: float = 4.6
|
||||
@export_range(0.1, 5.0, 0.05) var upward_gravity_multiplier: float = 1.35
|
||||
|
|
@ -324,6 +332,7 @@ var _free_camera: Camera3D
|
|||
var _free_camera_yaw: float = 0.0
|
||||
var _free_camera_pitch: float = 0.0
|
||||
var _movement_enabled: bool = true
|
||||
var _local_input_suppressors: Dictionary[StringName, bool] = {}
|
||||
var _water_recovery_active: bool = false
|
||||
var _remote_recovery_presentation_active: bool = false
|
||||
var _remote_recovery_visual_origin: Vector3
|
||||
|
|
@ -382,6 +391,8 @@ var _fishing_rod_tip: Marker3D
|
|||
var _active_item_is_rod: bool = false
|
||||
var _controller_mapping_manager: ControllerMappingManagerType
|
||||
|
||||
@onready var _sprint_dust: CPUParticles3D = %SprintDust
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if (
|
||||
|
|
@ -480,7 +491,7 @@ func _physics_process(delta: float) -> void:
|
|||
_network_jump_pending = false
|
||||
return
|
||||
var jump_requested: bool = (
|
||||
_movement_enabled
|
||||
_is_movement_input_enabled()
|
||||
and not _free_camera_active
|
||||
and (
|
||||
(local_control_enabled and Input.is_action_just_pressed("jump"))
|
||||
|
|
@ -513,7 +524,7 @@ func _physics_process(delta: float) -> void:
|
|||
velocity.y = jump_velocity
|
||||
_network_jump_pending = false
|
||||
|
||||
if not _movement_enabled:
|
||||
if not _is_movement_input_enabled():
|
||||
velocity.x = 0.0
|
||||
velocity.z = 0.0
|
||||
move_and_slide()
|
||||
|
|
@ -560,6 +571,7 @@ func _physics_process(delta: float) -> void:
|
|||
|
||||
func _process(delta: float) -> void:
|
||||
_update_character_animation()
|
||||
_update_sprint_dust()
|
||||
# The hand bone supplies the attachment position, but its animated wrist
|
||||
# rotation should not turn the flat fish/catch artwork edge-on. Keep each
|
||||
# display aligned with the character's facing while it follows the hand.
|
||||
|
|
@ -592,7 +604,7 @@ func _process(delta: float) -> void:
|
|||
if _camera_dragging and not Input.is_action_pressed("camera_drag"):
|
||||
_set_camera_dragging(false)
|
||||
|
||||
if _camera_input_enabled:
|
||||
if _is_camera_input_enabled():
|
||||
var stick: Vector2 = _get_controller_camera_stick()
|
||||
if stick.length() > controller_camera_deadzone:
|
||||
if (
|
||||
|
|
@ -624,6 +636,26 @@ func _process(delta: float) -> void:
|
|||
)
|
||||
|
||||
|
||||
func _update_sprint_dust() -> void:
|
||||
if _sprint_dust == null:
|
||||
return
|
||||
var horizontal_speed_squared: float = (
|
||||
velocity.x * velocity.x + velocity.z * velocity.z
|
||||
)
|
||||
var fastest_non_sprint_speed := maxf(
|
||||
walk_speed,
|
||||
maxf(sneak_speed, slow_walk_speed)
|
||||
)
|
||||
var running_threshold := fastest_non_sprint_speed + 0.5
|
||||
_sprint_dust.emitting = (
|
||||
is_on_floor()
|
||||
and not _sitting
|
||||
and not _water_recovery_active
|
||||
and horizontal_speed_squared
|
||||
> running_threshold * running_threshold
|
||||
)
|
||||
|
||||
|
||||
func _get_controller_camera_stick() -> Vector2:
|
||||
if _controller_mapping_manager != null:
|
||||
return Vector2(
|
||||
|
|
@ -819,6 +851,14 @@ func _on_character_animation_finished(animation_name: StringName) -> void:
|
|||
CHARACTER_POCKET_IDLE_SHOW_ANIMATION,
|
||||
CHARACTER_POCKET_SHOW_SHOW_ANIMATION,
|
||||
CHARACTER_POCKET_SHOW_IDLE_ANIMATION,
|
||||
CHARACTER_POCKET_SIT_IDLE_IDLE_ANIMATION,
|
||||
CHARACTER_POCKET_SIT_IDLE_SHOW_ANIMATION,
|
||||
CHARACTER_POCKET_SIT_SHOW_SHOW_ANIMATION,
|
||||
CHARACTER_POCKET_SIT_SHOW_IDLE_ANIMATION,
|
||||
CHARACTER_POCKET_WALKING_IDLE_IDLE_ANIMATION,
|
||||
CHARACTER_POCKET_WALKING_IDLE_SHOW_ANIMATION,
|
||||
CHARACTER_POCKET_WALKING_SHOW_SHOW_ANIMATION,
|
||||
CHARACTER_POCKET_WALKING_SHOW_IDLE_ANIMATION,
|
||||
]:
|
||||
if _pocket_visual_active:
|
||||
_complete_pocket_visual()
|
||||
|
|
@ -904,7 +944,7 @@ func is_sitting() -> bool:
|
|||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if not local_control_enabled or not _camera_input_enabled:
|
||||
if not local_control_enabled or not _is_camera_input_enabled():
|
||||
return
|
||||
if event.is_action_pressed("toggle_free_camera"):
|
||||
_set_free_camera_active(not _free_camera_active)
|
||||
|
|
@ -1056,9 +1096,10 @@ func _update_free_camera_physics() -> void:
|
|||
Input.get_action_strength("jump")
|
||||
- Input.get_action_strength("sneak")
|
||||
)
|
||||
var yaw_basis := Basis(Vector3.UP, _free_camera_yaw)
|
||||
var movement: Vector3 = (
|
||||
_free_camera.global_basis.x * input_vector.x
|
||||
+ _free_camera.global_basis.z * input_vector.y
|
||||
yaw_basis.x * input_vector.x
|
||||
+ yaw_basis.z * input_vector.y
|
||||
+ Vector3.UP * vertical_input
|
||||
)
|
||||
if movement.length_squared() > 1.0:
|
||||
|
|
@ -1077,8 +1118,8 @@ func _rotate_free_camera(delta_rotation: Vector2) -> void:
|
|||
var vertical_direction: float = -1.0 if invert_camera_y else 1.0
|
||||
_free_camera_pitch = clampf(
|
||||
_free_camera_pitch - delta_rotation.y * vertical_direction,
|
||||
deg_to_rad(minimum_pitch_degrees),
|
||||
deg_to_rad(maximum_pitch_degrees),
|
||||
deg_to_rad(-90.0),
|
||||
deg_to_rad(90.0),
|
||||
)
|
||||
_free_camera.global_rotation = Vector3(
|
||||
_free_camera_pitch,
|
||||
|
|
@ -1087,6 +1128,12 @@ func _rotate_free_camera(delta_rotation: Vector2) -> void:
|
|||
)
|
||||
|
||||
|
||||
func get_active_gameplay_camera() -> Camera3D:
|
||||
if _free_camera_active and _free_camera != null:
|
||||
return _free_camera
|
||||
return _camera
|
||||
|
||||
|
||||
func _set_target_zoom(value: float) -> void:
|
||||
_target_zoom = clampf(value, minimum_zoom, maximum_zoom)
|
||||
|
||||
|
|
@ -1132,7 +1179,11 @@ func configure_network_remote(authoritative_simulation: bool) -> void:
|
|||
func capture_network_input(sequence: int) -> Dictionary:
|
||||
if _sitting_intent_pending and _sitting_intent_sequence < 0:
|
||||
_sitting_intent_sequence = sequence
|
||||
if not _movement_enabled or _water_recovery_active or _free_camera_active:
|
||||
if (
|
||||
not _is_movement_input_enabled()
|
||||
or _water_recovery_active
|
||||
or _free_camera_active
|
||||
):
|
||||
return {
|
||||
"sequence": sequence,
|
||||
"axis": [0.0, 0.0],
|
||||
|
|
@ -1363,6 +1414,26 @@ func is_movement_enabled() -> bool:
|
|||
return _movement_enabled
|
||||
|
||||
|
||||
func set_local_input_suppressed(owner: StringName, suppressed: bool) -> void:
|
||||
if owner.is_empty():
|
||||
return
|
||||
if suppressed:
|
||||
_local_input_suppressors[owner] = true
|
||||
velocity.x = 0.0
|
||||
velocity.z = 0.0
|
||||
_set_camera_dragging(false)
|
||||
else:
|
||||
_local_input_suppressors.erase(owner)
|
||||
|
||||
|
||||
func _is_movement_input_enabled() -> bool:
|
||||
return _movement_enabled and _local_input_suppressors.is_empty()
|
||||
|
||||
|
||||
func _is_camera_input_enabled() -> bool:
|
||||
return _camera_input_enabled and _local_input_suppressors.is_empty()
|
||||
|
||||
|
||||
func set_camera_input_enabled(enabled: bool) -> void:
|
||||
_camera_input_enabled = enabled
|
||||
if not enabled:
|
||||
|
|
@ -1642,7 +1713,33 @@ func _begin_pocket_visual(
|
|||
midpoint_callback: Callable = Callable(),
|
||||
) -> void:
|
||||
var animation_name: StringName
|
||||
if starts_in_show_pose:
|
||||
if _sitting:
|
||||
if starts_in_show_pose:
|
||||
animation_name = (
|
||||
CHARACTER_POCKET_SIT_SHOW_SHOW_ANIMATION
|
||||
if ends_in_show_pose
|
||||
else CHARACTER_POCKET_SIT_SHOW_IDLE_ANIMATION
|
||||
)
|
||||
else:
|
||||
animation_name = (
|
||||
CHARACTER_POCKET_SIT_IDLE_SHOW_ANIMATION
|
||||
if ends_in_show_pose
|
||||
else CHARACTER_POCKET_SIT_IDLE_IDLE_ANIMATION
|
||||
)
|
||||
elif _is_moving_for_pocket_visual():
|
||||
if starts_in_show_pose:
|
||||
animation_name = (
|
||||
CHARACTER_POCKET_WALKING_SHOW_SHOW_ANIMATION
|
||||
if ends_in_show_pose
|
||||
else CHARACTER_POCKET_WALKING_SHOW_IDLE_ANIMATION
|
||||
)
|
||||
else:
|
||||
animation_name = (
|
||||
CHARACTER_POCKET_WALKING_IDLE_SHOW_ANIMATION
|
||||
if ends_in_show_pose
|
||||
else CHARACTER_POCKET_WALKING_IDLE_IDLE_ANIMATION
|
||||
)
|
||||
elif starts_in_show_pose:
|
||||
animation_name = (
|
||||
CHARACTER_POCKET_SHOW_SHOW_ANIMATION
|
||||
if ends_in_show_pose
|
||||
|
|
@ -1665,8 +1762,7 @@ func _begin_pocket_visual(
|
|||
else:
|
||||
_cancel_pocket_visual()
|
||||
if (
|
||||
_sitting
|
||||
or animation_name.is_empty()
|
||||
animation_name.is_empty()
|
||||
or _character_animation_player == null
|
||||
or not _character_animation_player.has_animation(animation_name)
|
||||
):
|
||||
|
|
@ -1689,6 +1785,11 @@ func _begin_pocket_visual(
|
|||
_schedule_pocket_visual_midpoint(midpoint_generation, animation_name)
|
||||
|
||||
|
||||
func _is_moving_for_pocket_visual() -> bool:
|
||||
var horizontal_velocity := Vector2(velocity.x, velocity.z)
|
||||
return horizontal_velocity.length_squared() > 0.0025
|
||||
|
||||
|
||||
func _schedule_pocket_visual_midpoint(
|
||||
generation: int,
|
||||
animation_name: StringName,
|
||||
|
|
@ -1758,6 +1859,7 @@ func begin_catch_showcase(fish_catch: FishCatchType) -> void:
|
|||
if _pocket_visual_target == PocketVisualTarget.CATCH_SHOWCASE:
|
||||
_cancel_pocket_visual()
|
||||
_showcase_animation_active = true
|
||||
set_fishing_visual(false)
|
||||
_kill_showcase_camera_tween()
|
||||
_kill_showcase_restore_tween()
|
||||
_capture_showcase_camera_snapshot()
|
||||
|
|
@ -1785,6 +1887,7 @@ func begin_remote_catch_showcase(fish_catch: FishCatchType) -> void:
|
|||
return
|
||||
end_catch_showcase(Callable(), true)
|
||||
_showcase_animation_active = true
|
||||
set_fishing_visual(false)
|
||||
_showcase_rod_visibility = _fishing_rod.visible
|
||||
_showcase_rod_state_stored = true
|
||||
_fishing_rod.visible = false
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=18 format=3]
|
||||
[gd_scene load_steps=21 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://player/player.gd" id="1_script"]
|
||||
[ext_resource type="Script" path="res://inventory/fish_inventory.gd" id="2_inventory"]
|
||||
|
|
@ -24,6 +24,17 @@ height = 1.8
|
|||
material = ExtResource("12_blob_shadow")
|
||||
size = Vector2(1.15, 0.72)
|
||||
|
||||
[sub_resource type="Shader" id="SprintDustShader"]
|
||||
code = "shader_type spatial;\nrender_mode blend_mix, depth_draw_never, cull_disabled, unshaded, shadows_disabled;\n\nuniform vec4 dust_color : source_color = vec4(0.47, 0.39, 0.27, 0.42);\nvarying float particle_phase;\n\nvoid vertex() {\n\tparticle_phase = INSTANCE_CUSTOM.y;\n\tMODELVIEW_MATRIX = VIEW_MATRIX * mat4(\n\t\tINV_VIEW_MATRIX[0],\n\t\tINV_VIEW_MATRIX[1],\n\t\tINV_VIEW_MATRIX[2],\n\t\tMODEL_MATRIX[3]\n\t);\n}\n\nvoid fragment() {\n\tvec2 centered_uv = UV * 2.0 - 1.0;\n\tfloat angle = atan(centered_uv.y, centered_uv.x);\n\tfloat edge_wobble = sin(angle * 5.0 + particle_phase * 8.0) * 0.07;\n\tfloat radius = length(centered_uv);\n\tfloat soft_shape = 1.0 - smoothstep(0.48 + edge_wobble, 1.0 + edge_wobble, radius);\n\tfloat fade_in = smoothstep(0.0, 0.12, particle_phase);\n\tfloat fade_out = 1.0 - smoothstep(0.38, 1.0, particle_phase);\n\tALBEDO = dust_color.rgb;\n\tALPHA = soft_shape * dust_color.a * fade_in * fade_out;\n}\n"
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="SprintDustMaterial"]
|
||||
render_priority = 1
|
||||
shader = SubResource("SprintDustShader")
|
||||
|
||||
[sub_resource type="QuadMesh" id="SprintDustMesh"]
|
||||
material = SubResource("SprintDustMaterial")
|
||||
size = Vector2(0.38, 0.28)
|
||||
|
||||
[node name="Player" type="CharacterBody3D"]
|
||||
collision_layer = 2
|
||||
collision_mask = 1
|
||||
|
|
@ -55,6 +66,28 @@ rotation_degrees = Vector3(-90, 0, 0)
|
|||
cast_shadow = 0
|
||||
mesh = SubResource("GroundShadowMesh")
|
||||
|
||||
[node name="SprintDust" type="CPUParticles3D" parent="."]
|
||||
unique_name_in_owner = true
|
||||
position = Vector3(0, 0.08, 0)
|
||||
emitting = false
|
||||
amount = 16
|
||||
lifetime = 0.65
|
||||
randomness = 0.55
|
||||
visibility_aabb = AABB(-4, -2, -4, 8, 5, 8)
|
||||
local_coords = false
|
||||
emission_shape = 3
|
||||
emission_box_extents = Vector3(0.24, 0.03, 0.16)
|
||||
direction = Vector3(0, 1, 0)
|
||||
spread = 65.0
|
||||
gravity = Vector3(0, -0.8, 0)
|
||||
initial_velocity_min = 0.35
|
||||
initial_velocity_max = 0.8
|
||||
angular_velocity_min = -90.0
|
||||
angular_velocity_max = 90.0
|
||||
scale_amount_min = 0.35
|
||||
scale_amount_max = 0.72
|
||||
mesh = SubResource("SprintDustMesh")
|
||||
|
||||
[node name="CharacterRig" parent="Visuals" instance=ExtResource("13_character")]
|
||||
rotation_degrees = Vector3(0, 180, 0)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,18 +7,19 @@ const SPEECH_SECONDS: float = 6.0
|
|||
const DRAFT_SAVE_DELAY: float = 0.4
|
||||
const IDLE_ALPHA: float = 0.58
|
||||
const CHAT_SURFACE_COLOR := Color(0.025, 0.13, 0.19, 0.94)
|
||||
const ORIGINAL_PANEL_WIDTH: float = 390.0
|
||||
const PANEL_WIDTH: float = ORIGINAL_PANEL_WIDTH * 0.85
|
||||
const PANEL_WIDTH: float = 270.0
|
||||
const COMPACT_HEIGHT: float = 250.0
|
||||
const BOTTOM_MARGIN: float = 94.0
|
||||
const BOTTOM_MARGIN: float = 12.0
|
||||
const EXPANDED_TOP_MARGIN: float = 94.0
|
||||
const MIN_TOP_MARGIN: float = 24.0
|
||||
const MIN_HISTORY_HEIGHT: float = 92.0
|
||||
const HISTORY_FONT_SIZE: int = 17
|
||||
const HISTORY_FONT_SIZE: int = 20
|
||||
const INPUT_FONT_SIZE: int = 23
|
||||
const HINT_FONT_SIZE: int = 12
|
||||
const HISTORY_INPUT_GAP: int = 8
|
||||
const HANDLE_SIZE := Vector2(34, 42)
|
||||
const HANDLE_GAP: float = 6.0
|
||||
const HANDLE_TOP_INSET: float = 18.0
|
||||
const COLLAPSED_REVEAL_WIDTH: float = 8.0
|
||||
const HINT_EDGE_MARGIN: float = 4.0
|
||||
const SPEECH_BUBBLE_WIDTH: float = 320.0
|
||||
|
|
@ -105,8 +106,6 @@ var _last_message_time: float = -INF
|
|||
var _target_alpha: float = -1.0
|
||||
var _send_pending: bool = false
|
||||
var _pending_send_body: String = ""
|
||||
var _prior_movement: bool = true
|
||||
var _prior_camera: bool = true
|
||||
var _controller_refocused: bool = false
|
||||
var _input_lock_applied: bool = false
|
||||
var _last_submit_frame: int = -1
|
||||
|
|
@ -202,13 +201,10 @@ func open_chat() -> void:
|
|||
if _presentation_state == PresentationState.COLLAPSED:
|
||||
_set_presentation_state(_visible_state_before_collapse, true, true)
|
||||
_controller_refocused = false
|
||||
_prior_movement = _player.is_movement_enabled()
|
||||
_prior_camera = _player.is_camera_input_enabled()
|
||||
_opened = true
|
||||
text_entry_ownership_changed.emit(true)
|
||||
_input_lock_applied = true
|
||||
_player.set_movement_enabled(false)
|
||||
_player.set_camera_input_enabled(false)
|
||||
_player.set_local_input_suppressed(INPUT_OWNER, true)
|
||||
_fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, true)
|
||||
# Clear the last authoritative input immediately. Merely disabling local
|
||||
# prediction would let a remote host continue the last held movement.
|
||||
|
|
@ -249,8 +245,7 @@ func close_chat() -> void:
|
|||
_entry.release_focus()
|
||||
_entry.hide()
|
||||
if _input_lock_applied:
|
||||
_player.set_movement_enabled(_prior_movement)
|
||||
_player.set_camera_input_enabled(_prior_camera)
|
||||
_player.set_local_input_suppressed(INPUT_OWNER, false)
|
||||
_fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, false)
|
||||
_input_lock_applied = false
|
||||
if ownership_was_active:
|
||||
|
|
@ -365,6 +360,9 @@ func _process(_delta: float) -> void:
|
|||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
if _input_lock_applied and is_instance_valid(_player):
|
||||
_player.set_local_input_suppressed(INPUT_OWNER, false)
|
||||
_input_lock_applied = false
|
||||
_flush_draft()
|
||||
|
||||
|
||||
|
|
@ -541,7 +539,6 @@ func _build_ui() -> void:
|
|||
add_child(_unread_indicator)
|
||||
_hint = Label.new()
|
||||
_hint.name = "ChatHint"
|
||||
_hint.text = "Press Enter to chat"
|
||||
_hint.size = Vector2(180, 18)
|
||||
_hint.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_hint.add_theme_font_override("font", UtilityPageStyle.TuffyFont)
|
||||
|
|
@ -809,7 +806,7 @@ func _handle_time_command(parts: PackedStringArray) -> void:
|
|||
"World time set to %s (%s)."
|
||||
% [phase_name, _world_time.get_clock_text()]
|
||||
)
|
||||
call_deferred("close_chat")
|
||||
close_chat()
|
||||
|
||||
|
||||
func _on_local_message_confirmed(message: Dictionary) -> void:
|
||||
|
|
@ -824,7 +821,7 @@ func _on_local_message_confirmed(message: Dictionary) -> void:
|
|||
_entry.clear()
|
||||
_set_status("")
|
||||
_flush_draft()
|
||||
call_deferred("close_chat")
|
||||
close_chat()
|
||||
|
||||
|
||||
func _on_message(message: Dictionary) -> void:
|
||||
|
|
@ -923,6 +920,8 @@ func _on_rejected(message: String) -> void:
|
|||
_set_status(message)
|
||||
if not _opened:
|
||||
open_chat()
|
||||
else:
|
||||
call_deferred("_focus_entry_after_open")
|
||||
|
||||
|
||||
func _refresh_history() -> void:
|
||||
|
|
@ -1244,7 +1243,7 @@ func _layout_presentation(animate: bool) -> void:
|
|||
if layout_state == PresentationState.EXPANDED:
|
||||
height = maxf(
|
||||
MIN_HISTORY_HEIGHT,
|
||||
viewport_height - 2.0 * bottom_margin,
|
||||
viewport_height - bottom_margin - EXPANDED_TOP_MARGIN,
|
||||
)
|
||||
height = minf(
|
||||
height,
|
||||
|
|
@ -1263,10 +1262,7 @@ func _layout_presentation(animate: bool) -> void:
|
|||
else 0.0
|
||||
)
|
||||
panel_y = bottom - height
|
||||
var handle_stack_height := HANDLE_SIZE.y * 2.0 + HANDLE_GAP
|
||||
var handle_stack_top := (
|
||||
bottom - COMPACT_HEIGHT * 0.5 - handle_stack_height * 0.5
|
||||
)
|
||||
var handle_stack_top := panel_y + HANDLE_TOP_INSET
|
||||
var handle_x: float = (
|
||||
panel_x - HANDLE_SIZE.x
|
||||
if _dock_right
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ const FishingSpotType = preload("res://fishing/fishing_spot.gd")
|
|||
|
||||
const DESKTOP_REFERENCE_SIZE := Vector2(1280.0, 720.0)
|
||||
const COMPACT_REFERENCE_SIZE := Vector2(640.0, 480.0)
|
||||
const HOTBAR_PRESENTATION_SCALE: float = 0.60
|
||||
const HOTBAR_CANONICAL_POSITION := Vector2(256.0, 288.0)
|
||||
const HOTBAR_MENU_POSITION := Vector2(80.0, 198.0)
|
||||
const HOTBAR_PRESENTATION_SCALE: float = 1.00
|
||||
const HOTBAR_CANONICAL_POSITION := Vector2.ZERO
|
||||
const HOTBAR_MENU_POSITION := Vector2(-136.0, -90.0)
|
||||
const HOTBAR_GAMEPLAY_Z_INDEX: int = 35
|
||||
# PlayerMenu is z=30 and its authored inventory panels are relative z=50.
|
||||
const HOTBAR_MENU_Z_INDEX: int = 90
|
||||
|
|
|
|||
|
|
@ -3059,7 +3059,7 @@ func _on_sort_selected(index: int, source: OptionButton) -> void:
|
|||
|
||||
|
||||
func _on_cooler_sort_selected(sort_id: int) -> void:
|
||||
_sort_mode = sort_id
|
||||
_sort_mode = sort_id as SortMode
|
||||
_sort_option.select(_sort_mode)
|
||||
_cooler_sort_option.select(_sort_mode)
|
||||
_refresh_inventory()
|
||||
|
|
@ -3607,11 +3607,11 @@ func _configure_cooler_fish_focus() -> void:
|
|||
if not _shop_cooler_context_active:
|
||||
_cooler_sub_tab.focus_neighbor_bottom = NodePath()
|
||||
return
|
||||
var top_control: Control = (
|
||||
_cooler_sort_option
|
||||
if _shop_cooler_context_active
|
||||
else _cooler_sub_tab
|
||||
)
|
||||
var top_control: Control
|
||||
if _shop_cooler_context_active:
|
||||
top_control = _cooler_sort_option
|
||||
else:
|
||||
top_control = _cooler_sub_tab
|
||||
if not _shop_cooler_context_active:
|
||||
_cooler_sub_tab.focus_neighbor_bottom = _cooler_sub_tab.get_path_to(
|
||||
controls.front()
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ var fishing_shop_path: NodePath = (
|
|||
@export_group("Terrain Collision")
|
||||
@export var rebuild_terrain_collision_on_ready: bool = false
|
||||
@export_group("Foliage Wind")
|
||||
@export_range(0.0, 0.4, 0.005) var foliage_wind_strength: float = 0.12
|
||||
@export_range(0.0, 0.4, 0.005) var foliage_wind_strength: float = 0.36
|
||||
@export_range(0.0, 4.0, 0.05) var foliage_wind_speed: float = 0.9
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue