0 votes

I am very new to godot and making 2d platformer zombie game.
I have made an enemy that can do following things when collide with player.
(1) Disable player movement, disable player visibility, lock player's position
(2) Play enemy grab attack animation, show button mashing progress bar
(3)-(1) If cleared, enable player visibility, unlock player's position, enemy return to chase state
(3)-(2) After few sec, enable player movement
(4)-(1) If failed, play enemy execution animation
(4)-(2) when animation finished, enable player visibility, unlock player's position
(4)-(3) After few sec, enable player movement

Everything works perfectly fine except, when I duplicated enemy, duplicated one is synchronized with original one. So when I collide with duplicated enemy, the button mashing progress bar will also be shown upon original one.
I would appreciate if you could kindly teach me how to fix this problem.

When it is not duplicated:
enter image description here

When it is duplicated (Left is duplicated one, and Right is original one):
enter image description here

Player Script: Sorry if my code is messy and annoyingly hard to see

class_name Player

extends KinematicBody2D

var speed = 200
var acceleration = 30
var friction = 15
var gravity = 1500

var air_jump = true
var jump_strength = 570
var air_jump_strength := 550
var air_friction = 1

var can_move = true
var player_in_hitbox: bool = false

var max_hp = 100
var current_hp

var _velocity := Vector2.ZERO

onready var position2D = $Position2D
onready var _animation_player: AnimationPlayer = $Position2D/PlayerSkinIK/AnimationPlayer
onready var hurtbox_collision = $HurtboxPlayer/CollisionShape2D and $HurtboxPlayer/CollisionShape2D2
onready var player_grab_finished = get_node("../Mushroom")
onready var label = get_node("Label")

signal player_in_hitbox_false() #signal for player movement disable
signal player_in_hitbox_true() #signal for player movement disable


func _ready():
    player_grab_finished.connect("grab_finished", self, "grab_finished_signal_received") #connect mushroom
    current_hp = max_hp


func get_input_direction() -> float:
    if player_in_hitbox: #disable player movement if true
        return 0.0
    var _horizontal_direction = ( #player movement direction
        Input.get_action_strength("move_right")
        - Input.get_action_strength("move_left")
    )
    return _horizontal_direction


func _process(delta: float) -> void:
    if get_global_mouse_position().x > $Position2D/PlayerSkinIK.global_position.x: #flip player sprite
        position2D.scale.x=1
    else:
        position2D.scale.x=-1

    if can_move == false: #lock player position when in Grab Attack
        self.position = get_node("../Mushroom/HitPosition").global_position


func grab_finished_signal_received(): #Grab Finished
    print("Grab Finished!")
    label.text="CAN'T MOVE"
    _animation_player.play("Player-Damaged IK")
    can_move = true
    _velocity.x = 1 * -250
    self.visible = true
    $player_in_hitboxTimer.start()

func _on_player_in_hitboxTimer_timeout(): #After Grab Finished
    print("FREE!")
    label.text="CAN MOVE"
    player_in_hitbox = false
    $player_in_hitboxTimer.is_stopped()
    emit_signal("player_in_hitbox_true")

func _on_HurtboxPlayer_area_entered(area: Area2D): #When Player Enter Enemy Hitbox
    if area.name == "HitboxMushroom":
        label.text="CAN'T MOVE"
        player_in_hitbox = true #Disable Movement
        can_move = false #Lock Position
        self.visible = true #Disable Visibility
        emit_signal("player_in_hitbox_false") #Emit Signal to Disable Shooting
        pass

Enemy Script:

extends KinematicBody2D

var max_hp = 100
var current_hp= max_hp
var defense = 0

var speed = 100
var chase_speed = 100
var gravity = 10
var direction = -1
var velocity = Vector2.ZERO

onready var _animation_player: AnimationPlayer = $Position2D/Node2D/AnimationPlayer
onready var sprite = get_node("Position2D/Node2D")
onready var flashTimer = $flashTimer
onready var player = get_node("../Player")
onready var enemy_collision = get_node("EnemyCollision")
onready var label = get_node("Label")

signal button_mashing_start()
signal grab_finished()


enum state {IDLE, CHASE, GRAB, FINISH, DEATH}
var enemy_state = state.IDLE


func _ready():
    enter_state(state.IDLE)
    $Position2D/Node2D/AnimationPlayer.connect("animation_finisehd", self, "on_animation_finished")
    current_hp = max_hp
    pass

func _physics_process(delta):
    process_state()
    pass

func enter_state(pass_state):
    if(enemy_state != pass_state):
        leave_state(enemy_state)
        enemy_state = pass_state

    if(pass_state == state.IDLE):
        _animation_player.play("Mushroom-Idle")
        label.text="IDLE"

    if(pass_state == state.CHASE):
        _animation_player.play("Mushroom-Idle")
        label.text="CHASE"

    if(pass_state == state.GRAB):
        _animation_player.play("Mushroom-Grab")
        label.text="GRAB01"

    if(pass_state == state.FINISH):
        _animation_player.play("Mushroom-Execution")
        label.text="GRAB02"

    if(pass_state == state.DEATH):
        _animation_player.play("Mushroom-Idle")
        label.text="DEATH"

func process_state():
    if(enemy_state == state.IDLE):
        process_idle()

    if(enemy_state == state.CHASE):
        process_chase()

    if(enemy_state == state.GRAB):
        process_grab()

    if(enemy_state == state.FINISH):
        process_finish()

    if(enemy_state == state.DEATH):
        process_death()

func leave_state(pass_state):
    pass

func process_idle():
    if is_on_wall() or not $Position2D/FloorRay.is_colliding() and is_on_floor():
        print("turn around")
        direction = direction * -1
        flip()
    velocity.y += gravity
    velocity.x = speed * direction 
    velocity = move_and_slide(velocity, Vector2.UP)
    if enemy_collision.is_colliding():
        velocity += enemy_collision.get_push_vector() * 2.5


func process_chase():
    if velocity.x>0:
        $Position2D.scale.x=-1
    elif velocity.x<0:
        $Position2D.scale.x=1

    velocity = (player.position - position).normalized()
    if not is_on_floor():
        velocity.y += gravity 

    if enemy_collision.is_colliding():
        velocity += enemy_collision.get_push_vector() * 2.5

    velocity = move_and_slide(velocity * chase_speed)


func process_grab():
    velocity.y += 0
    velocity.x = 0 * direction 
    velocity = move_and_slide(velocity, Vector2.UP)
    emit_signal("button_mashing_start")


func process_finish():
    velocity.y += 0
    velocity.x = 0 * direction 
    velocity = move_and_slide(velocity, Vector2.UP)


func process_death():
    velocity.x = 0 * direction 
    velocity.y += gravity
    velocity = move_and_slide(velocity, Vector2.UP)
    set_collision_mask_bit(0, false)



func _on_AnimationPlayer_animation_finished(anim_name):
    print("Grab02 End!")
    emit_signal("grab_finished")
    enter_state(state.CHASE)


func flip(): #Flip Sprite
    if direction>0:
        $Position2D.scale.x=-1
    elif direction<0:
        $Position2D.scale.x=1

func _on_HitboxMushroom_area_entered(area):
    if !area.name == "HurtboxPlayer":
        enter_state(state.CHASE)
    if area.name == "HurtboxPlayer":
        print("Grab01!")
        enter_state(state.GRAB)


func _on_PlayerDetector_area_entered(area: Area2D): #Player Search Area. Chase when Player entered
    if area.name == "HurtboxPlayer":
        print("found you !")
        enter_state(state.CHASE)
        pass

func _on_PlayerDetector_area_exited(area: Area2D): #Player Search Area. Stop Chase when Player exited
    if area.name == "HurtboxPlayer":
        print("lost you !")
        enter_state(state.IDLE)
        pass


func _on_ProgressBar_button_mashing_cleared():
    enter_state(state.CHASE)
    emit_signal("grab_finished")
    print("Return Chase!")
    pass 

func _on_ProgressBar_button_mashing_failed():
    enter_state(state.FINISH)
    print("Grab02!")
    pass 

Progress Bar Script:

extends ProgressBar

var start = false

signal button_mashing_cleared()
signal button_mashing_failed()

func _ready():
    set_visible(false)


func set_bar_value(value_to_set):
    value = value_to_set


func _physics_process(delta):
    if not start == false:
        set_visible(true)
        value -= 15 * delta 

        if Input.is_action_just_pressed("jump"):
            set_bar_value(value + 5)

        if(value <= 0):
            print("Bar Failed!")
            start = false
            set_visible(false)
            set_bar_value(50)
            emit_signal("button_mashing_failed")
        elif(value >= 100):
            print("Bar Cleared!")
            start = false
            set_visible(false)
            set_bar_value(50)
            emit_signal("button_mashing_cleared")


func _on_Mushroom_button_mashing_start():
    start = true
Godot version ver 3.3.3
in Engine by (80 points)

Try to expaind the method you use to duplicate the enemy.
I think this will solve the problem.

I simply right clicked enemy node, and duplicated.

1 Answer

+1 vote

It is hard to dive in this wall of code. Can You shortly explain signals progression ? I mean, what signals trigger what in order from start to end of a whole action ?

If origin of the action is triggered by Zombies upon player entering their private Hitbox area, this is most likely the problem of duplicate itself. It must have connected second zombie to the area of first zombie. Don't use duplicates. Save zombie as a scene and add another instance of it in your main scene. If You duplicate by code, You can choose to ignore duplicating signals, by inserting additional tag arguments.

by (7,925 points)

Sure :)
If You used my signalling example, it would also fix the teleport to 1st zombie problem.
Because signal is now sent with information referencing the actual grabbing zombie , You could connect to this signal and teleport player to position of the zombie, that triggered grab.

Thank you Inces.

Wow. I could solve the teleport issue with signal as well !?
I could not understand your example of Autoload for grab state machine.
I did something like var HitPosition = get_node("../Zombie/HitPosition) for autoload, but didnt work.

You don't know what autoload is ?
It is a script that is independent of SceneTree hierarchy, and can be referenced from anywhere in code. Read about it in documentation too.
You just need to create new script ( not a node, just a script, but extends from Node ), and go to Project/Project Settings/Autoload tab - add new script. You choose name for it ( in my example it was "Autoload" ) and now You can refer to it from anywhere in your code and compiler will autocomplete its name.

Why is it so good ? Because if You introduce every signal of your game inside it, You will never have to know scene tree hierarchy in order to reference a node for connecting. So no longer stuff like get_node/enemies/redenemies/zombie52 , but just Autoload. instead
You can think of it as radio transmitter, to communicate between far away lands. No need to know location to send a letter, just emit information into the ether, and a land who is interested will listen to it.

Oh my apologies. Did not meant to say I do not know what autoload is. I indeed do understand what autoload is however, I was not sure how to rearrange the code inside autoload in order to change enemy state. Simple autoload such as signal grab_finished was very much simple and easy to implement, as what I needed to do is simply write "signal grab_finished" inside autoload and connect it. Changing enemy's state and solving position teleporting issue using autoload is tricky, since I am really not sure what to write inside autoload. For now, I set velocity.x, y and gravity to zero instead of locking the player's position. Thank you very much for thorough explanation about autoload. It is indeed very useful since I would never need to write lines of code to reference a node.

Inside autoload introduce all the signals You have and that is it :)
Right now your player keeps reference to zombie with hardcode :

onready var player_grab_finished = get_node("../Mushroom")

which only leads to first zombie.
Instead of using this hardcoded variable for setting teleport position, You should use reference sent by signal.
As I stated before, if You emit signal with another argument, it is sent along with signal :

Autoload.emit_signal("grab_finished", self)

so now zombie emits a signal with SELF
Thanks to this,Player, which is connected to Autoload in general, can now recognize which zombie sent a signal. And this is enough to teleport to it :

func grab_finished_signal_received( who ):
        global_position = who.get_node("Position2D").global_position

another example with gamestate machine is just a gimmick, it is optional, let's leave that for now ;)

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read Frequently asked questions and How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to [email protected] with your username.