diff --git a/index.rst b/index.rst index df1afd6ef..a742d2b61 100644 --- a/index.rst +++ b/index.rst @@ -104,8 +104,7 @@ The main documentation for the site is organized into the following sections: tutorials/platform/index tutorials/threads/index tutorials/content/index - tutorials/optimization/index - tutorials/misc/index + tutorials/performance/index tutorials/debug/index diff --git a/tutorials/misc/files/llama.zip b/tutorials/misc/files/llama.zip deleted file mode 100644 index d8cb4b147..000000000 Binary files a/tutorials/misc/files/llama.zip and /dev/null differ diff --git a/tutorials/misc/img/llama_run.gif b/tutorials/misc/img/llama_run.gif deleted file mode 100644 index a60e91023..000000000 Binary files a/tutorials/misc/img/llama_run.gif and /dev/null differ diff --git a/tutorials/misc/img/state_design_complete.gif b/tutorials/misc/img/state_design_complete.gif deleted file mode 100644 index 17a34e86c..000000000 Binary files a/tutorials/misc/img/state_design_complete.gif and /dev/null differ diff --git a/tutorials/misc/img/state_design_node_setup.png b/tutorials/misc/img/state_design_node_setup.png deleted file mode 100644 index 3ea5b8328..000000000 Binary files a/tutorials/misc/img/state_design_node_setup.png and /dev/null differ diff --git a/tutorials/misc/index.rst b/tutorials/misc/index.rst deleted file mode 100644 index d17f1a650..000000000 --- a/tutorials/misc/index.rst +++ /dev/null @@ -1,8 +0,0 @@ -Miscellaneous -============= - -.. toctree:: - :maxdepth: 1 - :name: toc-learn-features-misc - - state_design_pattern diff --git a/tutorials/misc/state_design_pattern.rst b/tutorials/misc/state_design_pattern.rst deleted file mode 100644 index 7ee4ad07d..000000000 --- a/tutorials/misc/state_design_pattern.rst +++ /dev/null @@ -1,257 +0,0 @@ -.. _doc_state_design_pattern: - -State design pattern -==================== - -Introduction ------------- - -Scripting a game can be difficult when there are many states that need to handled, but -only one script can be attached to a node at a time. Instead of creating a state machine -within the player's control script, it would make development simpler if the states were -separated out into different classes. - -There are many ways to implement a state machine with Godot, and some other methods are below: - -* The player can have a child node for each state, which are called when utilized. -* Enums can be used in conjunction with a match statement. -* The state scripts themselves could be swapped out from a node dynamically at run-time. - -This tutorial will focus only on adding and removing nodes which have a state script attached. Each state -script will be an implementation of a different state. - -.. note:: - There is a great resource explaining the concept of the state design pattern here: - https://gameprogrammingpatterns.com/state.html - -Script setup ------------- - -The feature of inheritance is useful for getting started with this design principle. -A class should be created that describes the base features of the player. For now, a -player will be limited to two actions: **move left**, **move right**. This means -there will be two states: **idle** and **run**. - -Below is the generic state, from which all other states will inherit. - -.. tabs:: - .. code-tab:: gdscript GDScript - - # state.gd - - extends Node2D - - class_name State - - var change_state - var animated_sprite - var persistent_state - var velocity = 0 - - # Writing _delta instead of delta here prevents the unused variable warning. - func _physics_process(_delta): - persistent_state.move_and_slide(persistent_state.velocity, Vector2.UP) - - func setup(change_state, animated_sprite, persistent_state): - self.change_state = change_state - self.animated_sprite = animated_sprite - self.persistent_state = persistent_state - - func move_left(): - pass - - func move_right(): - pass - -A few notes on the above script. First, this implementation uses a -``setup(change_state, animated_sprite, persistent_state)`` method to assign -references. These references will be instantiated in the parent of this state. This helps with something -in programming known as *cohesion*. The state of the player does not want the responsibility of creating -these variables, but does want to be able to use them. However, this does make the state *coupled* to the -state's parent. This means that the state is highly reliant on whether it has a parent which contains -these variables. So, remember that *coupling* and *cohesion* are important concepts when it comes to code management. - -.. note:: - See the following page for more details on cohesion and coupling: - https://courses.cs.washington.edu/courses/cse403/96sp/coupling-cohesion.html - -Second, there are some methods in the script for moving, but no implementation. The state script -just uses ``pass`` to show that it will not execute any instructions when the methods are called. This is important. - -Third, the ``_physics_process(delta)`` method is actually implemented here. This allows the states to have a default -``_physics_process(delta)`` implementation where ``velocity`` is used to move the player. The way that the states can modify -the movement of the player is to use the ``velocity`` variable defined in their base class. - -Finally, this script is actually being designated as a class named ``State``. This makes refactoring the code -easier, since the file path from using the ``load()`` and ``preload()`` functions in Godot will not be needed. - -So, now that there is a base state, the two states discussed earlier can be implemented. - -.. tabs:: - .. code-tab:: gdscript GDScript - - # idle_state.gd - - extends State - - class_name IdleState - - func _ready(): - animated_sprite.play("idle") - - func _flip_direction(): - animated_sprite.flip_h = not animated_sprite.flip_h - - func move_left(): - if animated_sprite.flip_h: - change_state.call_func("run") - else: - _flip_direction() - - func move_right(): - if not animated_sprite.flip_h: - change_state.call_func("run") - else: - _flip_direction() - -.. tabs:: - .. code-tab:: gdscript GDScript - - # run_state.gd - - extends State - - class_name RunState - - var move_speed = Vector2(180, 0) - var min_move_speed = 0.005 - var friction = 0.32 - - func _ready(): - animated_sprite.play("run") - if animated_sprite.flip_h: - move_speed.x *= -1 - persistent_state.velocity += move_speed - - func _physics_process(_delta): - if abs(persistent_state.velocity.x) < min_move_speed: - change_state.call_func("idle") - persistent_state.velocity.x *= friction - - func move_left(): - if animated_sprite.flip_h: - persistent_state.velocity += move_speed - else: - change_state.call_func("idle") - - func move_right(): - if not animated_sprite.flip_h: - persistent_state.velocity += move_speed - else: - change_state.call_func("idle") - -.. note:: - Since the ``Run`` and ``Idle`` states extend from ``State`` which extends ``Node2D``, the function - ``_physics_process(delta)`` is called from the **bottom-up** meaning ``Run`` and ``Idle`` will call their - implementation of ``_physics_process(delta)``, then ``State`` will call its implementation, then ``Node2D`` - will call its own implementation and so on. This may seem strange, but it is only relevant for predefined functions - such as ``_ready()``, ``_process(delta)``, etc. Custom functions use the normal inheritance rules of overriding - the base implementation. - -There is a roundabout method for obtaining a state instance. A state factory can be used. - -.. tabs:: - .. code-tab:: gdscript GDScript - - # state_factory.gd - - class_name StateFactory - - var states - - func _init(): - states = { - "idle": IdleState, - "run": RunState - } - - func get_state(state_name): - if states.has(state_name): - return states.get(state_name) - else: - printerr("No state ", state_name, " in state factory!") - -This will look for states in a dictionary and return the state if found. - -Now that all the states are defined with their own scripts, it is time to figure out -how those references that passed to them will be instantiated. Since these references -will not change it makes sense to call this new script ``persistent_state.gd``. - -.. tabs:: - .. code-tab:: gdscript GDScript - - # persistent_state.gd - - extends KinematicBody2D - - class_name PersistentState - - var state - var state_factory - - var velocity = Vector2() - - func _ready(): - state_factory = StateFactory.new() - change_state("idle") - - # Input code was placed here for tutorial purposes. - func _process(_delta): - if Input.is_action_pressed("ui_left"): - move_left() - elif Input.is_action_pressed("ui_right"): - move_right() - - func move_left(): - state.move_left() - - func move_right(): - state.move_right() - - func change_state(new_state_name): - if state != null: - state.queue_free() - state = state_factory.get_state(new_state_name).new() - state.setup(funcref(self, "change_state"), $AnimatedSprite, self) - state.name = "current_state" - add_child(state) - -.. note:: - The ``persistent_state.gd`` script contains code for detecting input. This was to make the tutorial simple, but it is not usually - best practice to do this. - -Project setup -------------- - -This tutorial made an assumption that the node it would be attached to contained a child node which is an :ref:`AnimatedSprite `. -There is also the assumption that this :ref:`AnimatedSprite ` has at least two animations, -the idle and run animations. Also, the top-level node is assumed to be a :ref:`KinematicBody2D `. - -.. image:: img/llama_run.gif - -.. note:: - The zip file of the llama used in this tutorial is :download:`here `. - The source was from `piskel_llama `_, but - I couldn't find the original creator information on that page... - There is also a good tutorial for sprite animation already. See :ref:`2D Sprite Animation `. - -So, the only script that must be attached is ``persistent_state.gd``, which should be attached to the top node of the -player, which is a :ref:`KinematicBody2D `. - -.. image:: img/state_design_node_setup.png - -.. image:: img/state_design_complete.gif - -Now the player has utilized the state design pattern to implement its two different states. The nice part of this -pattern is that if one wanted to add another state, then it would involve creating another class that need only -focus on itself and how it changes to another state. Each state is functionally separated and instantiated dynamically.