Lalala
Lalala

Reputation: 9

How to fix 'Invalid access to property or key 'is_chaser' on a base object of type 'CharacterBody2D'.'

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

Answers (0)

Related Questions