如何为我的回合制系统增加速度,以便如果怪物速度更快,它会先攻击

How do I add speed to my turn based system so if a monster has more speed it attacks first

我制作了一个功能来比较玩家和敌人两个怪物的速度,但我不知道如何让他们进行攻击然后回到玩家可以再次按下按钮的状态,在如果我将按钮更改为check_speed,那么玩家攻击然后敌人攻击而速度不是一个因素的那一刻它变成了一个无限循环

这是代码:

func check_speed(): # Gonna make it so speed becomes a factor
    if P_team.monster_invetory[0].speed > enemy_scene.speed:
        player_attack()
    else:
        enemy_attack()
        
func player_attack(): # Attacks the enemy
    print("Player attacking")
    enemy_scene.take_damage(P_team.monster_invetory[0].attack) # Calls the function to take damage
    yield(get_tree().create_timer(2), "timeout") #Wait for 2 seconds
    Enemy_battle_scene(enemy_scene) #This updates the display values
    
    var is_dead = enemy_scene.take_damage(P_team.monster_invetory[0].attack) # Checks if dead
    if is_dead:
        current_state = GameState.WON
    elif !is_dead:
        current_state = GameState.ENEMYTURN
        enemy_attack() # Here is where the enemy decides what to do, for now it only attacks
        
func enemy_attack(): # Attacks the player
    print("Enemy attacking")
    P_team.monster_invetory[0].take_damage(enemy_scene.attack) # Calls the function to take damage
    yield(get_tree().create_timer(2), "timeout") #Wait for 2 seconds
    Player_battle_scene(P_team.monster_invetory[0]) #This updates the display values
    
    var is_dead = P_team.monster_invetory[0].take_damage(enemy_scene.attack) # Checks if dead
    if is_dead:
        current_state = GameState.LOST
    elif !is_dead:
        current_state = GameState.PLAYERTURN
        #player_attack()

func _on_Attack_button_pressed():
    if current_state != GameState.PLAYERTURN:
        return
    player_attack()
    #check_speed()

新答案

任何其他名称的状态机。

你有状态:

enum GameState {START, CHOICE, PLAYERTURN, ENEMYTURN, WON, LOST, CAPTURE}
var current_state = GameState.START

我们可以通过将 current_state 变成 属性 并添加 "state_changed" 信号来使其更有用:

signal state_changed()
enum GameState {START, CHOICE, PLAYERTURN, ENEMYTURN,WON, LOST, CAPTURE}
var current_state setget set_current_state
func set_current_state(new_value):
    current_state = new_value
    emit_signal("state_changed")

现在,我们每次设置self.current_state,都会触发信号。当然我们可以连接到它。

所以我们可以这样做:

func _ready():
    self.connect("state_changed", self, "_on_state_changed")
    self.current_state = GameState.START

func _on_state_changed():
    match current_state:
        GameState.START:
            _start()
        GameState.CHOICE:
            _choice()
        GameState.PLAYERTURN:
            _player_turn()
        GameState.ENEMYTURN:
            _enemy_turn()
        GameState.LOST:
            _lost()
        GameState.CAPTURE:
            _capture()

为什么不把_on_state_changed直接放在set_current_state里呢?因为信号是异步的。 此外,您不必在此脚本中处理所有这些问题。您甚至可以为每个州准备一个脚本。

好的,让我们开始制作这些函数:

开始:

func _start():
    # initalization stuff
    self.current_state = GameState.CHOICE

选择:

func _choice():
    # We have to wait for the button press, we do nothing here
    pass
    
func _on_Attack_button_pressed():
    if current_state != GameState.CHOICE:
        return

    if _player_speed() > _enemy_speed(): # Checks which monster is faster
        self.current_state = GameState.PLAYERTURN
    else:
        self.current_state = GameState.ENEMYTURN

具有 _player_speed_enemy_speed 的一些定义。

PLAYER_TURN 和 ENEMY_TURN:

var enemy_attk = false
var player_attk = false

func _player_turn():
    yield(_player_attack(), "completed")
    if _enemy_is_dead():
        self.current_state = GameState.WON
    else:
        if enemy_attk:
            player_attk = false
            enemy_attk = false
            self.current_state = GameState.CHOICE
        else:
            player_attk = true
            self.current_state = GameState.ENEMYTURN

func _enemy_turn():
    yield(_enemy_attack(), "completed")
    if _player_is_dead():
        self.current_state = GameState.LOST
    else:
        if player_attk:
            player_attk = false
            enemy_attk = false
            self.current_state = GameState.CHOICE
        else:
            enemy_attk = true
            self.current_state = GameState.PLAYERTURN

具有 _player_is_dead_enemy_is_dead_player_attack_enemy_attack 的一些定义。

获胜、失败和捕获:

func _lost():
    # whatever happens
    pass
    
func _won():
    # whatever happens
    pass
    
func _capture():
    # whatever happens
    pass

我不知道。


现在,让我们将 PLAYERTURN、ENEMYTURN 合并到一个新的 BATTLE 状态。更新枚举:

enum GameState {START, CHOICE, BATTLE, WON, LOST, CAPTURE}

更新匹配语句:

func _on_state_changed():
    match current_state:
        GameState.START:
            _start()
        GameState.CHOICE:
            _choice()
        GameState.BATTLE:
            _battle()
        GameState.LOST:
            _lost()
        GameState.CAPTURE:
            _capture()

当按下按钮时,我们只需更改为 BATTLE:

func _on_Attack_button_pressed():
    if current_state != GameState.CHOICE:
        return

    self.current_state = GameState.BATTLE

现在是重头戏:

func _battle():
    if _player_speed() > _enemy_speed(): # Checks which monster is faster
        yield(_player_attack(), "completed")
        if _enemy_is_dead():
            self.current_state = GameState.WON
        else:
            yield(_enemy_attack(), "completed")
            if _player_is_dead():
                self.current_state = GameState.LOST
            else:
                self.current_state = GameState.CHOICE
    else:
        yield(_enemy_attack(), "completed")
        if _player_is_dead():
            self.current_state = GameState.LOST
        else:
            yield(_player_attack(), "completed")
            if _enemy_is_dead():
                self.current_state = GameState.WON
            else:
                self.current_state = GameState.CHOICE

让我们谈谈使用对象。首先,参考它们。您已经拥有 enemy_scene,让我们使用它。我们需要添加一个计数器部分 player_scene.

onready var player_scene = P_team.monster_invetory[0]

顺便说一下,如果我是你,我会在 EnemyPosPlayerPos 中添加一个脚本,这样我就可以这样做:

$EnemyPos.update_view(enemy_scene)
$PlayerPos.update_view(player_scene)

现在,START 可以如下所示:

func _start():
    _update_view()
    print("Battle started")
    yield(get_tree().create_timer(0.2), "timeout")
    self.current_state = GameState.CHOICE

func _update_view():
    $PlayerPos.update(player_scene)
    $EnemyPos.update(enemy_scene)

建议:给那些场景加一个“health_changed”信号,分别接在$PlayerPos$EnemyPos上,这样就可以自动更新了。 事实上,当怪物死亡时,我们可以使用该信号来处理。

攻击是个问题,因为这些对象不知道它们的目标。以后会担心的。

现在更新 BATTLE 以使用 player_sceneenemy_scene:

func _battle():
    if player_scene.speed > enemy_scene.speed:
        yield(attack(player_scene, enemy_scene), "completed")
        if enemy_scene.current_health == 0:
            self.current_state = GameState.WON
        else:
            yield(attack(enemy_scene, player_scene), "completed")
            if player_scene.current_health == 0:
                self.current_state = GameState.LOST
            else:
                self.current_state = GameState.CHOICE
    else:
        yield(attack(enemy_scene, player_scene), "completed")
        if player_scene.current_health == 0:
            self.current_state = GameState.LOST
        else:
            yield(attack(player_scene, enemy_scene), "completed")
            if enemy_scene.current_health == 0:
                self.current_state = GameState.WON
            else:
                self.current_state = GameState.CHOICE

func attack(attacker, target):
    print(attacker.name, " attacking")
    target.take_damage(attacker.attack) # Calls the function to take damage
    yield(get_tree().create_timer(2), "timeout") #Wait for 2 seconds
    _update_view()

请注意,使用“health_changed”信号,这段代码要简单得多。因为您不必在 _battle 上处理输赢条件,也不必从 attack 调用 _update_view

最后,我们可以编写一个将它们放在数组中的版本:

func _battle():
    var participants = [player_scene, enemy_scene]
    participants.sort_custom(self, "check_speed")
    for attacker in participants:
        var target = _choose_target(attacker, participants)
        yield(attack(attacker, target), "completed")

        if player_scene.current_health == 0:
            self.current_state = GameState.LOST
            return
        if enemy_scene.current_health == 0:
            self.current_state = GameState.WON
            return

    self.current_state = GameState.CHOICE

func check_speed(a, b):
    return a.speed > b.speed:

func _choose_target(attacker, participants):
    for participant in participants:
        if participant == attacker:
            continue

        return participant

同样,使用“health_changed”信号会更简单。

这仍然需要一些修改才能完全支持两个以上的怪物:

  • 要赢得它会检查是否所有敌方怪物都死了,而不仅仅是一个。
  • 失去它会检查所有盟友怪物是否都死了,而不仅仅是一个。
  • 选择目标时,请确保它选择了考虑到攻击者的合适团队。 您也可以考虑让玩家控制选择目标。

当然,您可以将数组保留在 _battle 之外,并在参与者死亡时将其移除。然后检查是否所有敌方怪物都死了,实际上是检查所有剩余的怪物是否都是盟友(反之亦然)。你可以通过计算那里有多少盟友或敌方怪物,并在它们死亡时减少它来做到这一点。 你可以连接到“health_changed”信号。


啊,对了,一个可以让怪物根据速度进行多次攻击的版本。您将需要这些场景的新属性。我叫它exhaustion,战斗开始时应该是0

func _battle():
    var participants = [player_scene, enemy_scene]
    var player_attacked = false
    while (true):
        var attacker = get_next(participants)

        if player_scene == attacker:
            if player_attacked:
                # It is time for the player to attack again.
                # Return control to the player.
                self.current_state = GameState.CHOICE
                return
            else:
                player_attacked = true

        var target = _choose_target(attacker, participants)

        yield(attack(attacker, target), "completed")
        attacker.exhaustion += 1.0 / attacker.speed # <--

        if player_scene.current_health == 0:
            self.current_state = GameState.LOST
            return
        if enemy_scene.current_health == 0:
            self.current_state = GameState.WON
            return

func get_next(participants):
    participants.sort_custom(self, "check_order")
    return participants[0]

func check_order(a, b):
    if a.exhaustion < b.exhaustion:
        return true

    if a.exhaustion == b.exhaustion:
        return a.speed > b.speed:

    return false

如你所见,规则是exhaustion少的怪物先出。如果两只怪物的 exhaustion 相同,则 speed 多的先出。每次怪物攻击时,它的 exhaustion 都会增加。多少? speed 的倒数。因此 speed 少的怪物耗尽得更快,而 speed 多的怪物耗尽得慢。因此,如果一个怪物的 speed 多于另一个,它最终会像第一个怪物一样对每个怪物进行多次攻击。

注意:我称其为“耗尽”只是为了让您对值如何变化有一些直觉。但是,重要的是这不是一场又一场的战斗,它必须重置为 0。如果你想象一个筋疲力尽的怪物与一个没有筋疲力尽的怪物,没有筋疲力尽的人会在另一个人做任何事情之前得到很多攻击。

我们需要跟踪玩家是否进行了攻击,正如我在原始答案中提到的那样,以打破循环。


原回答

这取决于您希望 Speed 的具体工作方式,我只会展示最简单的方法。但首先让我们从基线开始。

这么说吧,不管速度,玩家攻击,然后怪物攻击,然后等待下一次输入。

这意味着像这样做:

func _on_Attack_button_pressed():
    # ...
    player_attack()
    enemy_attack()

除了那些函数yield。所以我们需要这个:

func _on_Attack_button_pressed():
    # ...
    yield(player_attack(), "completed")
    yield(enemy_attack(), "completed")

原因是产生 returns 的函数是 GDScriptFunctionState

文档说 GDScriptFunctionState:

Calling @GDScript.yield within a function will cause that function to yield and return its current state as an object of this type

这里我使用 GDScriptFunctionState"completed" 信号,正如 Coroutines & signals:

文档中所建议的

If you're unsure whether a function may yield or not, or whether it may yield multiple times, you can yield to the completed signal conditionally


现在,让“速度”发挥作用的最简单方法是将速度视为回合顺序。类似于 D&D 计划。这意味着我们所做的就是根据速度对这些操作进行排序。所以你做这样的事情:

func _on_Attack_button_pressed():
    # ...
    if player_goes_first():
        yield(player_attack(), "completed")
        yield(enemy_attack(), "completed")
    else:
        yield(enemy_attack(), "completed")
        yield(player_attack(), "completed")

根据您的代码,我猜 player_goes_first 只会 return P_team.monster_invetory[0].speed > enemy_scene.speed。参见 Functions

我们也可以这样写:

func _on_Attack_button_pressed():
    # ...
    if player_speed() > enemy_speed():
        yield(player_attack(), "completed")
        yield(enemy_attack(), "completed")
    else:
        yield(enemy_attack(), "completed")
        yield(player_attack(), "completed")

其中 player_speedenemy_speed 是 return 适当值的函数。

如果你需要三个参与者,你可以想象代码(something like this)。


但是,您的代码将受益于使用具有 speed 属性 和 attack 方法的对象(这些对象可能是也可能不是节点,请参阅 Classes ).

如果你有对象,支持更多的参与者会更容易:

  • 将参与者放在一个数组中。
  • speed 排序,sort_custom
  • 遍历数组,并对每个数组调用 attack

另一种方法是使用 get_next 函数,return 调用 attack 的下一个对象。然后循环调用 get_next 。您将有一个变量来跟踪玩家是否进行了攻击(通过检查您从 get_next 获得的内容是否是玩家对象),并在循环播放器攻击两次之前停止(因为用户应该按下按钮再次攻击)。

使用 get_next 函数可以实现更复杂的逻辑。例如,让参与者根据其 speed.

进行多次攻击