bcp de characters
This commit is contained in:
@@ -5,7 +5,8 @@ extends Camera3D
|
||||
var sensitivity = 0.008
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseMotion and not get_tree().paused:
|
||||
player.rotate_y(-event.relative.x * sensitivity)
|
||||
player.camera_pov.rotate_x(-event.relative.y * sensitivity)
|
||||
player.camera_pov.rotation.x=clamp(player.camera_pov.rotation.x,deg_to_rad(-90),deg_to_rad(90))
|
||||
if !player.third_person:
|
||||
if event is InputEventMouseMotion and not get_tree().paused:
|
||||
player.rotate_y(-event.relative.x * sensitivity)
|
||||
player.camera_pov.rotate_x(-event.relative.y * sensitivity)
|
||||
player.camera_pov.rotation.x=clamp(player.camera_pov.rotation.x,deg_to_rad(-90),deg_to_rad(90))
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
extends Node3D
|
||||
|
||||
@export var window_inside : bool = false
|
||||
|
||||
@onready var window: MeshInstance3D = $window
|
||||
@onready var window_001: MeshInstance3D = $window_001
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if !window_inside:
|
||||
var material = window.get_active_material(0)
|
||||
material.emission_enabled = false
|
||||
var material1 = window_001.get_active_material(0)
|
||||
material1.emission_enabled = true
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://dr3423c5e1enj
|
||||
@@ -0,0 +1,20 @@
|
||||
extends Node3D
|
||||
|
||||
@onready var cinematic_camera: Node3D = $cinematic_camera
|
||||
@onready var car_anim_player: AnimationPlayer = $cinematic_camera/car/AnimationPlayer
|
||||
@onready var player_anim_player: AnimationPlayer = $cinematic_camera/player_driving/AnimationPlayer
|
||||
|
||||
func _ready() -> void:
|
||||
Dialogic.start("timeline")
|
||||
Dialogic.timeline_ended.connect( change_scene )
|
||||
|
||||
func change_scene():
|
||||
Transition.change_scene("res://scenes/main.scn")
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
car_anim_player.play("driving")
|
||||
player_anim_player.play("driving")
|
||||
if cinematic_camera.global_position.x < 490:
|
||||
cinematic_camera.global_position.x += 16 * delta
|
||||
else:
|
||||
cinematic_camera.global_position.x += 10 * delta
|
||||
@@ -0,0 +1 @@
|
||||
uid://c2tdcxorciwkd
|
||||
@@ -0,0 +1,78 @@
|
||||
extends CharacterBody3D
|
||||
|
||||
@export var have_dialog : bool = false
|
||||
@export var alrdy_talking : bool = false
|
||||
@export var can_wander : bool = false
|
||||
|
||||
@onready var sprite_label: Sprite3D = $Sprite3D
|
||||
@onready var anim_player: AnimationPlayer = $character/AnimationPlayer
|
||||
|
||||
# navigation
|
||||
@export var dest_array:Array[Marker3D]
|
||||
@onready var navigation_agent: NavigationAgent3D = $NavigationAgent3D
|
||||
@onready var idle_timer: Timer = $Timer
|
||||
enum NPCSTATE {
|
||||
IDLE,
|
||||
WALKING
|
||||
}
|
||||
var cur_state : NPCSTATE
|
||||
var cur_destination: Marker3D
|
||||
var speed = 2
|
||||
|
||||
func _ready() -> void:
|
||||
sprite_label.visible = false
|
||||
if alrdy_talking:
|
||||
await get_tree().create_timer( randf_range(1.0, 2.5) ).timeout
|
||||
anim_player.play("RootAction")
|
||||
|
||||
if can_wander:
|
||||
_set_state( NPCSTATE.IDLE )
|
||||
|
||||
func _on_timer_timeout() -> void:
|
||||
decide_next_state()
|
||||
|
||||
func _set_state(new_state:NPCSTATE):
|
||||
cur_state = new_state
|
||||
match cur_state:
|
||||
NPCSTATE.IDLE:
|
||||
idle_timer.start( randf_range(1, 2) )
|
||||
anim_player.play("RootAction", -1, 10)
|
||||
NPCSTATE.WALKING:
|
||||
anim_player.play("RootAction_001", -1, 2.5)
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
match cur_state:
|
||||
NPCSTATE.IDLE:
|
||||
pass
|
||||
NPCSTATE.WALKING:
|
||||
var next_path_pos:Vector3 = navigation_agent.get_next_path_position()
|
||||
var new_vel : Vector3 = global_position.direction_to( next_path_pos ) * speed
|
||||
velocity = new_vel
|
||||
var look_at_target : Vector3 = Vector3( next_path_pos.x, global_position.y, next_path_pos.z )
|
||||
if not global_position.is_equal_approx( look_at_target ):
|
||||
look_at( look_at_target )
|
||||
|
||||
move_and_slide()
|
||||
|
||||
|
||||
|
||||
func decide_next_state():
|
||||
if cur_state == NPCSTATE.IDLE:
|
||||
cur_destination = dest_array.pick_random()
|
||||
navigation_agent.target_position = cur_destination.global_position
|
||||
_set_state( NPCSTATE.WALKING )
|
||||
|
||||
func _on_area_3d_body_entered(body: Node3D) -> void:
|
||||
if have_dialog and body.is_in_group("player"):
|
||||
sprite_label.visible = true
|
||||
body.set_dialog_mode( global_position )
|
||||
|
||||
func _on_area_3d_body_exited(body: Node3D) -> void:
|
||||
if have_dialog and body.is_in_group("player"):
|
||||
sprite_label.visible = false
|
||||
body.set_dialog_mode()
|
||||
|
||||
|
||||
func _on_navigation_agent_3d_navigation_finished() -> void:
|
||||
_set_state( NPCSTATE.IDLE )
|
||||
@@ -0,0 +1 @@
|
||||
uid://fknk7s075mxu
|
||||
@@ -0,0 +1,8 @@
|
||||
extends MultiMeshInstance3D
|
||||
|
||||
func _ready() -> void:
|
||||
var characters_seating:Array = get_tree().get_nodes_in_group("char_seat")
|
||||
for char in characters_seating:
|
||||
char.get_node("AnimationPlayer").play("mixamo_com")
|
||||
await get_tree().create_timer( randf_range(0.1, 1.5) ).timeout
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://bgau5civo1ppi
|
||||
@@ -0,0 +1,67 @@
|
||||
extends CharacterBody3D
|
||||
|
||||
@onready var sprite_label: Sprite3D = $Sprite3D
|
||||
@onready var label: Label = $Sprite3D/SubViewport/Label
|
||||
@onready var area_3d: Area3D = $Area3D
|
||||
|
||||
@onready var anim_player: AnimationPlayer = $character/AnimationPlayer
|
||||
@onready var camera_zoom: Camera3D = $CameraZoom
|
||||
@onready var player: CharacterBody3D = $"../../Player"
|
||||
@onready var area_3d_zoom: Area3D = $Area3DZoom
|
||||
|
||||
@onready var indicateur_tarte: CSGTorus3D = $"../indicateur_tarte"
|
||||
@onready var client_spawner: Node3D = $"../client_spawner"
|
||||
|
||||
var zoom_active : bool = false
|
||||
|
||||
func zoom_dialog():
|
||||
label.text = Sauvegarde.get_text("coco" + str( 0 ) )
|
||||
camera_zoom.make_current()
|
||||
zoom_active = true
|
||||
anim_player.play("RootAction")
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if zoom_active:
|
||||
camera_zoom.position.z += 25 * delta
|
||||
if camera_zoom.position.z > -4:
|
||||
zoom_active = false
|
||||
await get_tree().create_timer( 6 ).timeout
|
||||
player.camera.make_current()
|
||||
player.can_play = true
|
||||
|
||||
anim_player.stop()
|
||||
|
||||
|
||||
func dialog():
|
||||
rotation.y = deg_to_rad( 180 )
|
||||
anim_player.play("RootAction")
|
||||
|
||||
for i in range(1,3):
|
||||
label.text = Sauvegarde.get_text("coco" + str( i ) )
|
||||
await get_tree().create_timer( 1 ).timeout
|
||||
|
||||
|
||||
rotation.y = deg_to_rad( -90 )
|
||||
anim_player.stop()
|
||||
sprite_label.visible = false
|
||||
player.set_dialog_mode()
|
||||
|
||||
indicateur_tarte.visible = true
|
||||
client_spawner.start_timer()
|
||||
await get_tree().create_timer( 10 ).timeout
|
||||
indicateur_tarte.visible = false
|
||||
|
||||
|
||||
func _on_area_3d_body_entered(body: Node3D) -> void:
|
||||
if body.is_in_group("player"):
|
||||
sprite_label.visible = true
|
||||
player.set_dialog_mode( global_position, 0)
|
||||
dialog()
|
||||
area_3d.queue_free()
|
||||
|
||||
|
||||
func _on_area_3d_zoom_body_entered(body: Node3D) -> void:
|
||||
if body.is_in_group("player"):
|
||||
player.can_play = false
|
||||
area_3d_zoom.queue_free()
|
||||
zoom_dialog()
|
||||
@@ -0,0 +1 @@
|
||||
uid://bv2vqmllmek6f
|
||||
@@ -0,0 +1,33 @@
|
||||
extends CharacterBody3D
|
||||
|
||||
@onready var sprite_label: Sprite3D = $Sprite3D
|
||||
@onready var label: Label = $Sprite3D/SubViewport/Label
|
||||
@onready var area_3d: Area3D = $Area3D
|
||||
|
||||
@onready var doggo_anim_player: AnimationPlayer = $dog/AnimationPlayer
|
||||
@onready var anim_player: AnimationPlayer = $character/AnimationPlayer
|
||||
|
||||
var player
|
||||
|
||||
func _ready() -> void:
|
||||
doggo_anim_player.play("sleep")
|
||||
|
||||
func dialog():
|
||||
anim_player.play("RootAction")
|
||||
for i in range(1, 7):
|
||||
label.text = Sauvegarde.get_text("neige" + str( i ) )
|
||||
await get_tree().create_timer( 7 ).timeout
|
||||
|
||||
anim_player.stop()
|
||||
sprite_label.visible = false
|
||||
player.set_dialog_mode()
|
||||
|
||||
|
||||
func _on_area_3d_body_entered(body: Node3D) -> void:
|
||||
if body.is_in_group("player"):
|
||||
player = body
|
||||
sprite_label.visible = true
|
||||
player.set_dialog_mode( global_position, 0)
|
||||
dialog()
|
||||
area_3d.queue_free()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://b8kl67fi5cpas
|
||||
@@ -0,0 +1,127 @@
|
||||
extends CharacterBody3D
|
||||
|
||||
# --- ÉTATS DU CLIENT ---
|
||||
enum State { ENTERING, WAITING, LEAVING }
|
||||
var current_state: State = State.ENTERING
|
||||
|
||||
# --- PARAMÈTRES ---
|
||||
@export var speed: float = 12
|
||||
@export var entry_path: Array[Marker3D]
|
||||
@export var exit_path: Array[Marker3D]
|
||||
|
||||
var current_path_index: int = 0
|
||||
|
||||
var possible_pies = [Sauvegarde.get_text("pie1"), Sauvegarde.get_text("pie2"), Sauvegarde.get_text("pie3")]
|
||||
var possible_toppings = [Sauvegarde.get_text("topping1"), Sauvegarde.get_text("topping2"), "Aucun"]
|
||||
var pie_icons = {
|
||||
Sauvegarde.get_text("pie1"): "res://assets/img/ui/client/orange.png",
|
||||
Sauvegarde.get_text("pie2"): "res://assets/img/ui/client/yellow.png",
|
||||
Sauvegarde.get_text("pie3"): "res://assets/img/ui/client/purple.png",
|
||||
}
|
||||
var topping_icons = {
|
||||
Sauvegarde.get_text("topping1"): "res://assets/img/ui/client/meringue.png",
|
||||
Sauvegarde.get_text("topping2"): "res://assets/img/ui/client/croisillon.png",
|
||||
}
|
||||
|
||||
var desired_pie: String = ""
|
||||
var desired_topping: String = ""
|
||||
|
||||
var ready_to_take_pie : bool = false
|
||||
|
||||
# --- RÉFÉRENCES UI ---
|
||||
@onready var sprite: Sprite3D = $Sprite3D
|
||||
@onready var label: RichTextLabel = $Sprite3D/SubViewport/RichTextLabel
|
||||
|
||||
func _ready() -> void:
|
||||
sprite.visible = false
|
||||
|
||||
desired_pie = possible_pies.pick_random()
|
||||
desired_topping = possible_toppings.pick_random()
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if not is_on_floor():
|
||||
velocity.y -= 9.8 * delta
|
||||
|
||||
match current_state:
|
||||
State.ENTERING:
|
||||
move_along_path(entry_path, delta)
|
||||
State.WAITING:
|
||||
velocity.x = move_toward(velocity.x, 0, speed)
|
||||
velocity.z = move_toward(velocity.z, 0, speed)
|
||||
State.LEAVING:
|
||||
move_along_path(exit_path, delta)
|
||||
|
||||
move_and_slide()
|
||||
|
||||
# --- FONCTION DE DÉPLACEMENT ---
|
||||
func move_along_path(path_array: Array[Marker3D], delta: float) -> void:
|
||||
if path_array.is_empty() or current_path_index >= path_array.size():
|
||||
if current_state == State.ENTERING:
|
||||
start_waiting()
|
||||
elif current_state == State.LEAVING:
|
||||
queue_free()
|
||||
return
|
||||
|
||||
var target = path_array[current_path_index].global_position
|
||||
var direction = global_position.direction_to(target)
|
||||
direction.y = 0
|
||||
direction = direction.normalized()
|
||||
|
||||
if global_position.distance_to(Vector3(target.x, global_position.y, target.z)) > 0.5:
|
||||
velocity.x = direction.x * speed
|
||||
velocity.z = direction.z * speed
|
||||
var target_angle = atan2(velocity.x, velocity.z)
|
||||
rotation.y = lerp_angle(rotation.y, target_angle, 10.0 * delta)
|
||||
else:
|
||||
current_path_index += 1
|
||||
|
||||
# --- ACTIONS DU CLIENT ---
|
||||
func start_waiting() -> void:
|
||||
ready_to_take_pie = true
|
||||
current_state = State.WAITING
|
||||
velocity = Vector3.ZERO
|
||||
|
||||
var p_icon = pie_icons[ desired_pie ]
|
||||
var order_text = Sauvegarde.get_text("desire") + desired_pie
|
||||
order_text += " [img=60]" + p_icon + "[/img]"
|
||||
if desired_topping != "Aucun":
|
||||
var t_icon = topping_icons[ desired_topping ]
|
||||
order_text += "\n+ " + desired_topping + " [img=80]" + t_icon + "[/img]"
|
||||
set_dialogue(order_text)
|
||||
sprite.visible = true
|
||||
|
||||
# pour tests
|
||||
#await get_tree().create_timer( 3 ).timeout
|
||||
#leave_store()
|
||||
|
||||
# FONCTION MISE À JOUR : Le joueur doit donner la tarte ET le nappage
|
||||
func give_item(pie_given: String, topping_given: String) -> void:
|
||||
if current_state != State.WAITING:
|
||||
return
|
||||
|
||||
#print("pie_given: ", pie_given, " | topping_given:", topping_given)
|
||||
#print("desired_pie: ", desired_pie, " | desired_topping:", desired_topping)
|
||||
|
||||
# On vérifie les deux conditions
|
||||
if pie_given == desired_pie and topping_given == desired_topping:
|
||||
set_dialogue( Sauvegarde.get_text("goodpie") )
|
||||
elif pie_given == desired_pie and topping_given != desired_topping:
|
||||
set_dialogue( Sauvegarde.get_text("mixpie") )
|
||||
else:
|
||||
set_dialogue( Sauvegarde.get_text("badpie") )
|
||||
|
||||
leave_store()
|
||||
|
||||
func leave_store() -> void:
|
||||
ready_to_take_pie = false
|
||||
await get_tree().create_timer(2.0).timeout
|
||||
sprite.visible = false
|
||||
current_path_index = 0
|
||||
current_state = State.LEAVING
|
||||
|
||||
func set_dialogue(text: String) -> void:
|
||||
label.text = text
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://biax3djgh5pyc
|
||||
@@ -0,0 +1,25 @@
|
||||
extends Node3D
|
||||
|
||||
@export var client : PackedScene
|
||||
@export var entry_path: Array[Marker3D]
|
||||
@export var exit_path: Array[Marker3D]
|
||||
|
||||
@onready var sprite_3d: Sprite3D = $Sprite3D
|
||||
@onready var timer: Timer = $Timer
|
||||
@onready var water_mill: MultiMeshInstance3D = $".."
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
sprite_3d.visible = false
|
||||
|
||||
func _on_timer_timeout() -> void:
|
||||
var new_client = client.instantiate()
|
||||
water_mill.add_child(new_client)
|
||||
|
||||
new_client.global_position = global_position
|
||||
|
||||
new_client.entry_path = entry_path
|
||||
new_client.exit_path = exit_path
|
||||
|
||||
func start_timer():
|
||||
timer.start()
|
||||
@@ -0,0 +1 @@
|
||||
uid://rm8aavdetmri
|
||||
@@ -0,0 +1,117 @@
|
||||
extends CanvasLayer
|
||||
|
||||
@onready var game_area: ColorRect = $BackGround/GameArea
|
||||
@onready var target_rect: ColorRect = $BackGround/GameArea/TargetRect
|
||||
@onready var feather: ColorRect = $BackGround/GameArea/Feather
|
||||
|
||||
@onready var player: CharacterBody3D = $".."
|
||||
@onready var frog: CharacterBody3D = $"../../frog"
|
||||
|
||||
var cpt_col : int = -1
|
||||
var is_playing : bool = false
|
||||
var is_flashing : bool = false
|
||||
var default_target_color : Color = Color(1.0, 1.0, 1.0, 0.6)
|
||||
var tween : Tween
|
||||
|
||||
func _ready():
|
||||
hide()
|
||||
target_rect.color = default_target_color
|
||||
|
||||
func _input(event):
|
||||
if Input.is_action_just_pressed("ui_accept") and player.can_play:
|
||||
toggle_minigame()
|
||||
|
||||
# Contrôle de la plume avec la souris (uniquement sur Y)
|
||||
if is_playing and event is InputEventMouseMotion:
|
||||
var local_mouse_y = game_area.get_local_mouse_position().y
|
||||
|
||||
feather.position.y = clamp(local_mouse_y, 0, game_area.size.y - feather.size.y)
|
||||
|
||||
func toggle_minigame():
|
||||
is_playing = !is_playing
|
||||
if is_playing:
|
||||
show()
|
||||
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
player.camera_pov.make_current()
|
||||
target_rect.position.y = game_area.size.y - target_rect.size.y
|
||||
|
||||
await get_tree().create_timer( 2 ).timeout
|
||||
if is_playing:
|
||||
player.can_play = false
|
||||
|
||||
start_pattern()
|
||||
else:
|
||||
player.can_play = true
|
||||
player.camera.make_current()
|
||||
hide()
|
||||
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
if tween:
|
||||
tween.kill()
|
||||
|
||||
if cpt_col == -1:
|
||||
return
|
||||
cpt_col/= 2
|
||||
if cpt_col < 2:
|
||||
frog.size = 1
|
||||
elif cpt_col == 2 or cpt_col == 3:
|
||||
if frog.size > 1.5:
|
||||
frog.size = 1.5
|
||||
else:
|
||||
if frog.size > 2:
|
||||
frog.size = 2
|
||||
|
||||
|
||||
func start_pattern():
|
||||
if tween:
|
||||
tween.kill()
|
||||
|
||||
if is_playing:
|
||||
tween = create_tween()
|
||||
else:
|
||||
return
|
||||
|
||||
var bottom_y = game_area.size.y - target_rect.size.y
|
||||
var top_y = 0.0
|
||||
|
||||
tween.tween_property(target_rect, "position:y", top_y, 5)
|
||||
tween.tween_interval(4.0)
|
||||
tween.tween_property(target_rect, "position:y", bottom_y, 5)
|
||||
tween.tween_interval(4.0)
|
||||
|
||||
await tween.finished
|
||||
if cpt_col == -1:
|
||||
cpt_col = 0
|
||||
toggle_minigame()
|
||||
|
||||
|
||||
|
||||
func _process(delta):
|
||||
if not is_playing:
|
||||
return
|
||||
|
||||
# On vérifie si le centre de la plume est dans le rectangle
|
||||
var feather_center = feather.position.y + (feather.size.y / 2.0)
|
||||
var rect_top = target_rect.position.y
|
||||
var rect_bottom = target_rect.position.y + target_rect.size.y
|
||||
|
||||
var is_inside = feather_center >= rect_top and feather_center <= rect_bottom
|
||||
|
||||
# Si elle est dehors et qu'on n'est pas déjà en train de clignoter en rouge
|
||||
if not is_inside and not is_flashing and !player.can_play : # can_play juste pour le début
|
||||
trigger_error()
|
||||
|
||||
func trigger_error():
|
||||
is_flashing = true
|
||||
cpt_col += 1
|
||||
|
||||
# Passage en rouge
|
||||
target_rect.color = Color.RED
|
||||
|
||||
# On attend 0.5 secondes
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
|
||||
# Retour à la normale
|
||||
target_rect.color = default_target_color
|
||||
is_flashing = false
|
||||
@@ -0,0 +1 @@
|
||||
uid://dfli5tvn7us86
|
||||
@@ -0,0 +1,89 @@
|
||||
extends CharacterBody3D
|
||||
|
||||
@export var player: Node3D
|
||||
@export var follow_speed: float = 4.0
|
||||
@export var right_offset: float = 2.5
|
||||
@export var forward_offset: float = 1
|
||||
@export var car_cinematic : bool = false
|
||||
|
||||
@onready var anim_player: AnimationPlayer = $frog/AnimationPlayer
|
||||
var size : float = 0.5
|
||||
|
||||
@onready var arms: MeshInstance3D = $frog/Armature/Skeleton3D/arms
|
||||
@onready var body: MeshInstance3D = $frog/Armature/Skeleton3D/body
|
||||
@onready var eyes: MeshInstance3D = $frog/Armature/Skeleton3D/eyes
|
||||
@onready var feet: MeshInstance3D = $frog/Armature/Skeleton3D/feet
|
||||
@onready var hands: MeshInstance3D = $frog/Armature/Skeleton3D/hands
|
||||
@onready var legs: MeshInstance3D = $frog/Armature/Skeleton3D/legs
|
||||
@onready var mesh = [ arms, body, eyes, feet, hands, legs ]
|
||||
|
||||
|
||||
# Variables pour la gravité
|
||||
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
|
||||
|
||||
func _ready() -> void:
|
||||
if !car_cinematic:
|
||||
scale = Vector3( size, size, size )
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if car_cinematic:
|
||||
if anim_player.current_animation != "idle":
|
||||
anim_player.play("idle")
|
||||
else:
|
||||
scale_frog()
|
||||
# transparent blue color
|
||||
var dist = global_transform.origin.distance_to(player.camera.global_transform.origin)
|
||||
for m in mesh:
|
||||
for i in m.get_surface_override_material_count():
|
||||
var mat = m.get_active_material(i)
|
||||
if mat:
|
||||
mat.set_shader_parameter("object_dist_to_camera", dist)
|
||||
|
||||
func _physics_process(delta):
|
||||
if not player or car_cinematic:
|
||||
return
|
||||
|
||||
# Appliquer la gravité
|
||||
if not is_on_floor():
|
||||
velocity.y -= gravity * delta
|
||||
|
||||
var target_position = player.global_position + (player.global_transform.basis.x * right_offset) + (-player.global_transform.basis.z * forward_offset)
|
||||
global_position.y = target_position.y
|
||||
|
||||
var direction = global_position.direction_to(target_position)
|
||||
var distance = global_position.distance_to(target_position)
|
||||
|
||||
if distance > 15: # tp
|
||||
target_position = player.global_position + (player.global_transform.basis.x * right_offset) + (-player.global_transform.basis.z * (-forward_offset*4) )
|
||||
|
||||
global_position = target_position
|
||||
velocity.x = 0
|
||||
velocity.z = 0
|
||||
elif distance > 0.5: # walk
|
||||
velocity.x = direction.x * follow_speed
|
||||
velocity.z = direction.z * follow_speed
|
||||
else: # idle
|
||||
velocity.x = 0
|
||||
velocity.z = 0
|
||||
|
||||
var look_target = player.global_position
|
||||
look_target.y = global_position.y
|
||||
look_at(look_target, Vector3.UP)
|
||||
|
||||
var real_speed = Vector2(get_real_velocity().x, get_real_velocity().z).length()
|
||||
# Si frog n'est pas bloquée par un mur
|
||||
if real_speed > 0.1:
|
||||
size += 0.0
|
||||
if anim_player.current_animation != "walk":
|
||||
anim_player.play("walk", 0.2)
|
||||
else:
|
||||
if anim_player.current_animation != "idle":
|
||||
anim_player.play("idle", 0.2)
|
||||
|
||||
move_and_slide()
|
||||
|
||||
func scale_frog():
|
||||
var tween = create_tween()
|
||||
var final_scale = Vector3(size, size, size)
|
||||
|
||||
tween.tween_property(self, "scale", final_scale, 2)
|
||||
@@ -0,0 +1 @@
|
||||
uid://csfno2e8stbfd
|
||||
@@ -0,0 +1,17 @@
|
||||
extends Node
|
||||
|
||||
func _ready() -> void:
|
||||
#Dialogic.timeline_ended.connect(set_player_cam)
|
||||
pass
|
||||
|
||||
func change_cinematic_cam(camera_name:String):
|
||||
var all_cinematic_cams:Array = get_tree().get_nodes_in_group("cinematic_cams")
|
||||
for cam in all_cinematic_cams:
|
||||
if cam.name == camera_name:
|
||||
cam.make_current()
|
||||
|
||||
func set_player_cam():
|
||||
return
|
||||
var player = get_tree().get_first_node_in_group("player")
|
||||
if player:
|
||||
player.set_default_cam()
|
||||
@@ -0,0 +1 @@
|
||||
uid://badkx3jvh8r0y
|
||||
+108
-17
@@ -1,8 +1,7 @@
|
||||
extends CharacterBody3D
|
||||
|
||||
|
||||
var SPEED = 20
|
||||
var JUMP_VELOCITY = 8
|
||||
var SPEED = 7
|
||||
|
||||
@onready var arm: Node3D = $arm
|
||||
@onready var model: Node3D = $model
|
||||
@@ -24,16 +23,24 @@ var footstep_sounds: Array[AudioStream] = [
|
||||
preload("res://assets/audio/footsteps/dirt_step_4.wav")
|
||||
]
|
||||
var footstep_timer: float = 0.0
|
||||
const FOOTSTEP_DELAY: float = 0.15
|
||||
const FOOTSTEP_DELAY: float = 0.24
|
||||
|
||||
@onready var pause: Control = $pause
|
||||
|
||||
# animations
|
||||
@onready var anim_player: AnimationPlayer = $character/AnimationPlayer
|
||||
|
||||
# boulangerie MOULIN
|
||||
var item : int = 0 # 0 rien, 1 abricot, 2 citron, 3 mirtylle
|
||||
var topping : int = 0 # 0 rien, 1 meringue, 2 croisillon
|
||||
@onready var area_tarte: Area3D = $Area3DTarte
|
||||
@onready var tarte_holded: StaticBody3D = $tarte
|
||||
|
||||
var can_play : bool = true
|
||||
|
||||
|
||||
func _ready():
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
||||
label_tuto.text = Sauvegarde.get_text("tuto_1")
|
||||
|
||||
if third_person:
|
||||
camera.make_current()
|
||||
@@ -48,28 +55,70 @@ func _input(_event):
|
||||
else:
|
||||
pause.resume()
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if Input.is_action_just_pressed("give"):
|
||||
var objects_in_front = area_tarte.get_overlapping_bodies()
|
||||
|
||||
# check if there is a client in the box
|
||||
for obj in objects_in_front:
|
||||
if obj.is_in_group("client"):
|
||||
if obj.get_parent().ready_to_take_pie:
|
||||
var l = get_conversion_pie()
|
||||
obj.get_parent().give_item( l[0], l[1] )
|
||||
|
||||
reset_pie()
|
||||
return
|
||||
# no client was found
|
||||
for obj in objects_in_front:
|
||||
if obj.is_in_group("tarte"):
|
||||
set_item_topping( obj.item, obj.topping, obj.reset)
|
||||
return
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
# Add the gravity.
|
||||
#print("global position; ", global_position)
|
||||
|
||||
if not is_on_floor():
|
||||
velocity += get_gravity() * delta
|
||||
|
||||
# Handle jump
|
||||
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
|
||||
velocity.y = JUMP_VELOCITY
|
||||
|
||||
|
||||
# Get the input direction and handle the movement/deceleration.
|
||||
var input_dir := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
|
||||
var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
|
||||
if direction:
|
||||
velocity.x = direction.x * SPEED
|
||||
velocity.z = direction.z * SPEED
|
||||
#var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
|
||||
#if direction:
|
||||
#velocity.x = direction.x * SPEED
|
||||
#velocity.z = direction.z * SPEED
|
||||
#else:
|
||||
#velocity.x = move_toward(velocity.x, 0, SPEED)
|
||||
#velocity.z = move_toward(velocity.z, 0, SPEED)
|
||||
|
||||
if input_dir.length() < 0.1:
|
||||
input_dir = Vector2.ZERO
|
||||
var direction = (spring_arm.transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
|
||||
|
||||
# On ne déplace et on ne tourne le joueur que si une vraie direction est donnée
|
||||
if can_play:
|
||||
if direction.length() > 0.1:
|
||||
anim_player.play("RootAction_001", -1, 3)
|
||||
velocity.x = direction.x * SPEED
|
||||
velocity.z = direction.z * SPEED
|
||||
|
||||
# 2. CORRECTION DE LA ROTATION INVERSÉE
|
||||
# Si le joueur tourne toujours à l'envers, enlève les signes "-" devant velocity.x et velocity.z
|
||||
var target_angle = atan2(-velocity.x, -velocity.z)
|
||||
|
||||
# Le 10.0 est bien, il permet au modèle 3D de se tourner vite sans faire de coup sec.
|
||||
rotation.y = lerp_angle(rotation.y, target_angle, 10.0 * delta)
|
||||
else:
|
||||
anim_player.stop()
|
||||
# Le joueur s'arrête doucement
|
||||
velocity.x = move_toward(velocity.x, 0, SPEED)
|
||||
velocity.z = move_toward(velocity.z, 0, SPEED)
|
||||
else:
|
||||
velocity.x = move_toward(velocity.x, 0, SPEED)
|
||||
velocity.z = move_toward(velocity.z, 0, SPEED)
|
||||
anim_player.stop()
|
||||
|
||||
|
||||
# footstep sounds
|
||||
if is_on_floor() and Vector2(velocity.x, velocity.z).length() > 0.5:
|
||||
if is_on_floor() and Vector2(velocity.x, velocity.z).length() > 0.5 and can_play:
|
||||
footstep_timer -= delta
|
||||
if footstep_timer <= 0.0:
|
||||
play_random_footstep()
|
||||
@@ -80,10 +129,52 @@ func _physics_process(delta: float) -> void:
|
||||
if can_play:
|
||||
move_and_slide()
|
||||
|
||||
# PARTIE BOULANGERIE MOULIN
|
||||
func set_item_topping( new_item : int, new_topping : int, reset:bool ):
|
||||
if reset:
|
||||
tarte_holded.visible = false
|
||||
else:
|
||||
tarte_holded.visible = true
|
||||
|
||||
if new_item != 0 or reset:
|
||||
item = new_item
|
||||
tarte_holded.item = new_item
|
||||
if new_topping != 0 or reset:
|
||||
topping = new_topping
|
||||
tarte_holded.topping = topping
|
||||
|
||||
|
||||
tarte_holded.setup_color_and_name()
|
||||
|
||||
func reset_pie():
|
||||
item = 0
|
||||
tarte_holded.item = 0
|
||||
topping = 0
|
||||
tarte_holded.topping = 0
|
||||
|
||||
tarte_holded.visible = false
|
||||
|
||||
|
||||
func get_conversion_pie():
|
||||
var possible_pies = [Sauvegarde.get_text("pie1"), Sauvegarde.get_text("pie2"), Sauvegarde.get_text("pie3")]
|
||||
var possible_toppings = [Sauvegarde.get_text("topping1"), Sauvegarde.get_text("topping2"), "Aucun"]
|
||||
if item != 0:
|
||||
return [ possible_pies[ item - 1 ], possible_toppings[ topping -1 ] ]
|
||||
return [ "", possible_toppings[ topping -1 ] ]
|
||||
|
||||
func play_random_footstep( pitch=[0.85, 1.15] ) -> void:
|
||||
var random_index = randi() % footstep_sounds.size()
|
||||
audio_footstep.stream = footstep_sounds[random_index]
|
||||
|
||||
audio_footstep.pitch_scale = randf_range( pitch[0], pitch[1] )
|
||||
audio_footstep.play()
|
||||
|
||||
func set_dialog_mode( npc_pos=Vector3.ZERO, force_pos=-1):
|
||||
can_play = !can_play
|
||||
spring_arm.set_dialog_mode( npc_pos, force_pos )
|
||||
|
||||
func set_default_cam():
|
||||
if third_person:
|
||||
camera.set_current(true)
|
||||
else:
|
||||
camera_pov.set_current(true)
|
||||
|
||||
@@ -2,11 +2,90 @@ extends SpringArm3D
|
||||
|
||||
@onready var player: CharacterBody3D = $"../.."
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if player.third_person and event is InputEventMouseMotion :
|
||||
player.rotation.y -= event.relative.x * 0.005
|
||||
player.rotation.y = wrapf( player.rotation.y, 0.0, TAU )
|
||||
# --- Paramètres de mouvement ---
|
||||
@export var max_follow_speed: float = 3.0
|
||||
@export var speed_acceleration: float = 2.0
|
||||
@export var height_offset: float = 2.5
|
||||
@export var wait_time: float = 0.5
|
||||
|
||||
# --- Paramètres de Dialogue ---
|
||||
@export var dialog_zoom_length: float = 2.0
|
||||
@export var dialog_side_offset: float = 1.0
|
||||
@export var transition_speed: float = 4.0 # Gère la vitesse du zoom, du décalage ET de la rotation en dialog
|
||||
|
||||
@onready var normal_spring_length: float = spring_length
|
||||
|
||||
var delay_timer: float = 0.0
|
||||
var current_follow_speed: float = 0.0
|
||||
var current_side_offset: float = 0.0
|
||||
var target_side_offset: float = 0.0
|
||||
var is_in_dialog: bool = false
|
||||
|
||||
func _ready() -> void:
|
||||
top_level = true
|
||||
|
||||
func set_dialog_mode(npc_position: Vector3 = Vector3.ZERO, force_pos=-1) -> void:
|
||||
is_in_dialog = !is_in_dialog
|
||||
|
||||
if is_in_dialog:
|
||||
var direction_to_npc = (npc_position - player.global_position).normalized()
|
||||
var right_dot = global_transform.basis.x.dot(direction_to_npc)
|
||||
|
||||
rotation.x -= event.relative.y * 0.005
|
||||
rotation.x = clamp( rotation.x, -PI/2, PI/4 )
|
||||
if right_dot >= 0 :
|
||||
target_side_offset = dialog_side_offset
|
||||
else:
|
||||
target_side_offset = -dialog_side_offset
|
||||
|
||||
if force_pos == 0 :
|
||||
target_side_offset = dialog_side_offset
|
||||
elif force_pos == 1 :
|
||||
target_side_offset = -dialog_side_offset
|
||||
else:
|
||||
target_side_offset = 0.0
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if not is_instance_valid(player):
|
||||
return
|
||||
|
||||
var target_rotation_y = player.global_rotation.y
|
||||
|
||||
# ==========================================
|
||||
# 1. GESTION DE LA ROTATION (Avant la position !)
|
||||
# ==========================================
|
||||
if is_in_dialog:
|
||||
delay_timer = 0.3
|
||||
current_follow_speed = 0.5
|
||||
|
||||
# La caméra tourne doucement pour se mettre dans le dos du joueur.
|
||||
# On utilise transition_speed pour que ça se finisse en même temps que le zoom.
|
||||
global_rotation.y = lerp_angle(global_rotation.y, target_rotation_y, transition_speed * delta)
|
||||
|
||||
else:
|
||||
if player.can_play:
|
||||
# Comportement normal d'exploration (Le vieux Zelda)
|
||||
var angle_diff = abs(angle_difference(global_rotation.y, target_rotation_y))
|
||||
|
||||
if angle_diff > 0.02 and player.velocity.length() > 0.1:
|
||||
delay_timer += delta
|
||||
else:
|
||||
delay_timer = 0.3
|
||||
current_follow_speed = 0.5
|
||||
|
||||
if delay_timer >= wait_time:
|
||||
current_follow_speed = move_toward(current_follow_speed, max_follow_speed, speed_acceleration * delta)
|
||||
global_rotation.y = lerp_angle(global_rotation.y, target_rotation_y, current_follow_speed * delta)
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 2. GESTION DU ZOOM ET DU DECALAGE
|
||||
# ==========================================
|
||||
var target_length = dialog_zoom_length if is_in_dialog else normal_spring_length
|
||||
|
||||
spring_length = lerp(spring_length, target_length, transition_speed * delta)
|
||||
current_side_offset = lerp(current_side_offset, target_side_offset, transition_speed * delta)
|
||||
|
||||
# On récupère le vecteur "Droite" MAINTENANT, car la rotation vient juste d'être mise à jour au-dessus.
|
||||
var right_direction = global_transform.basis.x
|
||||
|
||||
# On applique la position finale (Joueur + Hauteur + Décalage latéral)
|
||||
global_position = player.global_position + Vector3(0, height_offset, 0) + (right_direction * current_side_offset)
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
extends StaticBody3D
|
||||
|
||||
@export var item : int
|
||||
@export var topping : int
|
||||
@export var reset : bool = false
|
||||
|
||||
@onready var creme_abricot: MeshInstance3D = $tarte/creme_abricot
|
||||
@onready var creme_citron: MeshInstance3D = $tarte/creme_citron
|
||||
@onready var creme_myrtille: MeshInstance3D = $tarte/creme_myrtille
|
||||
@onready var meringue: MeshInstance3D = $tarte/meringue
|
||||
@onready var croisillon: MeshInstance3D = $tarte/croisillon
|
||||
|
||||
@onready var label: Label = $Sprite3D/SubViewport/Label
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
setup_color_and_name()
|
||||
|
||||
func setup_color_and_name():
|
||||
_reset()
|
||||
if item != 0:
|
||||
if item == 1:
|
||||
creme_abricot.visible = true
|
||||
label.text = Sauvegarde.get_text("pie1")
|
||||
label.add_theme_color_override("font_color", Color.ORANGE)
|
||||
elif item == 2:
|
||||
creme_citron.visible = true
|
||||
label.text = Sauvegarde.get_text("pie2")
|
||||
label.add_theme_color_override("font_color", Color.LEMON_CHIFFON)
|
||||
elif item == 3:
|
||||
creme_myrtille.visible = true
|
||||
label.text = Sauvegarde.get_text("pie3")
|
||||
label.add_theme_color_override("font_color", Color.BLUE_VIOLET)
|
||||
if topping != 0:
|
||||
if topping == 1:
|
||||
meringue.visible = true
|
||||
label.text = Sauvegarde.get_text("topping1")
|
||||
label.add_theme_color_override("font_color", Color.WHITE)
|
||||
elif topping == 2:
|
||||
croisillon.visible = true
|
||||
label.text = Sauvegarde.get_text("topping2")
|
||||
label.add_theme_color_override("font_color", Color.NAVAJO_WHITE)
|
||||
|
||||
if reset:
|
||||
label.text = Sauvegarde.get_text("toppingreset")
|
||||
|
||||
|
||||
|
||||
|
||||
func _reset():
|
||||
creme_abricot.visible = false
|
||||
creme_citron.visible = false
|
||||
creme_myrtille.visible = false
|
||||
meringue.visible = false
|
||||
croisillon.visible = false
|
||||
@@ -0,0 +1 @@
|
||||
uid://bp52fs0u3dch3
|
||||
@@ -0,0 +1,11 @@
|
||||
extends CanvasLayer
|
||||
|
||||
@onready var animation_player: AnimationPlayer = $AnimationPlayer
|
||||
|
||||
func change_scene(target_scene_path: String) -> void:
|
||||
animation_player.play("fade")
|
||||
|
||||
await animation_player.animation_finished
|
||||
get_tree().change_scene_to_file( target_scene_path )
|
||||
|
||||
animation_player.play_backwards("fade")
|
||||
@@ -0,0 +1 @@
|
||||
uid://cc8d1t2rvmf6h
|
||||
@@ -0,0 +1,262 @@
|
||||
@tool
|
||||
extends Node
|
||||
class_name WaterSurfaceGenerator
|
||||
|
||||
@export var river_material: ShaderMaterial
|
||||
|
||||
@export var path: Path3D:
|
||||
set(value):
|
||||
_path = value
|
||||
if Engine.is_editor_hint():
|
||||
_update_mesh()
|
||||
get:
|
||||
return _path
|
||||
|
||||
@export var width: float = 5.0:
|
||||
set(value):
|
||||
_width = value
|
||||
if Engine.is_editor_hint():
|
||||
_update_mesh()
|
||||
get:
|
||||
return _width
|
||||
|
||||
@export var steps: int = 30:
|
||||
set(value):
|
||||
_steps = value
|
||||
if Engine.is_editor_hint():
|
||||
_update_mesh()
|
||||
get:
|
||||
return _steps
|
||||
|
||||
@export var smoothness: float = 0.5:
|
||||
set(value):
|
||||
_smoothness = value
|
||||
if Engine.is_editor_hint():
|
||||
_update_mesh()
|
||||
get:
|
||||
return _smoothness
|
||||
|
||||
@export var path_rotation_accurate: bool = true:
|
||||
set(value):
|
||||
_path_rotation_accurate = value
|
||||
if Engine.is_editor_hint():
|
||||
_update_mesh()
|
||||
get:
|
||||
return _path_rotation_accurate
|
||||
|
||||
@export var double_sided: bool = false:
|
||||
set(value):
|
||||
_double_sided = value
|
||||
if Engine.is_editor_hint():
|
||||
_update_mesh()
|
||||
get:
|
||||
return _double_sided
|
||||
|
||||
@export var width_subdivisions: int = 0:
|
||||
set(value):
|
||||
_width_subdivisions = max(0, value)
|
||||
if Engine.is_editor_hint():
|
||||
_update_mesh()
|
||||
get:
|
||||
return _width_subdivisions
|
||||
|
||||
var _path: Path3D
|
||||
var _width: float = 5.0
|
||||
var _steps: int = 30
|
||||
var _smoothness: float = 0.5
|
||||
var _path_rotation_accurate: bool = true
|
||||
var _double_sided: bool = true
|
||||
var _width_subdivisions: int = 0
|
||||
var _mesh_instance: MeshInstance3D
|
||||
var _curve: Curve3D
|
||||
|
||||
|
||||
func _ready():
|
||||
_mesh_instance = MeshInstance3D.new()
|
||||
_mesh_instance.visible = true
|
||||
add_child(_mesh_instance)
|
||||
_mesh_instance.owner = owner if owner else self
|
||||
|
||||
call_deferred("_update_mesh")
|
||||
|
||||
func _process(_delta):
|
||||
if Engine.is_editor_hint():
|
||||
_check_curve_changes()
|
||||
|
||||
func _check_curve_changes():
|
||||
if not _path:
|
||||
return
|
||||
|
||||
var new_curve = _path.curve
|
||||
if new_curve != _curve:
|
||||
_curve = new_curve
|
||||
_update_mesh()
|
||||
|
||||
func _update_mesh():
|
||||
if not _mesh_instance:
|
||||
return
|
||||
|
||||
if not _path:
|
||||
return
|
||||
|
||||
if not _path.curve:
|
||||
return
|
||||
|
||||
if _path.curve.point_count < 2:
|
||||
return
|
||||
|
||||
var mesh = _generate_river_mesh(_path.curve, _width, _steps, _smoothness, _path_rotation_accurate, _width_subdivisions)
|
||||
|
||||
if mesh:
|
||||
|
||||
_post_process_tangents(mesh, _path.curve, _path_rotation_accurate, _width_subdivisions)
|
||||
|
||||
_mesh_instance.mesh = mesh
|
||||
if river_material:
|
||||
river_material.set_shader_parameter("double_sided", _double_sided)
|
||||
_mesh_instance.material_override = river_material
|
||||
else:
|
||||
var material = StandardMaterial3D.new()
|
||||
material.cull_mode = BaseMaterial3D.CULL_DISABLED if _double_sided else BaseMaterial3D.CULL_BACK
|
||||
material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
material.albedo_color = Color(0.5, 0.7, 1.0, 0.5)
|
||||
_mesh_instance.material_override = material
|
||||
else:
|
||||
_mesh_instance.mesh = null
|
||||
|
||||
func _post_process_tangents(mesh: ArrayMesh, curve: Curve3D, path_rotation_accurate: bool, width_subdivisions: int):
|
||||
if not mesh or mesh.get_surface_count() == 0:
|
||||
return
|
||||
|
||||
var arrays = mesh.surface_get_arrays(0)
|
||||
var vertices = arrays[ArrayMesh.ARRAY_VERTEX]
|
||||
var tangents = arrays[ArrayMesh.ARRAY_TANGENT]
|
||||
|
||||
var curve_length = curve.get_baked_length()
|
||||
var closed_loop = curve.closed
|
||||
var vertices_per_segment = width_subdivisions + 2
|
||||
var vertex_count = vertices.size() / vertices_per_segment
|
||||
var actual_steps = vertex_count - 1 if not closed_loop else vertex_count
|
||||
|
||||
var curve_tangents = []
|
||||
for i in range(vertex_count):
|
||||
var t = float(i) / float(actual_steps)
|
||||
var offset = t * curve_length
|
||||
|
||||
var forward_vector: Vector3
|
||||
if path_rotation_accurate:
|
||||
if curve.has_method("sample_baked_tangent"):
|
||||
forward_vector = curve.sample_baked_tangent(offset).normalized()
|
||||
else:
|
||||
var t_forward = min(t + 0.01, 1.0)
|
||||
var t_backward = max(t - 0.01, 0.0)
|
||||
var pos_forward = curve.sample_baked(t_forward * curve_length)
|
||||
var pos_backward = curve.sample_baked(t_backward * curve_length)
|
||||
forward_vector = (pos_forward - pos_backward).normalized()
|
||||
else:
|
||||
var t_forward = min(t + 0.01, 1.0)
|
||||
var t_backward = max(t - 0.01, 0.0)
|
||||
var pos_forward = curve.sample_baked(t_forward * curve_length)
|
||||
var pos_backward = curve.sample_baked(t_backward * curve_length)
|
||||
forward_vector = (pos_forward - pos_backward).normalized()
|
||||
|
||||
curve_tangents.append(forward_vector)
|
||||
|
||||
for i in range(vertex_count):
|
||||
var tangent = curve_tangents[i]
|
||||
|
||||
for j in range(vertices_per_segment):
|
||||
var vertex_idx = i * vertices_per_segment + j
|
||||
var t_idx = vertex_idx * 4
|
||||
tangents[t_idx] = tangent.x
|
||||
tangents[t_idx + 1] = tangent.y
|
||||
tangents[t_idx + 2] = tangent.z
|
||||
tangents[t_idx + 3] = 1.0
|
||||
|
||||
arrays[ArrayMesh.ARRAY_TANGENT] = tangents
|
||||
mesh.clear_surfaces()
|
||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
|
||||
|
||||
static func _generate_river_mesh(curve: Curve3D, width: float, steps: int, smoothness: float, path_rotation_accurate: bool, width_subdivisions: int = 0) -> ArrayMesh:
|
||||
if not curve or curve.point_count < 2:
|
||||
return null
|
||||
|
||||
if curve.bake_interval == 0:
|
||||
curve.bake_interval = 0.1
|
||||
|
||||
var st = SurfaceTool.new()
|
||||
st.begin(Mesh.PRIMITIVE_TRIANGLES)
|
||||
|
||||
var curve_length = curve.get_baked_length()
|
||||
|
||||
var subdivisions = int(smoothness * 4.0)
|
||||
var actual_steps = steps * (subdivisions + 1)
|
||||
var vertex_count = actual_steps + 1
|
||||
|
||||
var first_tangent: Vector3
|
||||
var closed_loop = curve.closed
|
||||
|
||||
for i in range(vertex_count):
|
||||
var t = float(i) / float(actual_steps)
|
||||
var offset = t * curve_length
|
||||
var position = curve.sample_baked(offset)
|
||||
|
||||
|
||||
var forward_vector: Vector3
|
||||
if path_rotation_accurate:
|
||||
if curve.has_method("sample_baked_tangent"):
|
||||
forward_vector = curve.sample_baked_tangent(offset).normalized()
|
||||
else:
|
||||
var t_forward = min(t + 0.01, 1.0)
|
||||
var t_backward = max(t - 0.01, 0.0)
|
||||
var pos_forward = curve.sample_baked(t_forward * curve_length)
|
||||
var pos_backward = curve.sample_baked(t_backward * curve_length)
|
||||
forward_vector = (pos_forward - pos_backward).normalized()
|
||||
else:
|
||||
var t_forward = min(t + 0.01, 1.0)
|
||||
var t_backward = max(t - 0.01, 0.0)
|
||||
var pos_forward = curve.sample_baked(t_forward * curve_length)
|
||||
var pos_backward = curve.sample_baked(t_backward * curve_length)
|
||||
forward_vector = (pos_forward - pos_backward).normalized()
|
||||
|
||||
if i == 0:
|
||||
first_tangent = forward_vector
|
||||
elif closed_loop and i == vertex_count - 1:
|
||||
forward_vector = first_tangent
|
||||
|
||||
var right_vector = forward_vector.cross(Vector3.UP).normalized()
|
||||
|
||||
var uv_t = offset / curve_length
|
||||
var vertices_per_segment = width_subdivisions + 2
|
||||
for j in range(vertices_per_segment):
|
||||
var width_t = float(j) / float(width_subdivisions + 1)
|
||||
var vertex_pos = position - right_vector * width * 0.5 + right_vector * width * width_t
|
||||
|
||||
st.set_normal(Vector3.UP)
|
||||
st.set_uv(Vector2(uv_t, width_t))
|
||||
st.add_vertex(vertex_pos)
|
||||
|
||||
|
||||
var triangle_count = vertex_count - 1
|
||||
var vertices_per_segment = width_subdivisions + 2
|
||||
for i in range(triangle_count):
|
||||
var base_idx = i * vertices_per_segment
|
||||
|
||||
for j in range(width_subdivisions + 1):
|
||||
var v0 = base_idx + j
|
||||
var v1 = base_idx + j + 1
|
||||
var v2 = base_idx + vertices_per_segment + j
|
||||
var v3 = base_idx + vertices_per_segment + j + 1
|
||||
|
||||
st.add_index(v0)
|
||||
st.add_index(v2)
|
||||
st.add_index(v1)
|
||||
|
||||
st.add_index(v1)
|
||||
st.add_index(v2)
|
||||
st.add_index(v3)
|
||||
|
||||
|
||||
st.generate_tangents()
|
||||
|
||||
return st.commit()
|
||||
@@ -0,0 +1 @@
|
||||
uid://sux5bo8rvnto
|
||||
Reference in New Issue
Block a user