точно есть дэшм

This commit is contained in:
2026-07-21 00:27:17 +07:00
parent 1df926f1d8
commit d74fa3bd80
601 changed files with 2375919 additions and 0 deletions
@@ -0,0 +1,7 @@
[gd_resource type="StandardMaterial3D" format=3 uid="uid://f3f1s20quvcb"]
[resource]
albedo_color = Color(0.14902, 0.298039, 0.984314, 1)
emission_enabled = true
emission = Color(0.14902, 0.298039, 0.984314, 1)
emission_energy_multiplier = 0.2
@@ -0,0 +1,13 @@
shader_type canvas_item;
void fragment() {
// Вычисляем расстояние от центра экрана (от 0.0 до 1.0)
vec2 uv = UV - 0.5;
float dist = length(uv);
// Плавное размытие к краям экрана
float vignette = smoothstep(0.3, 0.6, dist);
// Применяем красный цвет только к краям виньетки
COLOR = vec4(1.0, 0.0, 0.0, vignette * 0.4); // 0.4 — это максимальная яркость краев
}
@@ -0,0 +1 @@
uid://jffp0g42qpmg
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,62 @@
extends CanvasLayer
@onready var health_text = $HealthText
@onready var vignette = $Vignette
var health_component: Node = null
func _ready() -> void:
# Ищем HealthComponent у нашего игрока (родителя)
if get_parent():
health_component = get_parent().find_child("HealthComponent", true, false)
if health_component == null:
print("[UI ЗДОРОВЬЯ] КРИТИЧЕСКАЯ ОШИБКА: HealthComponent игрока не найден!")
# Изначально принудительно настраиваем видимость
if health_text:
health_text.visible = true
if vignette:
vignette.visible = false
func _process(delta: float) -> void:
if health_component == null:
return
var current_health = health_component.current_health
# 1. ОБНОВЛЯЕМ ЦИФРЫ НА ЭКРАНЕ
if health_text:
health_text.text = str(clamp(int(current_health), 0, 100))
# Красим цифры при низком здоровье
if current_health <= 30:
health_text.add_theme_color_override("font_color", Color.RED)
else:
health_text.add_theme_color_override("font_color", Color.WHITE)
# 2. УПРАВЛЯЕМ КРАСНОЙ ВИНЬЕТКОЙ
if vignette:
if current_health <= 30 and current_health > 0:
vignette.visible = true
# Эффект плавного биения сердца (пульсация)
var color_rect = vignette.get_node_or_null("ColorRect")
if color_rect:
color_rect.visible = true
var pulse = (sin(Time.get_ticks_msec() * 0.005) + 1.0) / 2.0
color_rect.modulate.a = lerp(0.3, 1.0, pulse)
else:
vignette.visible = false
# 3. АВТОРЕСТАРТ ПРИ СМЕРТИ (Перезагрузка уровня)
if current_health <= 0:
print("[UI ЗДОРОВЬЯ] Игрок погиб! Перезагружаю уровень через 2 секунды...")
# Отключаем процесс, чтобы не спамить перезагрузкой
set_process(false)
# Ждем 2 секунды, чтобы игрок успел понять, что умер
await get_tree().create_timer(2.0).timeout
# Мягко перезапускаем текущую сцену
get_tree().reload_current_scene()
@@ -0,0 +1 @@
uid://ccjc1yqh1eb0k
@@ -0,0 +1,114 @@
extends Node3D
#class name
class_name CameraObject
@export_group("Camera variables")
@export_range(0.0, 5.0, 0.01) var XAxisSens : float
@export_range(0.0, 5.0, 0.01) var YAxisSens : float
@export var maxUpAngleView : float
@export var maxDownAngleView : float
@export_group("FOV variables")
@export var startFOV : float
@export var runFOV : float
@export var fovTransitionSpeed : float
@export_group("Movement changes variables")
@export var baseCamAngle : float
@export var crouchCamAngle : float
@export var baseCameraLerpSpeed : float
@export var crouchCameraLerpSpeed : float
@export var crouchCameraDepth : float
@export_group("Camera bob variables")
@export var enableBob : bool = true
var headBobValue : float
@export var bobFrequency : float
@export var bobAmplitude : float
@export_group("Camera tilt variables")
@export var enableTilt : bool = true
@export var tiltRotationValue : float
@export var tiltRotationSpeed : float
@export var inAirTiltValDivider : float
@export_group("Input variables")
var mouseInput : Vector2
@export var mouseInputSpeed : float
var playCharInputDir : Vector2
#Mouse variables
var mouseFree : bool = false
@export_group("Keybind variables")
@export var mouseModeAction : String = ""
#References variables
@onready var camera : Camera3D = %Camera
@onready var playChar : PlayerCharacter = $".."
@onready var weaponManager : Node3D = %WeaponManager
func _ready():
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) #set mouse as captured
func _unhandled_input(event):
#this function manage camera rotation (360 on x axis, blocked at <= -60 and >= 60 on y axis, to not having the character do a complete head turn, which will be kinda weird)
if event is InputEventMouseMotion:
rotate_y(-event.relative.x * (XAxisSens / 10))
camera.rotate_x(-event.relative.y * (YAxisSens / 10))
camera.rotation.x = clamp(camera.rotation.x, deg_to_rad(maxUpAngleView), deg_to_rad(maxDownAngleView))
mouseInput = event.relative #get position of the mouse in a 2D sceen, so save it in a Vector2
func _process(delta):
applies(delta)
cameraBob(delta)
cameraTilt(delta)
func applies(delta : float):
#manage the differents camera modifications relative to a specific state, except for the FOV
if playChar.stateMachine.currStateName == "Crouch":
position.y = lerp(position.y, 0.715 + crouchCameraDepth, crouchCameraLerpSpeed * delta)
rotation.z = lerp(rotation.z, deg_to_rad(crouchCamAngle) * playChar.inputDirection.x if playChar.inputDirection.x != 0.0 else deg_to_rad(crouchCamAngle), crouchCameraLerpSpeed * delta)
elif playChar.stateMachine.currStateName == "Run":
camera.fov = lerp(camera.fov, runFOV, fovTransitionSpeed * delta)
rotation.z = lerp(rotation.z, deg_to_rad(baseCamAngle), baseCameraLerpSpeed * delta)
elif playChar.stateMachine.currStateName == "Jump":
# Maintain the current FOV when jumping
camera.fov = lerp(camera.fov, camera.fov, fovTransitionSpeed * delta)
elif playChar.stateMachine.currStateName == "Inair":
# Maintain the current FOV when in air
camera.fov = lerp(camera.fov, camera.fov, fovTransitionSpeed * delta)
else:
position.y = lerp(position.y, 0.715, baseCameraLerpSpeed * delta)
rotation.z = lerp(rotation.z, deg_to_rad(baseCamAngle), baseCameraLerpSpeed * delta)
camera.fov = lerp(camera.fov, startFOV, fovTransitionSpeed * delta)
func cameraBob(delta):
if enableBob:
headBobValue += delta * playChar.velocity.length() * float(playChar.is_on_floor())
camera.transform.origin = headbob(headBobValue, bobFrequency, bobAmplitude)
func headbob(time, bobFreq, bobAmpli):
#some trigonometry stuff here, basically it uses the cosinus and sinus functions (sinusoidal function) to get a nice and smooth bob effect
var pos = Vector3.ZERO
pos.y = sin(time * bobFreq) * bobAmpli
pos.x = cos(time * bobFreq / 2) * bobAmpli
return pos
func cameraTilt(delta):
if enableTilt:
#this function manage the camera tilting when the character is moving on the x axis (left and right)
if playChar.moveDirection != Vector3.ZERO and playChar.inputDirection != Vector2.ZERO:
playCharInputDir = playChar.inputDirection #get input direction to know where the character is heading to
#apply smooth tilt movement
if !playChar.is_on_floor(): rotation.z = lerp(rotation.z, -playCharInputDir.x * tiltRotationValue/inAirTiltValDivider, tiltRotationSpeed * delta)
else: rotation.z = lerp(rotation.z, -playCharInputDir.x * tiltRotationValue, tiltRotationSpeed * delta)
func mouseMode():
#manage the mouse mode (visible = can use mouse on the screen, captured = mouse not visible and locked in at the center of the screen)
if Input.is_action_just_pressed(mouseModeAction): mouseFree = !mouseFree
if !mouseFree: Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
else: Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
@@ -0,0 +1 @@
uid://b0fbr48rbvn27
@@ -0,0 +1,6 @@
extends Camera3D
@onready var cam : Camera3D = %Camera
func _process(_delta : float):
global_transform = cam.global_transform
@@ -0,0 +1 @@
uid://ba0b0ekmy1qub
@@ -0,0 +1,18 @@
extends Node3D
@onready var ammoManager : Node3D = %AmmunitionManager
@onready var weaponManager : Node3D = %WeaponManager
func ammoRefillLink(ammoDict : Dictionary):
for key in ammoDict.keys():
if key in ammoManager.ammoDict:
#two cases for the min function here :
#1 :
var nbAmmoToRefill : int = min(ammoManager.maxNbPerAmmoDict[key] - ammoManager.ammoDict[key], ammoDict[key])
ammoManager.ammoDict[key] += nbAmmoToRefill
@@ -0,0 +1 @@
uid://cnpa03x4ajc8b
@@ -0,0 +1,86 @@
extends State
class_name CrouchState
var stateName : String = "Crouch"
var cR : CharacterBody3D
func enter(charRef : CharacterBody3D):
cR = charRef
verifications()
func verifications():
cR.moveSpeed = cR.crouchSpeed
cR.moveAccel = cR.crouchAccel
cR.moveDeccel = cR.crouchDeccel
cR.floor_snap_length = 1.0
if cR.jumpCooldown > 0.0: cR.jumpCooldown = -1.0
if cR.nbJumpsInAirAllowed < cR.nbJumpsInAirAllowedRef: cR.nbJumpsInAirAllowed = cR.nbJumpsInAirAllowedRef
if cR.coyoteJumpCooldown < cR.coyoteJumpCooldownRef: cR.coyoteJumpCooldown = cR.coyoteJumpCooldownRef
func physics_update(delta : float):
checkIfFloor()
applies(delta)
cR.gravityApply(delta)
inputManagement()
move(delta)
func checkIfFloor():
if !cR.is_on_floor() and !cR.is_on_wall():
if cR.velocity.y < 0.0:
transitioned.emit(self, "InairState")
if cR.is_on_floor():
if cR.jumpBuffOn:
cR.bufferedJump = true
cR.jumpBuffOn = false
transitioned.emit(self, "JumpState")
func applies(delta : float):
if cR.hitGroundCooldown > 0.0: cR.hitGroundCooldown -= delta
cR.hitbox.shape.height = lerp(cR.hitbox.shape.height, cR.crouchHitboxHeight, cR.heightChangeSpeed * delta)
cR.model.scale.y = lerp(cR.model.scale.y, cR.crouchModelHeight, cR.heightChangeSpeed * delta)
func inputManagement():
if Input.is_action_just_pressed(cR.jumpAction):
if !raycastVerification(): #if nothing block the player character when it will leaves the crouch state
transitioned.emit(self, "JumpState")
if cR.continiousCrouch:
#has to press run button once to run
if Input.is_action_just_pressed(cR.crouchAction):
if !raycastVerification():
cR.walkOrRun = "WalkState"
transitioned.emit(self, "WalkState")
else:
#has to continuously press crouch button to crouch
if !Input.is_action_pressed(cR.crouchAction):
if !raycastVerification():
cR.walkOrRun = "WalkState"
transitioned.emit(self, "WalkState")
func raycastVerification():
#check if the raycast used to check ceilings is colliding or not
return cR.ceilingCheck.is_colliding()
func move(delta : float):
cR.inputDirection = Input.get_vector(cR.moveLeftAction, cR.moveRightAction, cR.moveForwardAction, cR.moveBackwardAction)
cR.moveDirection = (cR.camHolder.global_basis * Vector3(cR.inputDirection.x, 0.0, cR.inputDirection.y)).normalized()
if cR.moveDirection and cR.is_on_floor():
cR.velocity.x = lerp(cR.velocity.x, cR.moveDirection.x * cR.moveSpeed, cR.moveAccel * delta)
cR.velocity.z = lerp(cR.velocity.z, cR.moveDirection.z * cR.moveSpeed, cR.moveAccel * delta)
else:
cR.velocity.x = lerp(cR.velocity.x, 0.0, cR.moveDeccel * delta)
cR.velocity.z = lerp(cR.velocity.z, 0.0, cR.moveDeccel * delta)
if cR.hitGroundCooldown <= 0: cR.desiredMoveSpeed = cR.velocity.length()
if cR.desiredMoveSpeed >= cR.maxSpeed: cR.desiredMoveSpeed = cR.maxSpeed
@@ -0,0 +1 @@
uid://bhoqhv7escpyx
@@ -0,0 +1,59 @@
extends State
class_name DashState
var stateName : String = "Dash"
var cR : CharacterBody3D
var dash_timer : float = 0.0
func enter(charRef : CharacterBody3D):
cR = charRef
# 1. Задаем таймер длительности дэша из настроек игрока
dash_timer = cR.dash_duration
cR.dash_cooldown_timer = cR.dash_cooldown
cR.is_dashing = true
# 2. Определяем направление рывка
# Если игрок бежал — дэшим в сторону бега. Если стоял — строго вперед, куда смотрит камера
cR.dash_direction = cR.moveDirection
if cR.dash_direction == Vector3.ZERO:
cR.dash_direction = -cR.camHolder.global_transform.basis.z
cR.dash_direction.y = 0 # Чтобы не улетать в небо
cR.dash_direction = cR.dash_direction.normalized()
# 3. Включаем сочный эффект FOV на камере игрока
if cR.player_camera:
var tween = create_tween()
tween.tween_property(cR.player_camera, "fov", cR.normal_fov + 15.0, 0.1)
func physics_update(delta : float):
# Отсчитываем время действия дэша
dash_timer -= delta
if dash_timer <= 0:
# Рывок окончен! Возвращаем FOV камеры назад
cR.is_dashing = false
if cR.player_camera:
var tween = create_tween()
tween.tween_property(cR.player_camera, "fov", cR.normal_fov, 0.15)
# Плавно переходим обратно в ходьбу, бег или воздух в зависимости от того, где мы оказались
if cR.is_on_floor():
if cR.walkOrRun == "RunState":
transitioned.emit(self, "RunState")
else:
transitioned.emit(self, "WalkState")
else:
transitioned.emit(self, "InairState")
return
# Принудительно толкаем физическое тело игрока во время рывка
cR.velocity.x = cR.dash_direction.x * cR.dash_speed
cR.velocity.z = cR.dash_direction.z * cR.dash_speed
# Применяем гравитацию (опционально, чтобы игрок мог дэшиться в воздухе и плавно падать)
cR.gravityApply(delta)
cR.modifyPhysicsProperties()
@@ -0,0 +1 @@
uid://t4bhhfuq3vda
@@ -0,0 +1,83 @@
extends State
class_name IdleState
var stateName : String = "Idle"
var cR : CharacterBody3D
func enter(char_ref : CharacterBody3D):
#pass play char reference
cR = char_ref
verifications()
func verifications():
#manage the appliements that need to be set at the start of the state
cR.floor_snap_length = 1.0
if cR.jumpCooldown > 0.0: cR.jumpCooldown = -1.0
if cR.nbJumpsInAirAllowed < cR.nbJumpsInAirAllowedRef: cR.nbJumpsInAirAllowed = cR.nbJumpsInAirAllowedRef
if cR.coyoteJumpCooldown < cR.coyoteJumpCooldownRef: cR.coyoteJumpCooldown = cR.coyoteJumpCooldownRef
func physics_update(delta : float):
checkIfFloor()
applies(delta)
cR.gravityApply(delta)
inputManagement()
move(delta)
func checkIfFloor():
#manage the appliements and state transitions that needs to be sets/checked/performed
#every time the play char pass through one of the following : floor-inair-onwall
if !cR.is_on_floor() and !cR.is_on_wall():
transitioned.emit(self, "InairState")
if cR.is_on_floor():
if cR.jumpBuffOn:
cR.bufferedJump = true
cR.jumpBuffOn = false
transitioned.emit(self, "JumpState")
func applies(delta : float):
#manage the appliements of things that needs to be set/checked/performed every frame
if cR.hitGroundCooldown > 0.0: cR.hitGroundCooldown -= delta
cR.hitbox.shape.height = lerp(cR.hitbox.shape.height, cR.baseHitboxHeight, cR.heightChangeSpeed * delta)
cR.model.scale.y = lerp(cR.model.scale.y, cR.baseModelHeight, cR.heightChangeSpeed * delta)
func inputManagement():
#manage the state transitions depending on the actions inputs
if Input.is_action_just_pressed(cR.jumpAction):
transitioned.emit(self, "JumpState")
if Input.is_action_just_pressed(cR.crouchAction):
transitioned.emit(self, "CrouchState")
if Input.is_action_just_pressed(cR.runAction):
if cR.walkOrRun == "WalkState": cR.walkOrRun = "RunState"
elif cR.walkOrRun == "RunState": cR.walkOrRun = "WalkState"
func move(delta : float):
#manage the character movement
#direction input
cR.inputDirection = Input.get_vector(cR.moveLeftAction, cR.moveRightAction, cR.moveForwardAction, cR.moveBackwardAction)
#get the move direction depending on the input
cR.moveDirection = (cR.camHolder.global_basis * Vector3(cR.inputDirection.x, 0.0, cR.inputDirection.y)).normalized()
if cR.moveDirection and cR.is_on_floor():
#transition to corresponding state
transitioned.emit(self, cR.walkOrRun)
else:
#apply smooth stop
cR.velocity.x = lerp(cR.velocity.x, 0.0, cR.moveDeccel * delta)
cR.velocity.z = lerp(cR.velocity.z, 0.0, cR.moveDeccel * delta)
#cancel desired move speed accumulation if the timer has elapsed (is up)
if cR.hitGroundCooldown <= 0: cR.desiredMoveSpeed = cR.velocity.length()
#set to ensure the character don't exceed the max speed authorized
if cR.desiredMoveSpeed >= cR.maxSpeed: cR.desiredMoveSpeed = cR.maxSpeed
@@ -0,0 +1 @@
uid://stil4xkf3tk0
@@ -0,0 +1,77 @@
extends State
class_name InairState
var stateName : String = "Inair"
var cR : CharacterBody3D
func enter(charRef : CharacterBody3D):
cR = charRef
verifications()
func verifications():
if cR.floor_snap_length != 0.0: cR.floor_snap_length = 0.0
if cR.hitGroundCooldown != cR.hitGroundCooldownRef: cR.hitGroundCooldown = cR.hitGroundCooldownRef
func physics_update(delta : float):
applies(delta)
cR.gravityApply(delta)
inputManagement()
checkIfFloor()
move(delta)
func applies(delta : float):
if !cR.is_on_floor():
if cR.jumpCooldown > 0.0: cR.jumpCooldown -= delta
if cR.coyoteJumpCooldown > 0.0: cR.coyoteJumpCooldown -= delta
cR.hitbox.shape.height = lerp(cR.hitbox.shape.height, cR.baseHitboxHeight, cR.heightChangeSpeed * delta)
cR.model.scale.y = lerp(cR.model.scale.y, cR.baseModelHeight, cR.heightChangeSpeed * delta)
func inputManagement():
if Input.is_action_just_pressed(cR.jumpAction):
#check if can jump buffer
if cR.floorCheck.is_colliding() and cR.lastFramePosition.y > cR.position.y and cR.nbJumpsInAirAllowed <= 0: cR.jumpBuffOn = true
#check if can coyote jump
if cR.wasOnFloor and cR.coyoteJumpCooldown > 0.0 and cR.lastFramePosition.y > cR.position.y:
cR.coyoteJumpOn = true
transitioned.emit(self, "JumpState")
transitioned.emit(self, "JumpState")
func checkIfFloor():
if cR.is_on_floor():
if cR.jumpBuffOn:
cR.bufferedJump = true
cR.jumpBuffOn = false
transitioned.emit(self, "JumpState")
else:
if cR.moveDirection: transitioned.emit(self, cR.walkOrRun)
else: transitioned.emit(self, "IdleState")
if cR.is_on_wall():
cR.velocity.x = 0.0
cR.velocity.z = 0.0
func move(delta : float):
cR.inputDirection = Input.get_vector(cR.moveLeftAction, cR.moveRightAction, cR.moveForwardAction, cR.moveBackwardAction)
cR.moveDirection = (cR.camHolder.global_basis * Vector3(cR.inputDirection.x, 0.0, cR.inputDirection.y)).normalized()
if !cR.is_on_floor():
if cR.moveDirection:
if cR.desiredMoveSpeed < cR.maxSpeed: cR.desiredMoveSpeed += cR.bunnyHopDmsIncre * delta
var contrdDesMoveSpeed : float = cR.desiredMoveSpeedCurve.sample(cR.desiredMoveSpeed)
var contrdInAirMoveSpeed : float = cR.inAirMoveSpeedCurve.sample(cR.desiredMoveSpeed) * cR.inAirInputMultiplier
cR.velocity.x = lerp(cR.velocity.x, cR.moveDirection.x * contrdDesMoveSpeed, contrdInAirMoveSpeed * delta)
cR.velocity.z = lerp(cR.velocity.z, cR.moveDirection.z * contrdDesMoveSpeed, contrdInAirMoveSpeed * delta)
else:
cR.desiredMoveSpeed = cR.velocity.length()
if cR.desiredMoveSpeed >= cR.maxSpeed: cR.desiredMoveSpeed = cR.maxSpeed
@@ -0,0 +1 @@
uid://bh3rdvokmrar1
@@ -0,0 +1,111 @@
extends State
class_name JumpState
var stateName : String = "Jump"
var cR : CharacterBody3D
func enter(charRef : CharacterBody3D):
cR = charRef
verifications()
jump()
func verifications():
if cR.floor_snap_length != 0.0: cR.floor_snap_length = 0.0
if cR.jumpCooldown < cR.jumpCooldownRef: cR.jumpCooldown = cR.jumpCooldownRef
if cR.hitGroundCooldown != cR.hitGroundCooldownRef: cR.hitGroundCooldown = cR.hitGroundCooldownRef
func physics_update(delta : float):
applies(delta)
cR.gravityApply(delta)
inputManagement()
checkIfFloor()
move(delta)
func applies(delta : float):
if !cR.is_on_floor():
if cR.jumpCooldown > 0.0: cR.jumpCooldown -= delta
if cR.coyoteJumpCooldown > 0.0: cR.coyoteJumpCooldown -= delta
cR.hitbox.shape.height = lerp(cR.hitbox.shape.height, cR.baseHitboxHeight, cR.heightChangeSpeed * delta)
cR.model.scale.y = lerp(cR.model.scale.y, cR.baseModelHeight, cR.heightChangeSpeed * delta)
func inputManagement():
if Input.is_action_just_pressed(cR.jumpAction):
jump()
func checkIfFloor():
if !cR.is_on_floor() and cR.velocity.y < 0.0:
transitioned.emit(self, "InairState")
if cR.is_on_floor():
if cR.moveDirection: transitioned.emit(self, cR.walkOrRun)
else: transitioned.emit(self, "IdleState")
#lose all velocity if play char hit a wall
if cR.is_on_wall():
cR.velocity.x = 0.0
cR.velocity.z = 0.0
func move(delta : float):
cR.inputDirection = Input.get_vector(cR.moveLeftAction, cR.moveRightAction, cR.moveForwardAction, cR.moveBackwardAction)
cR.moveDirection = (cR.camHolder.global_basis * Vector3(cR.inputDirection.x, 0.0, cR.inputDirection.y)).normalized()
#move only apply when the character is not on the floor (so if he's in the air)
if !cR.is_on_floor():
if cR.moveDirection:
if cR.desiredMoveSpeed < cR.maxSpeed: cR.desiredMoveSpeed += cR.bunnyHopDmsIncre * delta
var contrdDesMoveSpeed : float = cR.desiredMoveSpeedCurve.sample(cR.desiredMoveSpeed)
var contrdInAirMoveSpeed : float = cR.inAirMoveSpeedCurve.sample(cR.desiredMoveSpeed) * cR.inAirInputMultiplier
cR.velocity.x = lerp(cR.velocity.x, cR.moveDirection.x * contrdDesMoveSpeed, contrdInAirMoveSpeed * delta)
cR.velocity.z = lerp(cR.velocity.z, cR.moveDirection.z * contrdDesMoveSpeed, contrdInAirMoveSpeed * delta)
if cR.velocity.length() > cR.maxSpeed:
cR.velocity = cR.velocity.normalized() * cR.maxSpeed
else:
#accumulate desired speed for bunny hopping
cR.desiredMoveSpeed = cR.velocity.length()
if cR.desiredMoveSpeed >= cR.maxSpeed: cR.desiredMoveSpeed = cR.maxSpeed
func jump():
#manage the jump behaviour, depending of the different variables and states the character is
var canJump : bool = false #jump condition
#in air jump
if !cR.is_on_floor():
if !cR.coyoteJumpOn and cR.nbJumpsInAirAllowed > 0:
cR.nbJumpsInAirAllowed -= 1
cR.jumpCooldown = cR.jumpCooldownRef
canJump = true
if cR.coyoteJumpOn:
cR.jumpCooldown = cR.jumpCooldownRef
cR.coyoteJumpCooldown = -1.0 #so that the character cannot immediately make another coyote jump
cR.coyoteJumpOn = false
canJump = true
#on floor jump
if cR.is_on_floor():
cR.jumpCooldown = cR.jumpCooldownRef
canJump = true
#jump buffering
if cR.bufferedJump:
cR.bufferedJump = false
cR.nbJumpsInAirAllowed = cR.nbJumpsInAirAllowedRef
#apply jump
if canJump:
cR.velocity.y = cR.jumpVelocity
canJump = false
@@ -0,0 +1 @@
uid://dvu58wf01wils
@@ -0,0 +1,215 @@
extends CharacterBody3D
class_name PlayerCharacter
@export_group("Dash Mechanics")
@export var dash_speed: float = 15.0 # Скорость игрока во время рывка
@export var dash_duration: float = 0.45 # Длительность рывка в секундах
@export var dash_cooldown: float = 2.0 # Перезарядка дэша (в секундах)
var is_dashing: bool = false
var dash_timer: float = 0.0
var dash_cooldown_timer: float = 0.0
var dash_direction: Vector3 = Vector3.ZERO
@onready var player_camera: Camera3D = %Camera # Ссылка на камеру (убедись, что %Camera есть в сцене)
var normal_fov: float = 75.0 # Стандартный угол обзора
@export_group("Movement variables")
var moveSpeed : float
var moveAccel : float
var moveDeccel : float
var desiredMoveSpeed : float
@export var desiredMoveSpeedCurve : Curve
@export var maxSpeed : float
@export var inAirMoveSpeedCurve : Curve
var inputDirection : Vector2
var moveDirection : Vector3
@export var hitGroundCooldown : float #amount of time the character keep his accumulated speed before losing it (while being on ground)
var hitGroundCooldownRef : float
@export var bunnyHopDmsIncre : float #bunny hopping desired move speed incrementer
@export var autoBunnyHop : bool = false
var lastFramePosition : Vector3
var lastFrameVelocity : Vector3
var wasOnFloor : bool
var walkOrRun : String = "WalkState" #keep in memory if play char was walking or running before being in the air
#for crouch visible changes
@export var baseHitboxHeight : float
@export var baseModelHeight : float
@export var heightChangeSpeed : float
@export_group("Crouch variables")
@export var crouchSpeed : float
@export var crouchAccel : float
@export var crouchDeccel : float
@export var continiousCrouch : bool = false #if true, doesn't need to keep crouch button on to crouch
@export var crouchHitboxHeight : float
@export var crouchModelHeight : float
@export_group("Walk variables")
@export var walkSpeed : float
@export var walkAccel : float
@export var walkDeccel : float
@export_group("Run variables")
@export var runSpeed : float
@export var runAccel : float
@export var runDeccel : float
@export var continiousRun : bool = false #if true, doesn't need to keep run button on to run
@export_group("Jump variables")
@export var jumpHeight : float
@export var jumpTimeToPeak : float
@export var jumpTimeToFall : float
@onready var jumpVelocity : float = (2.0 * jumpHeight) / jumpTimeToPeak
@export_group("Jump variables continuation") # Разделили одинаковые группы, чтобы Godot не ругался
@export var jumpCooldown : float
var jumpCooldownRef : float
@export var nbJumpsInAirAllowed : int
var nbJumpsInAirAllowedRef : int
var jumpBuffOn : bool = false
var bufferedJump : bool = false
@export var coyoteJumpCooldown : float
var coyoteJumpCooldownRef : float
var coyoteJumpOn : bool = false
@export_range(0.1, 1.0, 0.05) var inAirInputMultiplier: float = 1.0
@export_group("Gravity variables")
@onready var jumpGravity : float = (-2.0 * jumpHeight) / (jumpTimeToPeak * jumpTimeToPeak)
@onready var fallGravity : float = (-2.0 * jumpHeight) / (jumpTimeToFall * jumpTimeToFall)
@export_group("Keybind variables")
@export var moveForwardAction : String = ""
@export var moveBackwardAction : String = ""
@export var moveLeftAction : String = ""
@export var moveRightAction : String = ""
@export var runAction : String = ""
@export var crouchAction : String = ""
@export var jumpAction : String = ""
#references variables
@onready var camHolder : Node3D = $CameraHolder
@onready var model : MeshInstance3D = $Model
@onready var hitbox : CollisionShape3D = $Hitbox
@onready var stateMachine : Node = %StateMachine
@onready var hud : CanvasLayer = $HUD
@onready var ceilingCheck : RayCast3D = $Raycasts/CeilingCheck
@onready var floorCheck : RayCast3D = $Raycasts/FloorCheck
func _ready():
# Запоминаем дефолтный FOV камеры, если она подключена
if player_camera:
normal_fov = player_camera.fov
#set move variables, and value references
moveSpeed = walkSpeed
moveAccel = walkAccel
moveDeccel = walkDeccel
hitGroundCooldownRef = hitGroundCooldown
jumpCooldownRef = jumpCooldown
nbJumpsInAirAllowedRef = nbJumpsInAirAllowed
coyoteJumpCooldownRef = coyoteJumpCooldown
func _process(_delta: float):
displayProperties()
func _physics_process(_delta : float):
# 1. Отсчет кулдауна перезарядки дэша
if dash_cooldown_timer > 0:
dash_cooldown_timer -= _delta
# 2. ЕСЛИ ИДЕТ ДЭШ: двигаем игрока силой кода
if is_dashing:
dash_timer -= _delta
if dash_timer <= 0:
is_dashing = false
# Возвращаем FOV камеры обратно
if player_camera:
var tween = create_tween()
tween.tween_property(player_camera, "fov", normal_fov, 0.15)
# ВКЛЮЧАЕМ СТEЙТ-МАШИНУ ОБРАТНО
if stateMachine:
stateMachine.process_mode = Node.PROCESS_MODE_INHERIT
else:
# Принудительно толкаем персонажа со скоростью дэша
velocity.x = dash_direction.x * dash_speed
velocity.z = dash_direction.z * dash_speed
# Применяем перемещение и ВЫХОДИМ, полностью блокируя ходьбу ассета во время рывка
modifyPhysicsProperties()
move_and_slide()
return
# 3. ПРОВЕРКА НАЖАТИЯ КНОПКИ ДЭША
if Input.is_action_just_pressed("dash") and dash_cooldown_timer <= 0 and not is_dashing:
# Если игрок бежит — берем направление moveDirection, если стоит — направление камеры
var final_dir = moveDirection
if final_dir == Vector3.ZERO:
final_dir = -camHolder.global_transform.basis.z
final_dir.y = 0 # Чтобы не летать вверх/вниз
final_dir = final_dir.normalized()
# Запускаем дэш
is_dashing = true
dash_timer = dash_duration
dash_cooldown_timer = dash_cooldown
dash_direction = final_dir
# Эффект наплыва камеры (FOV)
if player_camera:
var tween = create_tween()
tween.tween_property(player_camera, "fov", normal_fov + 15.0, 0.1)
# ВЫКЛЮЧАЕМ СТEЙТ-МАШИНУ НА ВРЕМЯ РЫВКА, чтобы она не сбивала скорость!
if stateMachine:
stateMachine.process_mode = Node.PROCESS_MODE_DISABLED
# Стандартная физика ходьбы из твоего ассета (работает, когда дэша нет)
modifyPhysicsProperties()
move_and_slide()
# Проверяем нажатие на кнопку дэша (кнопка "dash" должна быть настроена в Input Map)
if Input.is_action_just_pressed("dash") and dash_cooldown_timer <= 0 and not is_dashing:
# Если игрок бежит — дэшимся в сторону бега, если стоит — строго вперед по взгляду камеры
var final_dir = moveDirection
if final_dir == Vector3.ZERO:
final_dir = -camHolder.global_transform.basis.z # Берем направление взгляда камеры
final_dir.y = 0 # Обнуляем вертикальную ось, чтобы игрок не улетал в небо или под пол
final_dir = final_dir.normalized()
# Активируем рывок
is_dashing = true
dash_timer = dash_duration
dash_cooldown_timer = dash_cooldown
dash_direction = final_dir
# Эффект скорости: плавно увеличиваем угол обзора (FOV) камеры на +15 градусов
if player_camera:
var tween = create_tween()
tween.tween_property(player_camera, "fov", normal_fov + 15.0, 0.1)
# Стандартная физика ассета (вызывается, только когда мы не в дэше)
modifyPhysicsProperties()
move_and_slide()
func displayProperties():
if hud != null:
hud.displayCurrentState(stateMachine.currStateName)
hud.displayCurrentDirection(moveDirection)
hud.displayDesiredMoveSpeed(desiredMoveSpeed)
hud.displayVelocity(velocity.length())
hud.displayNbJumpsInAirAllowed(nbJumpsInAirAllowed)
func modifyPhysicsProperties():
lastFramePosition = position
lastFrameVelocity = velocity
wasOnFloor = !is_on_floor()
func gravityApply(delta : float):
if !is_on_floor():
if velocity.y >= 0.0: velocity.y += jumpGravity * delta
elif velocity.y < 0.0: velocity.y += fallGravity * delta
@@ -0,0 +1 @@
uid://d3s8yxuvpmekm
@@ -0,0 +1,84 @@
extends State
class_name RunState
var stateName : String = "Run"
var cR : CharacterBody3D
func enter(charRef : CharacterBody3D):
cR = charRef
verifications()
func verifications():
cR.moveSpeed = cR.runSpeed
cR.moveAccel = cR.runAccel
cR.moveDeccel = cR.runDeccel
cR.floor_snap_length = 1.0
if cR.jumpCooldown > 0.0: cR.jumpCooldown = -1.0
if cR.nbJumpsInAirAllowed < cR.nbJumpsInAirAllowedRef: cR.nbJumpsInAirAllowed = cR.nbJumpsInAirAllowedRef
if cR.coyoteJumpCooldown < cR.coyoteJumpCooldownRef: cR.coyoteJumpCooldown = cR.coyoteJumpCooldownRef
func physics_update(delta : float):
checkIfFloor()
applies(delta)
cR.gravityApply(delta)
inputManagement()
move(delta)
func checkIfFloor():
if !cR.is_on_floor():
if cR.velocity.y < 0.0:
transitioned.emit(self, "InairState")
if cR.is_on_floor():
if cR.autoBunnyHop and cR.hitGroundCooldown > 0.0 and cR.inputDirection != Vector2.ZERO:
transitioned.emit(self, "JumpState")
if cR.jumpBuffOn:
cR.bufferedJump = true
cR.jumpBuffOn = false
transitioned.emit(self, "JumpState")
func applies(delta : float):
if cR.hitGroundCooldown > 0.0: cR.hitGroundCooldown -= delta
cR.hitbox.shape.height = lerp(cR.hitbox.shape.height, cR.baseHitboxHeight, cR.heightChangeSpeed * delta)
cR.model.scale.y = lerp(cR.model.scale.y, cR.baseModelHeight, cR.heightChangeSpeed * delta)
func inputManagement():
if Input.is_action_just_pressed(cR.jumpAction):
transitioned.emit(self, "JumpState")
if Input.is_action_just_pressed(cR.crouchAction):
transitioned.emit(self, "CrouchState")
if cR.continiousRun:
#has to press run button once to run
if Input.is_action_just_pressed(cR.runAction):
cR.walkOrRun = "WalkState"
transitioned.emit(self, "WalkState")
else:
#has to continuously press run button to run
if !Input.is_action_pressed(cR.runAction):
cR.walkOrRun = "WalkState"
transitioned.emit(self, "WalkState")
func move(delta : float):
cR.inputDirection = Input.get_vector(cR.moveLeftAction, cR.moveRightAction, cR.moveForwardAction, cR.moveBackwardAction)
cR.moveDirection = (cR.camHolder.global_basis * Vector3(cR.inputDirection.x, 0.0, cR.inputDirection.y)).normalized()
if cR.moveDirection and cR.is_on_floor():
cR.velocity.x = lerp(cR.velocity.x, cR.moveDirection.x * cR.moveSpeed, cR.moveAccel * delta)
cR.velocity.z = lerp(cR.velocity.z, cR.moveDirection.z * cR.moveSpeed, cR.moveAccel * delta)
if cR.hitGroundCooldown <= 0: cR.desiredMoveSpeed = cR.velocity.length()
else:
transitioned.emit(self, "IdleState")
if cR.desiredMoveSpeed >= cR.maxSpeed: cR.desiredMoveSpeed = cR.maxSpeed
@@ -0,0 +1 @@
uid://5gk47o641xgt
@@ -0,0 +1,45 @@
extends Node
@export var initialState : State
var currState : State
var currStateName : String
var states : Dictionary = {}
@onready var charRef : CharacterBody3D = $".."
func _ready():
#get all the state childrens
for child in get_children():
if child is State:
states[child.name.to_lower()] = child
child.transitioned.connect(onStateChildTransition)
#if initial state, transition to it
if initialState:
initialState.enter(charRef)
currState = initialState
currStateName = currState.stateName
func _process(delta : float):
if currState: currState.update(delta)
func _physics_process(delta: float):
if currState: currState.physics_update(delta)
func onStateChildTransition(state : State, newStateName : String):
#manage the transition from one state to another
if state != currState: return
var newState = states.get(newStateName.to_lower())
if !newState: return
#exit the current state
if currState: currState.exit()
#enter the new state
newState.enter(charRef)
currState = newState
currStateName = currState.stateName
@@ -0,0 +1 @@
uid://sca7ypsol83f
@@ -0,0 +1,21 @@
extends Node
class_name State
signal transitioned
func enter(_charReference : CharacterBody3D):
#enter state
pass
func exit():
#exit state
pass
func update(_delta : float):
#process update
pass
func physics_update(_delta : float):
#physics_process update
pass
@@ -0,0 +1 @@
uid://gjekmtw4eb1e
@@ -0,0 +1,83 @@
extends State
class_name WalkState
var stateName : String = "Walk"
var cR : CharacterBody3D
func enter(charRef : CharacterBody3D):
cR = charRef
verifications()
func verifications():
cR.moveSpeed = cR.walkSpeed
cR.moveAccel = cR.walkAccel
cR.moveDeccel = cR.walkDeccel
cR.floor_snap_length = 1.0
if cR.jumpCooldown > 0.0: cR.jumpCooldown = -1.0
if cR.nbJumpsInAirAllowed < cR.nbJumpsInAirAllowedRef: cR.nbJumpsInAirAllowed = cR.nbJumpsInAirAllowedRef
if cR.coyoteJumpCooldown < cR.coyoteJumpCooldownRef: cR.coyoteJumpCooldown = cR.coyoteJumpCooldownRef
func physics_update(delta : float):
checkIfFloor()
applies(delta)
cR.gravityApply(delta)
inputManagement()
move(delta)
func checkIfFloor():
if !cR.is_on_floor() and !cR.is_on_wall():
if cR.velocity.y < 0.0:
transitioned.emit(self, "InairState")
if cR.is_on_floor():
#check if can auto bunny hop
if cR.autoBunnyHop and cR.hitGroundCooldown > 0.0 and cR.inputDirection != Vector2.ZERO:
transitioned.emit(self, "JumpState")
if cR.jumpBuffOn:
#apply jump buffering
cR.bufferedJump = true
cR.jumpBuffOn = false
transitioned.emit(self, "JumpState")
func applies(delta : float):
if cR.hitGroundCooldown > 0.0: cR.hitGroundCooldown -= delta
cR.hitbox.shape.height = lerp(cR.hitbox.shape.height, cR.baseHitboxHeight, cR.heightChangeSpeed * delta)
cR.model.scale.y = lerp(cR.model.scale.y, cR.baseModelHeight, cR.heightChangeSpeed * delta)
func inputManagement():
if Input.is_action_just_pressed("dash") and cR.dash_cooldown_timer <= 0:
transitioned.emit(self, "DashState")
return # Выходим из функции, чтобы не вызывать другие стейты
if Input.is_action_just_pressed(cR.jumpAction):
transitioned.emit(self, "JumpState")
if Input.is_action_just_pressed(cR.crouchAction):
transitioned.emit(self, "CrouchState")
if Input.is_action_just_pressed(cR.runAction):
cR.walkOrRun = "RunState"
transitioned.emit(self, "RunState")
func move(delta : float):
cR.inputDirection = Input.get_vector(cR.moveLeftAction, cR.moveRightAction, cR.moveForwardAction, cR.moveBackwardAction)
cR.moveDirection = (cR.camHolder.global_basis * Vector3(cR.inputDirection.x, 0.0, cR.inputDirection.y)).normalized()
if cR.moveDirection and cR.is_on_floor():
#apply smooth move
cR.velocity.x = lerp(cR.velocity.x, cR.moveDirection.x * cR.moveSpeed, cR.moveAccel * delta)
cR.velocity.z = lerp(cR.velocity.z, cR.moveDirection.z * cR.moveSpeed, cR.moveAccel * delta)
if cR.hitGroundCooldown <= 0: cR.desiredMoveSpeed = cR.velocity.length()
else:
transitioned.emit(self, "IdleState")
if cR.desiredMoveSpeed >= cR.maxSpeed: cR.desiredMoveSpeed = cR.maxSpeed
@@ -0,0 +1 @@
uid://blt7dest23fk0
@@ -0,0 +1,43 @@
extends CanvasLayer
@onready var currStateLabelText = %CurrStateLabelText
@onready var currDirLabelText = %CurrDirectionLabelText
@onready var desiredMoveSpeedLabelText = %DesiredMoveSpeedLabelText
@onready var velocityLabelText = %VelocityLabelText
@onready var nbJumpsInAirAllowedLabelText = %NbJumpsInAirAllowedLabelText
@onready var weaponStackLabelText = %WeaponStackLabelText
@onready var weaponNameLabelText = %WeaponNameLabelText
@onready var totalAmmoInMagLabelText = %TotalAmmoInMagLabelText
@onready var totalAmmoLabelText = %TotalAmmoLabelText
func displayCurrentState(currState : String):
currStateLabelText.set_text(str(currState))
func displayCurrentDirection(currDir : Vector3):
currDirLabelText.set_text(str(currDir))
func displayDesiredMoveSpeed(desMoveSpeed : float):
desiredMoveSpeedLabelText.set_text(str(desMoveSpeed))
func displayVelocity(vel : float):
velocityLabelText.set_text(str(vel))
func displayNbJumpsInAirAllowed(nbJumpsInAirAllowed : int):
nbJumpsInAirAllowedLabelText.set_text(str(nbJumpsInAirAllowed))
#----------------------------------------------------------------------------
func displayWeaponStack(weaponStack : int):
weaponStackLabelText.set_text(str(weaponStack))
func displayWeaponName(weaponName : String):
weaponNameLabelText.set_text(str(weaponName))
func displayTotalAmmoInMag(totalAmmoInMag : int, nbProjShotsAtSameTime : int):
totalAmmoInMagLabelText.set_text(str(totalAmmoInMag/nbProjShotsAtSameTime))
func displayTotalAmmo(totalAmmo : int, nbProjShotsAtSameTime : int):
totalAmmoLabelText.set_text(str(totalAmmo/nbProjShotsAtSameTime))
@@ -0,0 +1 @@
uid://dx02l6p6behb8