Godot的全球statemachine处理程序

发布于 2025-02-08 15:57:00 字数 3720 浏览 2 评论 0 原文

我正在尝试使这台有限的状态机起作用,但是它与播放器的运行状态无关,这是代码:

state.gd,


    extends KinematicBody2D
    class_name State
    
    var state_machine = null
    
    var player_velocity = Vector2()
    var player_speed = 200
    var player_energy = 50
    
    enum playerStates{IDLE, WALK, RUN, USE, SHOOT}
    enum ItemStates{ITEM_ON_GROUND, IS_EQUIPED}
    enum Inventory{SELECTED, NOTSELECTED}
    
    var parent = get_parent()
    
    func _ready():
        if Global.player_isIdle == State.state_machine == playerStates.IDLE:
            pass
        if Global.player_isWalking == State.state_machine == playerStates.WALK:
            State.player_speed = Global.player_speed
            State.player_speed = 200
        if Global.player_isRunning == State.state_machine == playerStates.RUN:
            State.player_speed = Global.player_speed
            State.player_speed = 300
        else:
            Global.player_isWalking == State.state_machine == playerStates.WALK
                
    func handle_input(_event: InputEvent):
        if Global.player_velocity != Global.player_velocity.ZERO:
            if Global.player_isWalking == State.state_machine == playerStates.WALK:
                pass
            if Global.player_isRunning == State.state_machine == playerStates.RUN:
                if Input.is_action_pressed("sprint"):
                    print("RUN")
            Global.player_velocity = Global.player_velocity.normalized() * player_speed
            Global.player_velocity = move_and_slide(Global.player_velocity)
            
    func _physics_process(_delta):
        Global.player_velocity = parent.move_and_slide(Global.player_velocity)
    ```

Player.gd
```rust

    onready var velocity = Global.player_velocity
    ...
    func get_input():
        velocity = Vector2()
        #speed = Global.player_speed
        
        if Input.is_action_pressed("sprint"):
            Global.player_isRunning = true 
            #speed = 300
        else:
            #speed = 200
            pass
        if Input.is_action_pressed("move-right"):
            Global.player_direction = "0"
            velocity.x += 1
            if $Footsteps.playing == false:
                $Footsteps.play()
        if Input.is_action_pressed("move-left"):
            Global.player_direction = "1"
            velocity.x -= 1
            if $Footsteps.playing == false:
                $Footsteps.play()
        if Input.is_action_pressed("move-down"):
            Global.player_direction = "2"
            velocity.y += 1
            if $Footsteps.playing == false:
                $Footsteps.play()
        if Input.is_action_pressed("move-up"):
            Global.player_direction = "3"
            velocity.y -= 1
            if $Footsteps.playing == false:
                $Footsteps.play()
        
        velocity = velocity.normalized() * speed
        velocity = move_and_slide(velocity)
    ```
Global.gd
```rust

    extends Node2D
    
    var player_initial_map_position = Vector2(50,50)
    var player_direction = ["LEFT","RIGHT","UP","DOWN"]
    var player_velocity = Vector2()
    var player_speed
    
    var player_isIdle : bool
    var player_isWalking : bool
    var player_isRunning : bool
    var player_isUsing : bool
    var player_isShooting : bool
    
    var player_pickedUpItem : bool
    var player_DropItem : bool
    
    var pressed_button
    
    var hasM4a1 : bool
    var hasMiningDrill : bool

如果有人能弄清楚我如何使用状态机器,请通过一些方法回应我如何使状态计算机工作以及我还有一个详细的软件体系结构功能模型 ”在此处输入图像说明”

I'm trying to get this finite state machine to work, but it doesn't have anything to do with the run state of the player here is the code:

State.gd


    extends KinematicBody2D
    class_name State
    
    var state_machine = null
    
    var player_velocity = Vector2()
    var player_speed = 200
    var player_energy = 50
    
    enum playerStates{IDLE, WALK, RUN, USE, SHOOT}
    enum ItemStates{ITEM_ON_GROUND, IS_EQUIPED}
    enum Inventory{SELECTED, NOTSELECTED}
    
    var parent = get_parent()
    
    func _ready():
        if Global.player_isIdle == State.state_machine == playerStates.IDLE:
            pass
        if Global.player_isWalking == State.state_machine == playerStates.WALK:
            State.player_speed = Global.player_speed
            State.player_speed = 200
        if Global.player_isRunning == State.state_machine == playerStates.RUN:
            State.player_speed = Global.player_speed
            State.player_speed = 300
        else:
            Global.player_isWalking == State.state_machine == playerStates.WALK
                
    func handle_input(_event: InputEvent):
        if Global.player_velocity != Global.player_velocity.ZERO:
            if Global.player_isWalking == State.state_machine == playerStates.WALK:
                pass
            if Global.player_isRunning == State.state_machine == playerStates.RUN:
                if Input.is_action_pressed("sprint"):
                    print("RUN")
            Global.player_velocity = Global.player_velocity.normalized() * player_speed
            Global.player_velocity = move_and_slide(Global.player_velocity)
            
    func _physics_process(_delta):
        Global.player_velocity = parent.move_and_slide(Global.player_velocity)
    ```

Player.gd
```rust

    onready var velocity = Global.player_velocity
    ...
    func get_input():
        velocity = Vector2()
        #speed = Global.player_speed
        
        if Input.is_action_pressed("sprint"):
            Global.player_isRunning = true 
            #speed = 300
        else:
            #speed = 200
            pass
        if Input.is_action_pressed("move-right"):
            Global.player_direction = "0"
            velocity.x += 1
            if $Footsteps.playing == false:
                $Footsteps.play()
        if Input.is_action_pressed("move-left"):
            Global.player_direction = "1"
            velocity.x -= 1
            if $Footsteps.playing == false:
                $Footsteps.play()
        if Input.is_action_pressed("move-down"):
            Global.player_direction = "2"
            velocity.y += 1
            if $Footsteps.playing == false:
                $Footsteps.play()
        if Input.is_action_pressed("move-up"):
            Global.player_direction = "3"
            velocity.y -= 1
            if $Footsteps.playing == false:
                $Footsteps.play()
        
        velocity = velocity.normalized() * speed
        velocity = move_and_slide(velocity)
    ```
Global.gd
```rust

    extends Node2D
    
    var player_initial_map_position = Vector2(50,50)
    var player_direction = ["LEFT","RIGHT","UP","DOWN"]
    var player_velocity = Vector2()
    var player_speed
    
    var player_isIdle : bool
    var player_isWalking : bool
    var player_isRunning : bool
    var player_isUsing : bool
    var player_isShooting : bool
    
    var player_pickedUpItem : bool
    var player_DropItem : bool
    
    var pressed_button
    
    var hasM4a1 : bool
    var hasMiningDrill : bool

if anyone can figure out how i can use the state machine please respond to me with some methods on how i can get the state machine to work and also i have a detailed software architecture functional model enter image description here

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

喜爱纠缠 2025-02-15 15:57:02

我在解析您的图表时遇到了麻烦。为了避免您的咆哮,我决定忽略它。


在进入状态机之前,我想鼓励使用 types

我可以看到一些更改类型的变量,例如,您声明此数组:

var player_direction = ["LEFT","RIGHT","UP","DOWN"]

但是 - 显然 - 用 string >:

Global.player_direction = "0"

这种事物对代码的正确性作为一个,这并不使我充满信心所有的。


属性蚂蚁匹配

我们从 enum 开始,例如您拥有的:

enum playerStates{IDLE, WALK, RUN, USE, SHOOT}

然后我们将拥有该类型的属性:

var state:playerStates

然后我们可以将该属性匹配到 enum 的值无论您需要:

match state:
   playerStates.IDLE:
       pass # code here
   playerStates.WALK:
       pass # code here
   playerStates.RUN:
       pass # code here
   playerStates.USE:
       pass # code here
   playerStates.SHOOT:
       pass # code here

它比使用语句使用更干净。如果您事先知道所有州,这是一个很好的方法。但是,请注意,如果您修改 enum ,则需要考虑 Match 您可能需要更新的每个地方。

关于状态过渡,它们只是设置变量。例如:

state = playerStates.RUN

我们可以通过拥有专用方法来设置状态来改进这一点。而且只有在那里写州。 例如:例如:

func set_state(new_value:playerStates) -> void:
    state = new_value

在那里您可以使用 Match 来处理状态转换:

func set_state(new_value:playerStates) -> void:
    if state == new_value:
        return

    match new_value:
        playerStates.IDLE:
            pass # code here
        playerStates.WALK:
            pass # code here
        playerStates.RUN:
            pass # code here
        playerStates.USE:
            pass # code here
        playerStates.SHOOT:
            pass # code here

    state = new_value

此外,您可以使用 setget> setget 。但是,请注意,除非您使用 self 访问它,否则从同一类设置器将无法运行。 也需要一点二曲线。

我们可以通过为每个状态使用功能来改进它:

func set_state(new_value:playerStates) -> void:
    if state == new_value:
        return

    var method_name := "on_" + playerStates.keys()[new_value]
    if has_method(method_name):
        call_deferred(method_name)

    state = new_value

func on_IDLE() -> void:
    pass # code here

func on_WALK() -> void:
    pass # code here

func on_RUN() -> void:
    pass # code here

func on_USE() -> void:
    pass # code here

func on_SHOOT() -> void:
    pass # code here

这需要一些解释。

首先,当您声明枚举时,Godot创建了一个具有其值的字典。因此,我们可以使用它来查询类似 enumname.keys()[value] 的值的名称,或者我们可以查询这样的名称的值 enumname [value]

其次,我们可以检查使用 has_method 的方法名称。 如果您确保声明它们,则不必这样做。但是我这里有完整性。,我们可以使用 call call_deferred 来调用名称来调用方法。 根据我的经验 call_deferred 是您想要的。它将避免在改变状态的状态过渡的情况下绩效问题。一个缺点是您失去了旧状态的跟踪,因为在调用方法的时间,状态已经更改了。


节点

到目前为止,问题是:

  • 您需要纪律来保持更改在一个地方的状态。
  • 需要额外的考虑和努力来改变可能的状态。

我们可以通过拥有代码与之交互的专用状态机对象来解决第一个。因此,国家的所有检查和操纵都必须经过它。

要解决第二部分,我们可以上课,而不是使用 enum 。实际上,在Godot,我建议将它们 Node s。

这导致场景树中的该组织:

StateMachine
├ IdleState
├ WalkState
├ RunState
├ UseState
└ ShootState

statemachine 将具有一个脚本,该脚本将暴露您可以使用该属性来设置状态的属性。现在,我们可能不想传递 node ,所以我将以 string >:

extends Node

var state:String setget set_state
var state_node:Node setget no_set

func set_state(new_state:String) -> void:
    var candidate_node := get_node_or_null(new_state + "State")
    if not is_instance_valid(candidate_node):
        return

    if state_node == candidate_node:
        return

    if candidate_node.get_parent() != self:
        return

    state = new_state
    state_node = candidate_node


func no_set(_new_value) -> void:
    push_error("Do not set the state_node directly")

让我们分解:

  • 我们正在使用几个属性 setget 。因此,当我们尝试从另一个脚本设置它们时,它会导致执行给定方法( set_state no_set )。
  • 方法 set_state 将尝试获取一个子节点,其中的名称是从给定的 String 派生的。
    • 我们检查 is_instance_valid ,如果我们没有得到有效 node (例如,我们得到 null )。
    • 我们检查节点我们得到的是一样,在这种情况下,我们什么也不做。
    • 我们检查了节点我们得到的是 statemachine 的孩子。 这是防御性编程检查。
    • 最后,如果一切正常,我们更改状态。
  • 方法 no_set ,除了推动错误之外,有意无能为力。它在那里提醒您不要直接设置 node - 并且要扩展,请防止它。

现在,每个州都可以拥有自己的方法和属性。 实际上,使用节点带来了使用导出var 使状态轻松从编辑器配置的机会。

让我们说,例如,您需要以不同的方式处理输入根据状态,您可以在玩家中使用此功能:

StateMachine.state_node.handle_input()

这将导致在当前状态 node 上调用 handle_input 。然后,每个状态节点 s可以定义该方法:

func handle_input() -> void:
    pass # code here

我们需要确定状态如何访问播放器。 我将把它留给结束。

ah,对,国家过渡。以前,我们在 set_state 中有一个匹配,我们正在根据名称调用方法。我们也做到了。例如,我们可以将当前属性提供给所有状态 node s:

var current:bool = false

然后我们将其设置为 set_state

func set_state(new_state:String) -> void:
    var candidate_node := get_node_or_null(new_state + "State")
    if not is_instance_valid(candidate_node):
        return

    if state_node == candidate_node:
        return

    if candidate_node.get_parent() != self:
        return

    if is_instance_valid(state_node) and "current" in state_node:
        state_node.current = false

    state = new_state
    state_node = candidate_node

    if "current" in state_node:
        state_node.current = true

请注意,我正在使用在中检查 node 是否具有特定属性。 再次,这是一项防御性编程检查。您可以避免,如果您确保它们都拥有它。

此外,我们可以在 setget on 当前上处理状态过渡:

var current:bool = false setget set_current

func set_current(new_value:bool) -> void:
    pass # code here

因此,我们没有<代码>匹配,否 语句根本任何地方。


具有行为的状态

我们一直在美国将方法提交,因此它们确实具有行为。它们是场景树中的节点。我们可以进一步推动它,并使用 _Process _PHYSICS_PROCESS 。为此,我建议使用当前变量启用和禁用它们:

var current:bool = false setget set_current

func _physics_process(delta:float) -> void:
    pass

func set_current(new_value:bool) -> void:
    set_physics_process(new_value)
    pass # code here

class sashitance

我想提到,我们可以为状态> code> node s。在那里,我们可以为所有范围放置常见的代码,类似的东西:

class_name StateNodeBase extends Node

var current:bool = false setget set_current

func _physics_process(delta:float) -> void:
    pass

func set_current(new_value:bool) -> void:
    set_physics_process(new_value)
    on_set_current(new_value)

func on_set_current(new_value:bool) -> void:
    pass

func handle_input() -> void:
    pass # NO code here

然后状态节点扩展了这一点,仅替换了所需的方法。

注意我已经拆分 set_current 纳入 set_current on_set_current ,其中 on_set_current 是要更换的部分。

extends StateNodeBase

func on_set_current(new_value:bool) -> void:
    pass # code here

func handle_input() -> void:
    pass # code here

​而且我宁愿不列举它们。

实际上,我有一个哲学上的问题:这些状态还是行为?由于我们一直在美国将方法提交,因此它们确实具有行为。如果他们应该是行为,也许他们不应该具有内部状态。这将使您重复使用它们。 例如,如果您的敌人具有如下所述创建的状态计算机。他们每个人都有状态机的副本。 如果您只能有一个副本并重复使用它?

但是, 这需要使玩家成为其自己场景的根源,例如:

Player
├ OtherStuff
└ StateMachine
  ├ IdleState
  ├ WalkState
  ├ RunState
  ├ UseState
  └ ShootState

如果 player 是场景的根源。然后其他节点的所有者将参考它。

I'm having trouble parsing your diagram. To spare you the rant, I've decided to ignore it.


Before getting into the state machine, I want to encourage using types.

I can see some variables changing type, for example, you declare this array:

var player_direction = ["LEFT","RIGHT","UP","DOWN"]

But - apparently - override it with a String:

Global.player_direction = "0"

This sort of thing does not give me confidence on the correctness of the code as a whole.


Property ant match

We start with an enum, such as the one you have:

enum playerStates{IDLE, WALK, RUN, USE, SHOOT}

Then we are going to have a property of that type:

var state:playerStates

Then we can match that property to the values of the enum wherever you need:

match state:
   playerStates.IDLE:
       pass # code here
   playerStates.WALK:
       pass # code here
   playerStates.RUN:
       pass # code here
   playerStates.USE:
       pass # code here
   playerStates.SHOOT:
       pass # code here

That is cleaner than using if statements. And it is a good approach if you know all the states before hand. However, notice that if you modify enum, you need to consider every place where you match that you might need to update.

About the state transitions, they are simply setting the variable. For example:

state = playerStates.RUN

We can improve upon that by having a dedicated method to set the state. And only ever write the state there. Which takes a little dicipline. For example:

func set_state(new_value:playerStates) -> void:
    state = new_value

And there you can handle state transitions with a match:

func set_state(new_value:playerStates) -> void:
    if state == new_value:
        return

    match new_value:
        playerStates.IDLE:
            pass # code here
        playerStates.WALK:
            pass # code here
        playerStates.RUN:
            pass # code here
        playerStates.USE:
            pass # code here
        playerStates.SHOOT:
            pass # code here

    state = new_value

Furthermore you could use setget. However, be aware that from the same class a setter would not run unless you use self to access it. Which also takes a little dicipline.

We can improve over that by using a function for each state:

func set_state(new_value:playerStates) -> void:
    if state == new_value:
        return

    var method_name := "on_" + playerStates.keys()[new_value]
    if has_method(method_name):
        call_deferred(method_name)

    state = new_value

func on_IDLE() -> void:
    pass # code here

func on_WALK() -> void:
    pass # code here

func on_RUN() -> void:
    pass # code here

func on_USE() -> void:
    pass # code here

func on_SHOOT() -> void:
    pass # code here

This takes a little explanation.

First, when you declare an enum, Godot creates a dictionary with its values. So we can use it to query the name of a value like this EnumName.keys()[value] or we can query the value of a name like this EnumName[value].

Second, we can check for a method name with has_method. Which you would not have to do if you make sure to declare them. But I have here for completeness. And we can call a method by name using call or call_deferred. In my experience call_deferred is what you want. It will avoid performance issues with state transitions changing the state. A drawback is that you lose track of the old state, because for the time the method is called, the state already changed.


Nodes

So far the issues are:

  • You need discipline to keep changes of state in a single place.
  • It takes extra consideration and effort to change the possible states.

We can address the first one by having a dedicated State Machine object that our code interacts with. So that all the checks and manipulations of the state has to go through it.

To address the second part, we can make classes instead of using an enum. In fact, in Godot, I would suggest to make them Nodes.

This leads to this organization in the scene tree:

StateMachine
├ IdleState
├ WalkState
├ RunState
├ UseState
└ ShootState

The StateMachine would have a script, which will expose a property that you can use to set the state. Now, we probably don't want to pass a Node, so I'll make it take a String:

extends Node

var state:String setget set_state
var state_node:Node setget no_set

func set_state(new_state:String) -> void:
    var candidate_node := get_node_or_null(new_state + "State")
    if not is_instance_valid(candidate_node):
        return

    if state_node == candidate_node:
        return

    if candidate_node.get_parent() != self:
        return

    state = new_state
    state_node = candidate_node


func no_set(_new_value) -> void:
    push_error("Do not set the state_node directly")

Let us break this down:

  • We are making a couple properties with setget. So when we try to set them from another script, it results in executing the given methods (set_state and no_set).
  • The method set_state will try to get a child node with a name derived from the given String.
    • We check is_instance_valid in case we didn't get a valid Node (e.g. we got null).
    • We check if the Node we got is the same we had, in which case we do nothing.
    • And we check if the Node we got is a child of the StateMachine. This a defensive programming check.
    • Finally, if everything is OK, we change the state.
  • The method no_set, intentionally does nothing except pushing an error. It is there to remind you to not set the Node directly - and to an extend, prevent it.

Now each of the states can have their own methods and properties. In fact, using nodes brings the opportunity of using export var to make the states easily configurable from the editor.

Let us say, for example, that you need to handle input differently depending on the state, you can have this in your player:

StateMachine.state_node.handle_input()

Which would result in calling an handle_input on the current state Node. Then each of the state Nodes can define that method:

func handle_input() -> void:
    pass # code here

We need to decide how the states are going to access the player. I'll leave that for the end.

Ah, right, the state transitions. Previously we had a match in set_state an we were calling methods based on the name. We approach that too. For example, we can give a current property to all state Nodes:

var current:bool = false

Then we will set it form set_state

func set_state(new_state:String) -> void:
    var candidate_node := get_node_or_null(new_state + "State")
    if not is_instance_valid(candidate_node):
        return

    if state_node == candidate_node:
        return

    if candidate_node.get_parent() != self:
        return

    if is_instance_valid(state_node) and "current" in state_node:
        state_node.current = false

    state = new_state
    state_node = candidate_node

    if "current" in state_node:
        state_node.current = true

Notice here that I'm using in to check if the Node has a particular property. Again, this is a defensive programming check. You could avoid that if you make sure they all have it.

Furthermore, we can use setget on current to handle the state transitions:

var current:bool = false setget set_current

func set_current(new_value:bool) -> void:
    pass # code here

So we have no match and no if statements anywhere at all.


States with behavior

We have been putting methods in the states, so they do have behavior. And they are Nodes in the scene tree. We can push that further, and use _process or _physics_process in them. For that I'd suggest enabling and disabling them with the current variable:

var current:bool = false setget set_current

func _physics_process(delta:float) -> void:
    pass

func set_current(new_value:bool) -> void:
    set_physics_process(new_value)
    pass # code here

Class inheritance

I want to mention that we can make a base class for the state Nodes. And there we can put the common code for all the sates, something like this:

class_name StateNodeBase extends Node

var current:bool = false setget set_current

func _physics_process(delta:float) -> void:
    pass

func set_current(new_value:bool) -> void:
    set_physics_process(new_value)
    on_set_current(new_value)

func on_set_current(new_value:bool) -> void:
    pass

func handle_input() -> void:
    pass # NO code here

And then the state Node extend that, replacing only the methods they need.

Notice I have split set_current into set_current and on_set_current, where on_set_current is the part intended to be replaced.

For example:

extends StateNodeBase

func on_set_current(new_value:bool) -> void:
    pass # code here

func handle_input() -> void:
    pass # code here

Passing the player

There are multiple approaches to pass a reference to the player to the states. And I would rather not enumerate them.

In fact, I have a philosophical question for you: Are these states or behaviors? Since we have been putting methods in the states, they do have behavior. If they ought to be behaviors, perhaps they should not have internal state. Which would allow you to reuse them. For example, if you have enemies with an state machine created as described here. Each of them would have a copy of the state machine. But what if you could only have one copy and reuse it?

Anyway, to keep it simple, the solution I'll put forth is to use owner. This requires making the player the root of its own scene, like this:

Player
├ OtherStuff
└ StateMachine
  ├ IdleState
  ├ WalkState
  ├ RunState
  ├ UseState
  └ ShootState

If the Player is the root of the scene. Then owner of the other nodes will refer to it.

请止步禁区 2025-02-15 15:57:02

这来自 gdquest
希望它将对您有所帮助。
它非常适合我。
您可以制作许多州机器,
一个用于球员运动,一个用于进攻状态,还有更多。

This is from GDQuest.
Hopefully, it will help you.
It worked for me perfectly.
You can make many state machines,
one for player movement, one for attacking state and many more.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文