class_name Player extends CharacterBody3D @export var walk_speed = 12 @export var jump_power = 32 @export var fall_speed = 86 const gear_slots = ["1", "2", "3"] @onready var spawn = Vector3(position) var starting_gear = preload("res://gears/gear.tscn") var dead = false var protected = false var suspended = false var direction = Vector3.ZERO var target_velocity = Vector3.ZERO var _health = 100 # Player UI func resize_ui(): var hud_h = 200 var size = get_viewport().get_visible_rect().size $Message.size.x = size.x $HUD.size.x = size.x $HUD.size.y = hud_h $HUD.position.x = 0 $HUD.position.y = size.y - hud_h func make_hud(): var text = "" for node in $Backpack.get_children(): if not node is Gear: continue text += "%s (%s)\n" % [node.gear_name(), node.name] text += "Holding " + $Pivot/Container/Gear.gear_name() + "\n" text += str(health()) + " hp" $HUD.text = text func message(string): $Message.text = string $Message.visible = true $Message/VanishTimer.start() # State functions func harm(hp): if protected: return assert(hp >= 0) _health -= hp make_hud() func heal(hp): assert(hp >= 0) _health += hp make_hud() func health(): return _health func _die(): dead = true suspended = true visible = false $HUD.visible = false # strip gears for gear in $Backpack.get_children(): gear.queue_free() if has_node("Pivot/Container/Gear"): $Pivot/Container/Gear.unequip(self) $Pivot/Container/Gear.queue_free() $RespawnTimer.start() message("Le gone") func die(): if protected: return _die() func respawn(): position = Vector3(spawn) $CameraGimbal.reset() _health = 100 visible = true dead = false # add starting gear var gear = starting_gear.instantiate() $Pivot/Container.add_child(gear) make_hud() $HUD.visible = true # Backpack functions func find_free_slot(): for slot in gear_slots: if not has_node("Backpack/" + slot): return slot return null func use_backpack_slot(n): assert(gear_slots.has(n)) var gear_node = "Backpack/" + n var old = $Pivot/Container/Gear # check if gear is at slot if not has_node(gear_node): message("Gear not found") return # place current gear in first free slot var slot = find_free_slot() if slot: old.unequip(self) old.name = slot old.reparent($Backpack, false) get_node(gear_node).reparent($Pivot/Container, false) get_node("Pivot/Container/" + n).name = "Gear" make_hud() return # couldn't find a free slot, so replace # the new slot with the current gear get_node(gear_node).reparent($Pivot/Container, false) old.unequip(self) old.reparent($Backpack, false) old.name = n get_node("Pivot/Container/" + n).name = "Gear" make_hud() return func equip(gear: Gear): var gear_name = gear.gear_name() # do we have the gear equipped? if gear_name == $Pivot/Container/Gear.gear_name(): return false # do we have the gear in our backpack? for item in $Backpack.get_children(): if item.gear_name() == gear_name: use_backpack_slot(item.name) return false # we do not have the gear, so set aside # the current gear and equip the new one var old = $Pivot/Container/Gear var new_gear: Gear # place current gear in first free slot var slot = find_free_slot() if slot: old.unequip(self) old.name = slot old.reparent($Backpack, false) new_gear = gear.duplicate() new_gear.name = "Gear" $Pivot/Container.add_child(new_gear) make_hud() message("You picked up a " + gear_name + "!") return true # if no slots are free message("Backpack full") return false # Player mechanics func move_player(x, z): var camera_basis = $CameraGimbal.get_global_transform().basis # rotate player get_global_transform().basis = Basis($CameraGimbal.basis) var z_basis = -camera_basis.z if z == 1: z_basis = -z_basis $Pivot.basis = Basis.looking_at(z_basis.normalized()) $CollisionShape3D.basis = $Pivot.basis # apply camera direction to movement direction if z != 0: # adding the value to the existing direction # variable lets the user press multiple keys # to move diagonally direction += camera_basis.z * z if x != 0: direction += camera_basis.x * x func do_backpack_keys(): if Input.is_action_just_pressed("backpack_1"): use_backpack_slot("1") if Input.is_action_just_pressed("backpack_2"): use_backpack_slot("2") if Input.is_action_just_pressed("backpack_3"): use_backpack_slot("3") func do_movement(delta): var mx = 0 var mz = 0 if not suspended: if Input.is_action_pressed("move_forward"): mz -= 1 if Input.is_action_pressed("move_back"): mz += 1 if Input.is_action_pressed("move_left"): mx -= 1 if Input.is_action_pressed("move_right"): mx += 1 if Input.is_action_pressed("jump") and is_on_floor(): target_velocity.y = jump_power if !(mx == 0 and mz == 0): move_player(mx, mz) target_velocity.x = direction.x * walk_speed target_velocity.z = direction.z * walk_speed direction = Vector3.ZERO if not is_on_floor(): target_velocity.y = target_velocity.y - (fall_speed * delta) velocity = target_velocity move_and_slide() func do_gears(): var gear = $Pivot/Container/Gear assert(gear is Gear) if gear.continuous(): if Input.is_action_pressed("gear_use"): gear.use(self) else: if Input.is_action_just_pressed("gear_use"): gear.use(self) # Engine callbacks func _ready(): get_viewport().size_changed.connect(resize_ui) resize_ui() respawn() func _physics_process(delta): if dead: return if health() < 1 or position.y <= -1000: _die() # Backpack keys do_backpack_keys() # Movement do_movement(delta) # Use gears do_gears() # Signals func _on_vanish_timer_timeout(): $Message.visible = false # Ensure that gears are invisible in the backpack. # As children of a Node they will be detached from # 3D space so if visible is true they may be visibly # littered on the ground where the player changed # gears. func _on_backpack_child_entered_tree(node): assert(node is Gear) node.visible = false func _on_backpack_child_exiting_tree(node): assert(node is Gear) node.visible = true