Reputation: 9
I am currently making a game of tag in Godot 4.3. I have a player that can be randomly assigned as a chaser/runner and a group of NPCs that can also be similarly assigned.
main.gd:
extends Node2D
@onready var start_timer = $StartTimer
@onready var game_timer = $GameTimer
@onready var countdown_label = $UI/CountdownLabel
@onready var game_over_label = $UI/GameOverLabel # Label for game over message
@onready var game_timer_label = $UI/GameTimerLabel # Label for game countdown
@onready var player = $Player
@onready var npcs = $NPCs.get_children()
var first_chaser_assigned = false # Prevents multiple assignments
var game_has_ended = false # Prevents duplicate game-over calls
func _ready():
# Ensure countdown label is centered
# Center the label using viewport size
var screen_size = get_viewport_rect().size
countdown_label.position = screen_size / 2 - countdown_label.size / 2
countdown_label.visible = true
game_over_label.visible = false
game_timer_label.visible = false # Hide timer at first
await countdown_sequence()
start_timer.start()
game_timer.wait_time = 90 # 1 minute 30 seconds
# Connect timers
start_timer.timeout.connect(_on_start_timer_timeout)
game_timer.timeout.connect(_on_game_timer_timeout)
func _process(delta):
# Update game timer label
if game_timer.time_left > 0 and game_timer_label.visible:
update_game_timer_label()
# End game if all players become chasers before time runs out
if not game_has_ended and all_are_chasers():
game_over("Game Over. Chasers Won!")
func countdown_sequence():
countdown_label.text = "3"
await get_tree().create_timer(1).timeout
countdown_label.text = "2"
await get_tree().create_timer(1).timeout
countdown_label.text = "1"
await get_tree().create_timer(1).timeout
countdown_label.text = "GO!"
await get_tree().create_timer(1).timeout
countdown_label.visible = false # Hide countdown label after "GO!"
func _on_start_timer_timeout():
if not first_chaser_assigned:
assign_chaser()
game_timer.start()
game_timer_label.visible = true # Show game timer when game starts
first_chaser_assigned = true
func assign_chaser():
var all_entities = [player] + npcs
if all_entities.is_empty():
print("No entities found to assign as chaser!")
return
# Select a random entity to become the first chaser
var chaser = all_entities[randi() % all_entities.size()]
# Ensure we're not selecting an already-chosen chaser
for i in range(10):
if not chaser.is_chaser:
break
chaser = all_entities[randi() % all_entities.size()]
if chaser.is_chaser:
print("Failed to assign a new chaser after multiple attempts!")
return
chaser.become_chaser()
print("First chaser assigned:", chaser.name)
func update_game_timer_label():
var time_left = int(game_timer.time_left)
var minutes = time_left / 60
var seconds = time_left % 60
game_timer_label.text = "Time: %02d:%02d" % [minutes, seconds] # Format MM:SS
func _on_game_timer_timeout():
if count_runners() > 0:
game_over("Game Over. Runners Won!") # Runners survived
else:
game_over("Game Over. Chasers Won!") # All got tagged last second
func all_are_chasers() -> bool:
# Check if player and all NPCs are chasers
if not player.is_chaser:
return false
for npc in npcs:
if not npc.is_chaser:
return false
return true # All entities are chasers
func count_runners() -> int:
var runner_count = 0
if not player.is_chaser:
runner_count += 1
for npc in npcs:
if not npc.is_chaser:
runner_count += 1
return runner_count
func game_over(message):
if game_has_ended:
return # Prevent multiple game-over calls
game_has_ended = true
game_over_label.visible = true # Show game over label
game_over_label.text = message # Set message
game_timer_label.visible = false # Hide timer when game ends
print(message) # Debug print
# Wait a few seconds before quitting
await get_tree().create_timer(3).timeout
get_tree().quit()
npc.gd:
extends CharacterBody2D
@export var speed: float = 100
var is_chaser: bool = false
var target_direction: Vector2
var target_runner: CharacterBody2D = null # Chasers will chase this
var nearby_chaser: CharacterBody2D = null # Runners will flee from this
var chase_speed: float = 20 # Speed boost when chasing
var flee_speed: float = 160 # Speed boost when fleeing
@onready var timer = $Timer
@onready var status_label = $StatusLabel
@onready var detection_area = $DetectionArea # Detect runners if chaser
@onready var flee_area = $FleeArea # Detect chasers if runner
@onready var animated_sprite = $AnimatedSprite2D # Reference to AnimatedSprite2D
func _ready():
add_to_group("npc")
randomize()
pick_new_direction()
timer.wait_time = randf_range(1.5, 3)
timer.start()
update_status()
# Connect detection signals
detection_area.body_entered.connect(_on_detection_area_body_entered)
detection_area.body_exited.connect(_on_detection_area_body_exited)
flee_area.body_entered.connect(_on_flee_area_body_entered)
flee_area.body_exited.connect(_on_flee_area_body_exited)
func _physics_process(delta):
if is_chaser and target_runner and not target_runner.is_chaser:
# Chase the runner
target_direction = (target_runner.global_position - global_position).normalized()
velocity = target_direction * chase_speed
elif not is_chaser and nearby_chaser:
# Flee from chaser
target_direction = (global_position - nearby_chaser.global_position).normalized()
velocity = target_direction * flee_speed
else:
# Default wandering movement
velocity = target_direction * speed
update_animation(target_direction)
# Check for collisions before moving
var collision = move_and_collide(velocity * delta)
if collision:
var collider = collision.get_collider()
if collider:
_on_body_entered(collider) # Handle collision with another entity
handle_collision() # Change direction on collision
move_and_slide()
func update_animation(direction):
if abs(direction.x) > abs(direction.y):
animated_sprite.play("IdleSide")
animated_sprite.flip_h = direction.x < 0 # Flip sprite if moving left
elif direction.y > 0:
animated_sprite.play("WalkFront")
else:
animated_sprite.play("IdleBack")
func pick_new_direction():
target_direction = Vector2(randf_range(-1, 1), randf_range(-1, 1)).normalized()
func handle_collision():
# Change direction slightly instead of stopping
target_direction = target_direction.rotated(randf_range(PI / 4, PI / 2)).normalized()
pick_new_direction()
func _on_Timer_timeout():
if not is_chaser or target_runner == null:
pick_new_direction() # Keep moving randomly only if not chasing
timer.wait_time = randf_range(1.5, 3)
timer.start()
func _on_body_entered(body):
if body is CharacterBody2D and body.is_chaser and not is_chaser:
print(body.name, " tagged ", name, " - Now a chaser!")
become_chaser()
elif body is CharacterBody2D and is_chaser and not body.is_chaser:
print(name, " tagged ", body.name, " - They are now a chaser!")
body.become_chaser()
target_runner = null # Stop chasing after tagging
func _on_detection_area_body_entered(body):
if body is CharacterBody2D and not body.is_chaser and is_chaser:
target_runner = body # Start chasing the runner
print(name, " detected ", body.name, " and is now chasing!")
func _on_detection_area_body_exited(body):
if body == target_runner:
target_runner = null # Stop chasing if runner leaves radius
print(name, " lost sight of ", body.name)
func _on_flee_area_body_entered(body):
if body is CharacterBody2D and body.is_chaser and not is_chaser:
nearby_chaser = body # Start fleeing
print(name, " noticed ", body.name, " and is fleeing!")
func _on_flee_area_body_exited(body):
if body == nearby_chaser:
nearby_chaser = null # Stop fleeing if chaser leaves radius
print(name, " is safe now.")
func become_chaser():
if not is_chaser:
is_chaser = true
speed = 130 # Slight speed boost
target_runner = null # Stop chasing when turned into a chaser
nearby_chaser = null # Stop fleeing when becoming a chaser
pick_new_direction() # Resume random movement
update_status()
print(name, " has become a CHASER!")
func update_status():
if is_chaser:
modulate = Color.RED
status_label.text = "Chaser"
status_label.add_theme_color_override("font_color", Color.RED)
else:
status_label.text = "Runner"
status_label.add_theme_color_override("font_color", Color.BLUE)
player.gd:
extends CharacterBody2D
@export var speed: float = 200
var is_chaser: bool = false
@onready var status_label = $StatusLabel
@onready var tag_area = $TagArea # Reference to Area2D
@onready var animated_sprite = $AnimatedSprite2D # Reference to AnimatedSprite2D
func _ready():
status_label.show()
status_label.position.y = -20
update_status()
# Ensure the tag_area exists and properly connects the signal
if tag_area:
tag_area.body_entered.connect(_on_tag_area_body_entered)
else:
print("Error: TagArea not found in Player!")
func _physics_process(delta):
var direction = Vector2.ZERO
if Input.is_action_pressed("ui_up"):
direction.y -= 1
if Input.is_action_pressed("ui_down"):
direction.y += 1
if Input.is_action_pressed("ui_left"):
direction.x -= 1
if Input.is_action_pressed("ui_right"):
direction.x += 1
if direction.length() > 0:
direction = direction.normalized()
update_animation(direction)
velocity = direction * speed
move_and_slide()
func update_animation(direction):
if abs(direction.x) > abs(direction.y):
animated_sprite.play("IdleSide")
animated_sprite.flip_h = direction.x < 0 # Flip if moving left
elif direction.y > 0:
animated_sprite.play("WalkFront")
else:
animated_sprite.play("IdleBack")
func _on_tag_area_body_entered(body):
# Ensure we're tagging an NPC that is a runner
if body is CharacterBody2D and not body.is_chaser and is_chaser:
print(name, " tagged ", body.name, " - They are now a chaser!")
body.become_chaser()
func become_chaser():
if not is_chaser:
is_chaser = true
speed = 220 # Slight boost for chasers
update_status()
print(name, " has become a CHASER!")
func update_status():
status_label.show()
if is_chaser:
modulate = Color.RED
status_label.text = "Chaser"
status_label.add_theme_color_override("font_color", Color.RED)
else:
modulate = Color.WHITE
status_label.text = "Runner"
status_label.add_theme_color_override("font_color", Color.BLUE)
error image:
I have already made this prototype work, but when I transferred the file to my GitHub repository(I tweaked the file locations a little because I want to integrate this as a minigame), it persistently showed the error 'Invalid access to property or key 'is_chaser' on a base object of type 'CharacterBody2D'.' whenever I try to run it. I checked each node if it has the proper script attached to it and still, this error occurs. I'm really struggling on finding the issue since this code has already worked before.
Upvotes: 0
Views: 36