full code: extends CharacterBody3D
@export var hero_name: String
@export var _device: int
@onready var anim_player: AnimationPlayer = $“3d/AnimationPlayer”
var camera: Camera3D
var SFX: Node
var gravity: float = ProjectSettings.get_setting(“physics/3d/default_gravity”)
const BASE_SPEED = 5.0
@export_group(“Jump”)
var jumps_made: int = 0
@export var max_jumps: int = 2
@export var jump_speed: float = 6.0
@export_group(“Dash”)
@export var dash_speed: float = 15.0
@export var dash_duration: float = 0.3
@export var rotation_speed: float = 5.0
var current_speed: float = BASE_SPEED
var is_raging: bool = false
@onready var mesh: Node3D = $“3d”
@onready var col: CollisionShape3D = $CollisionShape3D
@onready var skeleton3d: Skeleton3D = $“3d/metarig/Skeleton3D”
@export_group(“Skeleton”)
@export var skeletonik3d: Array[SkeletonIK3D]
var load_path: String
var gun_instance: Node3D
var can_move: bool = true
var is_dashing: bool = false
var dash_timer: float = 0.0
var gameplay_ui: Node
@onready var action_sword: Node3D = $“3d/metarig/Skeleton3D/action_sword”
@onready var gun0: Node3D = $“3d/metarig/Skeleton3D/action_gun0”
@onready var gun1: Node3D = $“3d/metarig/Skeleton3D/action_gun1”
@onready var sword: Node3D = $“3d/metarig/Skeleton3D/sword”
@onready var shield: Area3D = $shield
@export_group(“Orb Attraction”)
@onready var orb: Node
@export var attraction_speed: float = 15.0
@export var min_attraction_distance: float = 5.0
var action_anim: String = “_gun”
var auto_lock: bool
var can_do_anything: bool
var is_attacking: bool = false
var last_weapon: String = “gun”
var save_system: Node
@onready var area_sword: Area3D = %area_sword
var default_marker_position: Vector3
@export_group(“Gun”)
@onready var action_fire0: Node3D = $“3d/metarig/Skeleton3D/fire_gun0”
@onready var action_fire1: Node3D = $“3d/metarig/Skeleton3D/fire_gun1”
var can_interrupt_animation: bool = true
func _ready() → void:
can_do_anything = true
camera = find_target_camera()
SFX = find_node_in_group_only(“SFX”)
action_fire0.visible = false
action_fire1.visible = false
gameplay_ui = find_node_in_group_only(“GAMEPLAY_UI” + str(_device))
change_weapon_visibility(“only_gun”, true)
save_system = find_node_in_group_only(“save_system”)
active_shield(false)
can_do_anything = false
default_marker_position = Vector3.ZERO
func get_physical_bones() → Array[PhysicalBone3D]:
var bones: Array[PhysicalBone3D]
for child in skeleton3d.get_children():
if child is PhysicalBone3D:
bones.append(child)
return bones
func find_orb_in_group() → Node3D:
var nodes := get_tree().get_nodes_in_group(“orb”)
for node in nodes:
return node
return null
@warning_ignore(“shadowed_variable_base_class”)
func change_weapon_visibility(s: String, show: bool) → void:
match(s):
“only_action_sword”:
if show:
last_weapon = “sword”
sword.visible = !show
action_sword.visible = show
gun0.visible = !show
gun1.visible = !show
“only_gun”:
if show:
last_weapon = “gun”
sword.visible = show
action_sword.visible = !show
gun0.visible = show
gun1.visible = show
“only_action”:
if show:
last_weapon = “sword”
action_sword.visible = show
gun0.visible = show
gun1.visible = show
func calculate_last_anim() → void:
match(last_weapon):
“gun”:
action_anim = “_gun”
change_weapon_visibility(“only_gun”, true)
“sword”:
action_anim = “_sword”
change_weapon_visibility(“only_action_sword”, true)
func active_shield(is_on : bool) → void:
if is_on_floor() or can_do_anything:
if is_on:
action_anim = "_block"
change_weapon_visibility("only_action", false)
else:
calculate_last_anim()
func _input(event: InputEvent) → void:
if !can_move:
return
if event.is_action_pressed("lt_" + str(_device)):
active_shield(true)
elif event.is_action_released("lt_" + str(_device)):
active_shield(false)
if event.is_action_pressed("x_" + str(_device)):
gun_attack(true)
elif event.is_action_released("x_" + str(_device)):
gun_attack(false)
if event.is_action_pressed("b_" + str(_device)):
start_dash()
if event.is_action_pressed("y_" + str(_device)):
sword_attack()
if event.is_action_pressed("rb_" + str(_device)):
activate_lock_on(true)
elif event.is_action_released("rb_" + str(_device)):
activate_lock_on(false)
func gun_attack(s: bool) → void:
action_fire0.visible = s
action_fire1.visible = s
if s:
change_weapon_visibility(“only_gun”, true)
if gameplay_ui.current_target != null:
gameplay_ui.current_target.damage(1.0, _device)
play_my_sfx(“gun”)
func activate_lock_on(s: bool) → void:
auto_lock = s
if auto_lock:
if last_weapon == “gun”:
action_anim = “_gun0”
gameplay_ui.lock_on(true)
update_hands_target()
else:
gameplay_ui.lock_on(false)
if last_weapon == “gun”:
action_anim = “_gun”
reset_pos()
func update_hands_target() → void:
if gameplay_ui.current_target != null and is_instance_valid(gameplay_ui.current_target):
look_at_target(gameplay_ui.current_target)
else:
reset_pos()
func look_at_target(target: Node3D) → void:
if target and is_instance_valid(target):
var target_position: Vector3 = target.global_position
var direction: Vector3 = (target_position - global_position).normalized()
var target_rotation: Quaternion = Quaternion(Vector3.UP, atan2(direction.x, direction.z))
rotation = target_rotation.get_euler()
for ik in skeletonik3d:
ik.start()
ik.set_target_node(target.get_path())
ik.set_interpolation(1.0)
else:
reset_pos()
func reset_pos() → void:
for ik in skeletonik3d:
ik.stop()
ik.target_node = NodePath()
func sword_attack() → void:
if is_attacking or !can_interrupt_animation:
return
is_attacking = true
can_interrupt_animation = false
action_anim = “”
area_sword.sword_can_hit(true)
change_weapon_visibility(“only_action_sword”, true)
if anim_player.has_animation(“run_sword0”):
play_anim_attack()
await anim_player.animation_finished
else:
print(“No animation found for sword0.”)
area_sword.sword_can_hit(false)
is_attacking = false
action_anim = “_sword”
change_weapon_visibility(“only_action_sword”, true)
can_move = true
can_interrupt_animation = true
func play_anim_attack() → void:
if anim_player.current_animation.begins_with(“run”):
play_animation(“run_sword0”)
elif anim_player.current_animation.begins_with(“stance”):
can_move = false
play_animation(“stance_sword0”)
else:
return
func rage(state: bool) → void:
is_raging = state
if is_raging:
area_sword.rage(true)
current_speed = BASE_SPEED * 2.0
anim_player.speed_scale = 2.0
mesh.rage()
else:
area_sword.rage(false)
current_speed = BASE_SPEED
anim_player.speed_scale = 1.0
mesh.no_rage()
func sit_on_bike() → void:
action_sword.visible = false
sword.visible = true
can_move = false
play_animation(“bike_sit”)
col.disabled = true
func play_my_sfx(Sound: String) → void:
if Sound == “” or Sound == null or SFX == null:
return
SFX.play_sound(Sound)
func not_sit_on_bike() → void:
col.disabled = false
gravity = ProjectSettings.get_setting(“physics/3d/default_gravity”)
can_move = true
func find_target_camera() → Node3D:
return find_camera_in_node(get_tree().get_root())
func find_node_in_group_only(group_name: String) → Node:
var nodes: Array[Node] = get_tree().get_nodes_in_group(group_name)
for node in nodes:
return node
return null
func find_camera_in_node(node: Node) → Node3D:
for child in node.get_children():
if child.name == “Player” + str(_device) + “Camera”:
return child
var found: Node3D = find_camera_in_node(child)
if found:
return found
return null
func more_hp(h: float) → void:
play_my_sfx(“exp”)
gameplay_ui.more_hp(h)
func more_exp(ex: float) → void:
play_my_sfx(“exp”)
gameplay_ui.more_exp(ex)
func more_rage(r: float) → void:
play_my_sfx(“exp”)
gameplay_ui.more_rage(r)
func _physics_process(delta: float) → void:
if auto_lock:
update_hands_target()
if is_dashing:
handle_dash(delta)
else:
handle_input(delta)
apply_gravity(delta)
if can_move:
move_and_slide()
if position.y < -7:
respawn()
if not is_on_floor() and velocity.y < 0 and can_move:
play_animation("fall2")
orb = find_orb_in_group()
if orb != null:
var distance: float = global_transform.origin.distance_to(orb.global_transform.origin)
if distance <= min_attraction_distance:
var direction_to_orb: Vector3 = (global_transform.origin - orb.global_transform.origin).normalized()
orb.global_transform.origin += direction_to_orb * attraction_speed * delta
func respawn() → void:
play_my_sfx(“kick”)
save_system.my_vibration(_device, 0.9, 0.5)
gameplay_ui.zero_point()
position = Vector3(0, 0, 0)
velocity = Vector3.ZERO
gameplay_ui.less_hp(2.0)
func dead_cannot_control() → void:
can_move = false
play_animation(“dead”)
func handle_input(delta: float) → void:
if !can_move:
velocity.x = 0
velocity.z = 0
return
var input: Vector2 = Vector2(
Input.get_action_strength("move_right_" + str(_device)) - Input.get_action_strength("move_left_" + str(_device)),
Input.get_action_strength("move_up_" + str(_device)) - Input.get_action_strength("move_back_" + str(_device))
).normalized()
if is_on_floor():
if input.length() > 0:
if !is_attacking:
play_animation("run" + action_anim)
move_character(input, delta)
else:
if !is_attacking:
play_animation("stance" + action_anim)
velocity.x = 0
velocity.z = 0
if Input.is_action_just_pressed("a_" + str(_device)):
jump()
func activ_speed(S: bool) → void:
if S:
gameplay_ui.activ_speed_effect(1.0)
else:
gameplay_ui.activ_speed_effect(0.0)
func move_character(input: Vector2, delta: float) → void:
var camera_forward: Vector3 = -camera.global_transform.basis.z.normalized()
var camera_right: Vector3 = camera.global_transform.basis.x.normalized()
var target_direction: Vector3 = (camera_forward * input.y + camera_right * input.x).normalized()
if target_direction.length() > 0:
rotation.y = lerp_angle(rotation.y, atan2(target_direction.x, target_direction.z), rotation_speed * delta)
velocity.x = target_direction.x * current_speed
velocity.z = target_direction.z * current_speed
func apply_gravity(delta: float) → void:
if not is_on_floor():
velocity.y -= gravity * delta
func start_dash() → void:
if not is_dashing:
is_dashing = true
dash_timer = 0.0
play_my_sfx(“dash”)
play_animation(“dash”)
var input_direction: Vector2 = Vector2(
Input.get_action_strength("move_right_" + str(_device)) - Input.get_action_strength("move_left_" + str(_device)),
Input.get_action_strength("move_up_" + str(_device)) - Input.get_action_strength("move_back_" + str(_device))
).normalized()
var camera_forward: Vector3 = -camera.global_transform.basis.z.normalized()
var camera_right: Vector3 = camera.global_transform.basis.x.normalized()
if input_direction.length() > 0:
var target_direction: Vector3 = (camera_forward * input_direction.y + camera_right * input_direction.x).normalized()
velocity.x = target_direction.x * dash_speed
velocity.z = target_direction.z * dash_speed
else:
velocity.x = camera_forward.x * dash_speed
velocity.z = camera_forward.z * dash_speed
velocity.y = 0
func handle_dash(delta: float) → void:
dash_timer += delta
if dash_timer >= dash_duration:
is_dashing = false
velocity.x = 0
velocity.z = 0
func play_animation(animation_name: String) → void:
if anim_player.has_animation(animation_name):
anim_player.play(animation_name, 0.4)
func jump() → void:
if is_on_floor() or jumps_made < max_jumps:
velocity.y = jump_speed
jumps_made += 1
if not is_on_floor():
play_my_sfx("double_jump")
play_animation("double_jump")
else:
play_my_sfx("jump")
play_animation("jump_air")
if is_on_floor():
jumps_made = 0