Calendário 2026
extends CharacterBody3D
# --- Constantes de Movimento e Câmera ---
const VELOCIDADE_DE_MOVIMENTO = 5.0
const GRAVIDADE = 20.0
const FORCA_DE_PULO = 10.0
const SENSIBILIDADE_MOUSE = 0.002
const VELOCIDADE_DE_CORRIDA = 10
const MULTIPLIER_VELOCIDADE = 2.5 # O quão mais rápido o jogador ficará (2.5x a velocidade normal)
const DURACAO_POWERUP = 5.0 # Duração do power-up em segundos
# --- Variáveis de Controle ---
@onready var cam_pivot = $Pivot # Usa @onready para garantir que o nó 'Pivot' exista
var mouse_capturado = false
var powerup_ativo = false
var powerup_timer = 0.0
var direcao = Vector3()
var mouse_relative_motion = Vector2.ZERO # Novo vetor para capturar e aplicar o movimento do mouse
# Variáveis para o Mortal
var esta_dando_mortal = false
const TEMPO_MORTAL = 0.5 # Duração do giro em segundos
var mortal_timer = 0.0
const VELOCIDADE_GIRO_MORTAL = 720.0 # Rotação total em graus por segundo (360 para um giro completo)
func ativar_powerup_velocidade():
if not powerup_ativo:
powerup_ativo = true
powerup_timer = DURACAO_POWERUP # Zera o timer e define a duração
print("Power-up de Velocidade Ativo!")
func _input(event):
# 1. Capturar Mouse e Botão Direito
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT:
if event.pressed:
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
mouse_capturado = true
else:
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
mouse_capturado = false
# 2. Apenas ARMAZENAR o Movimento do Mouse
if event is InputEventMouseMotion and mouse_capturado:
# Armazena o movimento relativo (delta) para processar no _physics_process
mouse_relative_motion = event.relative
func _physics_process(delta):
# --- 0. Iniciar o Mortal ---
# Assumindo que você tem uma ação "flip" configurada, e só pode dar mortal no chão.
if Input.is_action_just_pressed("flip") and not esta_dando_mortal and is_on_floor():
esta_dando_mortal = true
mortal_timer = 0.0
# Opcional: Reduzir a velocidade horizontal durante o mortal
velocity.x *= 0.5
velocity.z *= 0.5
# --- 0. Gerenciar o Power-up ---
if powerup_ativo:
powerup_timer -= delta
if powerup_timer <= 0:
powerup_ativo = false
print("Power-up de Velocidade Expirou!")
# --- 1. Processar o Mortal ---
if esta_dando_mortal:
mortal_timer += delta
if mortal_timer < TEMPO_MORTAL:
# Rotação Local no Eixo Z (Giro Lateral/Barrel Roll)
# Gira em um ângulo por frame para completar 720 graus em 0.5s (giro de 360)
var giro_por_frame = deg_to_rad(VELOCIDADE_GIRO_MORTAL) * delta
rotate_object_local(Vector3.FORWARD, giro_por_frame)
# Opcional: Aplicar impulso para frente durante o mortal
var impulso_frontal = transform.basis.z * VELOCIDADE_DE_CORRIDA
velocity.x = move_toward(velocity.x, -impulso_frontal.x, VELOCIDADE_DE_CORRIDA * delta)
velocity.z = move_toward(velocity.z, -impulso_frontal.z, VELOCIDADE_DE_CORRIDA * delta)
move_and_slide()
return # Sai do _physics_process para ignorar movimento normal e rotação da câmera
else:
# Fim do Mortal: Resetar rotação e estado
esta_dando_mortal = false
# Tentar resetar a rotação Z (Barrel Roll) para evitar que o player fique inclinado.
# Note: Se a rotação Y também tiver sido modificada, isso pode ser complicado.
var rot = rotation
rot.z = 0.0 # Redefine o roll
rotation = rot
# --- 2. Aplicar Gravidade e Pulo (Vertical) ---
if not is_on_floor():
velocity.y -= GRAVIDADE * delta
else:
if Input.is_action_just_pressed("jump"):
velocity.y = FORCA_DE_PULO
# --- 3. Capturar Inputs (Horizontal) ---
var input_dir = Vector2(
Input.get_action_strength("move_right") - Input.get_action_strength("move_left"),
Input.get_action_strength("move_back") - Input.get_action_strength("move_forward")
).normalized()
direcao = Vector3(input_dir.x, 0, input_dir.y)
# --- 4. Aplicar Velocidade Horizontal (e Corrida) ---
# Determina a velocidade atual:
var velocidade_atual = VELOCIDADE_DE_MOVIMENTO
if Input.is_action_pressed("run"):
velocidade_atual = VELOCIDADE_DE_CORRIDA # Aumenta a velocidade se Shift estiver pressionado
if direcao:
# Usa a direção rotacionada pelo corpo do jogador para o movimento
var rotated_direction = direcao.rotated(Vector3.UP, rotation.y)
velocity.x = rotated_direction.x * velocidade_atual
velocity.z = rotated_direction.z * velocidade_atual
else:
# Desacelera suavemente (parada) - Usa a velocidade de movimento base como limite de desaceleração
velocity.x = move_toward(velocity.x, 0, VELOCIDADE_DE_MOVIMENTO)
velocity.z = move_toward(velocity.z, 0, VELOCIDADE_DE_MOVIMENTO)
# --- 5. Aplicar Rotação da Câmera (Mouse Look) ---
if mouse_capturado:
# Rotação Horizontal (Eixo Y - Gira o corpo do Player)
rotate_y(-mouse_relative_motion.x * SENSIBILIDADE_MOUSE)
# Rotação Vertical (Eixo X - Gira o Pivot da Câmera)
cam_pivot.rotate_x(-mouse_relative_motion.y * SENSIBILIDADE_MOUSE)
# Limitar a rotação vertical (evitar virar a cabeça 360°)
var rot_x = cam_pivot.rotation.x
rot_x = clamp(rot_x, deg_to_rad(-80), deg_to_rad(80))
cam_pivot.rotation.x = rot_x
# Resetar o vetor do mouse (importante para evitar movimento residual)
mouse_relative_motion = Vector2.ZERO
# --- 6. Executar o Movimento ---
move_and_slide()
func _on_node_3d_ready() -> void:
# Inicializa o modo do mouse
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
# Garante que o cam_pivot foi encontrado (necessário se não usar @onready)
# Se você usou @onready acima, esta linha é redundante, mas segura:
if cam_pivot == null:
push_error("Nó 'Pivot' não encontrado! Verifique a estrutura da cena.")
pass # Replace with function body.
enemy
extends CharacterBody3D
# ******************************************************************************
# IMPORTANTE: Se a colisão com o Player estiver falhando (Ponto 1),
# verifique se as COLISION LAYERS e MASKS estão configuradas corretamente:
# 1. O Inimigo deve ter sua CollisionShape na Layer 2 (por exemplo).
# 2. O Player deve ter sua CollisionShape na Layer 1 (por exemplo).
# 3. O Inimigo (Mask) deve verificar a Layer do Player (Layer 1).
# 4. O Player (Mask) deve verificar a Layer do Inimigo (Layer 2).
# ******************************************************************************
# --- Constantes de Movimento e Comportamento ---
const VELOCIDADE_FUGA = 6.0 # Velocidade do inimigo ao fugir
const GRAVIDADE = 20.0 # Deve ser a mesma gravidade do jogador
const ACELERACAO = 15.0 # Quão rápido o inimigo atinge a velocidade máxima
# --- Lógica de Fuga ---
const DISTANCIA_MINIMA_DE_FUGA = 8.0 # Distância a partir da qual o inimigo começa a fugir (raio de percepção)
const DISTANCIA_MAXIMA_DE_PARADA = 15.0 # Distância máxima para parar de fugir e começar a desacelerar
# --- Lógica de Desvio de Parede (Raycasting) ---
const RAY_LENGTH = 1.25 # O quão longe o inimigo "olha" para evitar paredes
const RAY_ANGLE_OFFSET = 30.0 # Ângulo em graus para os raios laterais (30 graus à esquerda e direita)
const AVOIDANCE_TURN_SPEED = 1.5 # Quão rápido o inimigo vira para desviar
# --- Variáveis de Controle ---
# O alvo que o inimigo deve evitar (o Player)
@export var caminho_para_o_alvo: NodePath
var alvo: CharacterBody3D = null
var direcao_fuga = Vector3.ZERO
func _ready():
# 1. Tenta encontrar o alvo usando o caminho fornecido
if caminho_para_o_alvo and get_node_or_null(caminho_para_o_alvo) is CharacterBody3D:
alvo = get_node(caminho_para_o_alvo)
# 2. Fallback: Tenta encontrar o primeiro nó no grupo "player"
if alvo == null:
alvo = get_tree().get_first_node_in_group("player")
# 3. Diagnóstico: Se o alvo ainda for nulo, tenta encontrar o Player assumindo que é um nó irmão (ex: Player)
if alvo == null:
# Assume que o Player pode ser um nó com o nome "Player"
var player_node = get_parent().get_node_or_null("Player")
if player_node and player_node is CharacterBody3D:
alvo = player_node
if alvo == null:
print("ERRO CRÍTICO: O nó do Jogador (Alvo) não foi encontrado.")
print("Verifique se o nó do Player existe, se está no grupo 'player', ou se o 'caminho_para_o_alvo' está preenchido no Inspetor do Inimigo.")
func _avoid_walls(direction: Vector3, delta: float) -> Vector3:
if direction == Vector3.ZERO:
return direction
# Obtém o estado do espaço físico
var space = get_world_3d().get_direct_space_state()
var ray_origin = global_position
var turn_direction = 0.0 # -1 (esquerda), 1 (direita), 0 (reto)
# 1. Raio Esquerdo (Verifica paredes na diagonal esquerda)
var ray_left_vector = direction.rotated(Vector3.UP, deg_to_rad(RAY_ANGLE_OFFSET)).normalized() * RAY_LENGTH
var query_left = PhysicsRayQueryParameters3D.create(ray_origin, ray_origin + ray_left_vector)
var result_left = space.intersect_ray(query_left)
# 2. Raio Direito (Verifica paredes na diagonal direita)
var ray_right_vector = direction.rotated(Vector3.UP, deg_to_rad(-RAY_ANGLE_OFFSET)).normalized() * RAY_LENGTH
var query_right = PhysicsRayQueryParameters3D.create(ray_origin, ray_origin + ray_right_vector)
var result_right = space.intersect_ray(query_right)
# 3. Define a direção de giro com base nos raios
if result_left and not result_right:
# Bateu na esquerda, vira para a direita (positiva)
turn_direction = 1.0
elif result_right and not result_left:
# Bateu na direita, vira para a esquerda (negativa)
turn_direction = -1.0
elif result_left and result_right:
# Encurralado, tenta virar para uma direção aleatória (pode ser ajustado)
turn_direction = randf_range(-1.0, 1.0)
if turn_direction != 0.0:
# Rotaciona a direção de fuga suavemente para desviar
var rotation_angle = deg_to_rad(turn_direction * AVOIDANCE_TURN_SPEED) * delta * 60.0 # Multiplica por 60 para maior impacto
return direction.rotated(Vector3.UP, rotation_angle).normalized()
return direction # Retorna a direção original se não houver parede
func _physics_process(delta):
if alvo == null:
# Não faz nada se não houver alvo para fugir
return
# --- 1. Aplicar Gravidade (Vertical) ---
if not is_on_floor():
velocity.y -= GRAVIDADE * delta
# --- 2. Calcular Distância e Vetor de Fuga ---
var distancia_ao_alvo = global_position.distance_to(alvo.global_position)
direcao_fuga = Vector3.ZERO
if distancia_ao_alvo < DISTANCIA_MINIMA_DE_FUGA:
# *** LÓGICA DE FUGA ***
var vetor_evasao = (global_position - alvo.global_position).normalized()
direcao_fuga = vetor_evasao
elif distancia_ao_alvo > DISTANCIA_MAXIMA_DE_PARADA:
pass
# --- 3. Aplicar Desvio de Parede (Inteligência) ---
var direcao_final = _avoid_walls(direcao_fuga, delta)
# --- 4. Aplicar Movimento Horizontal (Aceleração/Desaceleração) ---
var target_velocity = direcao_final * VELOCIDADE_FUGA
velocity.x = lerp(velocity.x, target_velocity.x, delta * ACELERACAO)
velocity.z = lerp(velocity.z, target_velocity.z, delta * ACELERACAO)
# --- 5. Aplicar Rotação ---
if direcao_final != Vector3.ZERO:
# Rotaciona para olhar para onde está correndo (direção final)
look_at(global_position + direcao_final, Vector3.UP)
rotation.x = 0
rotation.z = 0
# --- 6. Executar o Movimento ---
move_and_slide()
# --- 7. DETECÇÃO DE COLISÃO E REINÍCIO DO JOGO ---
# Verifica se move_and_slide() causou alguma colisão.
for i in range(get_slide_collision_count()):
var colisao = get_slide_collision(i)
# Verifica se o corpo colidido é o alvo (Player)
if colisao.get_collider() == alvo:
print("COLISÃO DETECTADA! O inimigo atingiu o Player. Reiniciando o jogo...")
# Reinicia a cena atual
get_tree().reload_current_scene()
break # Sai do loop para evitar múltiplas colisões na mesma frame
# DIAGNÓSTICO: Imprime a velocidade para ver o que o script está tentando fazer.
print("Velocidade do Inimigo: ", velocity)
print("Está no Chão? ", is_on_floor())
SpeedBoost
# SpeedBoost.gd
extends Area3D
func _ready():
# Conecta o sinal 'body_entered' a uma função para saber quando algo colid
body_entered.connect(_on_body_entered)
func _on_body_entered(body: Node3D):
# Verifica se o corpo que entrou é o Player.
# Assumimos que o Player está no grupo "player" (melhor prática).
if body.is_in_group("player"):
# Tenta chamar a função de ativação do power-up no script do Player
if body.has_method("ativar_powerup_velocidade"):
body.ativar_powerup_velocidade()
queue_free()