03.coding_the_player.rst 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. .. _doc_your_first_2d_game_coding_the_player:
  2. Coding the player
  3. =================
  4. In this lesson, we'll add player movement, animation, and set it up to detect
  5. collisions.
  6. To do so, we need to add some functionality that we can't get from a built-in
  7. node, so we'll add a script. Click the ``Player`` node and click the "Attach
  8. Script" button:
  9. .. image:: img/add_script_button.webp
  10. In the script settings window, you can leave the default settings alone. Just
  11. click "Create":
  12. .. note:: If you're creating a C# script or other languages, select the language
  13. from the `language` drop down menu before hitting create.
  14. .. image:: img/attach_node_window.webp
  15. .. note:: If this is your first time encountering GDScript, please read
  16. :ref:`doc_scripting` before continuing.
  17. Start by declaring the member variables this object will need:
  18. .. tabs::
  19. .. code-tab:: gdscript GDScript
  20. extends Area2D
  21. @export var speed = 400 # How fast the player will move (pixels/sec).
  22. var screen_size # Size of the game window.
  23. .. code-tab:: csharp
  24. using Godot;
  25. public partial class Player : Area2D
  26. {
  27. [Export]
  28. public int Speed { get; set; } = 400; // How fast the player will move (pixels/sec).
  29. public Vector2 ScreenSize; // Size of the game window.
  30. }
  31. .. code-tab:: cpp
  32. // A `player.gdns` file has already been created for you. Attach it to the Player node.
  33. // Create two files `player.cpp` and `player.hpp` next to `entry.cpp` in `src`.
  34. // This code goes in `player.hpp`. We also define the methods we'll be using here.
  35. #ifndef PLAYER_H
  36. #define PLAYER_H
  37. #include <AnimatedSprite2D.hpp>
  38. #include <Area2D.hpp>
  39. #include <CollisionShape2D.hpp>
  40. #include <Godot.hpp>
  41. #include <Input.hpp>
  42. class Player : public godot::Area2D {
  43. GODOT_CLASS(Player, godot::Area2D)
  44. godot::AnimatedSprite2D *_animated_sprite;
  45. godot::CollisionShape2D *_collision_shape;
  46. godot::Input *_input;
  47. godot::Vector2 _screen_size; // Size of the game window.
  48. public:
  49. real_t speed = 400; // How fast the player will move (pixels/sec).
  50. void _init() {}
  51. void _ready();
  52. void _process(const double p_delta);
  53. void start(const godot::Vector2 p_position);
  54. void _on_body_entered(godot::Node2D *_body);
  55. static void _register_methods();
  56. };
  57. #endif // PLAYER_H
  58. Using the ``export`` keyword on the first variable ``speed`` allows us to set
  59. its value in the Inspector. This can be handy for values that you want to be
  60. able to adjust just like a node's built-in properties. Click on the ``Player``
  61. node and you'll see the property now appears in the "Script Variables" section
  62. of the Inspector. Remember, if you change the value here, it will override the
  63. value written in the script.
  64. .. warning::
  65. If you're using C#, you need to (re)build the project assemblies
  66. whenever you want to see new export variables or signals. This
  67. build can be manually triggered by clicking the "Build" button at
  68. the top right of the editor.
  69. .. image:: img/build_dotnet.webp
  70. A manual build can also be triggered from the MSBuild Panel. Click
  71. the word "MSBuild" at the bottom of the editor window to reveal the
  72. MSBuild Panel, then click the "Build" button.
  73. .. image:: img/export_variable.webp
  74. Your ``player.gd`` script should already contain
  75. a ``_ready()`` and a ``_process()`` function.
  76. If you didn't select the default template shown above,
  77. create these functions while following the lesson.
  78. The ``_ready()`` function is called when a node enters the scene tree, which is
  79. a good time to find the size of the game window:
  80. .. tabs::
  81. .. code-tab:: gdscript GDScript
  82. func _ready():
  83. screen_size = get_viewport_rect().size
  84. .. code-tab:: csharp
  85. public override void _Ready()
  86. {
  87. ScreenSize = GetViewportRect().Size;
  88. }
  89. .. code-tab:: cpp
  90. // This code goes in `player.cpp`.
  91. #include "player.hpp"
  92. void Player::_ready() {
  93. _animated_sprite = get_node<godot::AnimatedSprite2D>("AnimatedSprite2D");
  94. _collision_shape = get_node<godot::CollisionShape2D>("CollisionShape2D");
  95. _input = godot::Input::get_singleton();
  96. _screen_size = get_viewport_rect().size;
  97. }
  98. Now we can use the ``_process()`` function to define what the player will do.
  99. ``_process()`` is called every frame, so we'll use it to update elements of our
  100. game, which we expect will change often. For the player, we need to do the
  101. following:
  102. - Check for input.
  103. - Move in the given direction.
  104. - Play the appropriate animation.
  105. First, we need to check for input - is the player pressing a key? For this game,
  106. we have 4 direction inputs to check. Input actions are defined in the Project
  107. Settings under "Input Map". Here, you can define custom events and assign
  108. different keys, mouse events, or other inputs to them. For this game, we will
  109. map the arrow keys to the four directions.
  110. Click on *Project -> Project Settings* to open the project settings window and
  111. click on the *Input Map* tab at the top. Type "move_right" in the top bar and
  112. click the "Add" button to add the ``move_right`` action.
  113. .. image:: img/input-mapping-add-action.webp
  114. We need to assign a key to this action. Click the "+" icon on the right, to
  115. open the event manager window.
  116. .. image:: img/input-mapping-add-key.webp
  117. The "Listening for Input..." field should automatically be selected.
  118. Press the "right" key on your keyboard, and the menu should look like this now.
  119. .. image:: img/input-mapping-event-configuration.webp
  120. Select the "ok" button. The "right" key is now associated with the ``move_right`` action.
  121. Repeat these steps to add three more mappings:
  122. 1. ``move_left`` mapped to the left arrow key.
  123. 2. ``move_up`` mapped to the up arrow key.
  124. 3. And ``move_down`` mapped to the down arrow key.
  125. Your input map tab should look like this:
  126. .. image:: img/input-mapping-completed.webp
  127. Click the "Close" button to close the project settings.
  128. .. note::
  129. We only mapped one key to each input action, but you can map multiple keys,
  130. joystick buttons, or mouse buttons to the same input action.
  131. You can detect whether a key is pressed using ``Input.is_action_pressed()``,
  132. which returns ``true`` if it's pressed or ``false`` if it isn't.
  133. .. tabs::
  134. .. code-tab:: gdscript GDScript
  135. func _process(delta):
  136. var velocity = Vector2.ZERO # The player's movement vector.
  137. if Input.is_action_pressed("move_right"):
  138. velocity.x += 1
  139. if Input.is_action_pressed("move_left"):
  140. velocity.x -= 1
  141. if Input.is_action_pressed("move_down"):
  142. velocity.y += 1
  143. if Input.is_action_pressed("move_up"):
  144. velocity.y -= 1
  145. if velocity.length() > 0:
  146. velocity = velocity.normalized() * speed
  147. $AnimatedSprite2D.play()
  148. else:
  149. $AnimatedSprite2D.stop()
  150. .. code-tab:: csharp
  151. public override void _Process(double delta)
  152. {
  153. var velocity = Vector2.Zero; // The player's movement vector.
  154. if (Input.IsActionPressed("move_right"))
  155. {
  156. velocity.X += 1;
  157. }
  158. if (Input.IsActionPressed("move_left"))
  159. {
  160. velocity.X -= 1;
  161. }
  162. if (Input.IsActionPressed("move_down"))
  163. {
  164. velocity.Y += 1;
  165. }
  166. if (Input.IsActionPressed("move_up"))
  167. {
  168. velocity.Y -= 1;
  169. }
  170. var animatedSprite2D = GetNode<AnimatedSprite2D>("AnimatedSprite2D");
  171. if (velocity.Length() > 0)
  172. {
  173. velocity = velocity.Normalized() * Speed;
  174. animatedSprite2D.Play();
  175. }
  176. else
  177. {
  178. animatedSprite2D.Stop();
  179. }
  180. }
  181. .. code-tab:: cpp
  182. // This code goes in `player.cpp`.
  183. void Player::_process(const double p_delta) {
  184. godot::Vector2 velocity(0, 0);
  185. velocity.x = _input->get_action_strength("move_right") - _input->get_action_strength("move_left");
  186. velocity.y = _input->get_action_strength("move_down") - _input->get_action_strength("move_up");
  187. if (velocity.length() > 0) {
  188. velocity = velocity.normalized() * speed;
  189. _animated_sprite->play();
  190. } else {
  191. _animated_sprite->stop();
  192. }
  193. }
  194. We start by setting the ``velocity`` to ``(0, 0)`` - by default, the player
  195. should not be moving. Then we check each input and add/subtract from the
  196. ``velocity`` to obtain a total direction. For example, if you hold ``right`` and
  197. ``down`` at the same time, the resulting ``velocity`` vector will be ``(1, 1)``.
  198. In this case, since we're adding a horizontal and a vertical movement, the
  199. player would move *faster* diagonally than if it just moved horizontally.
  200. We can prevent that if we *normalize* the velocity, which means we set its
  201. *length* to ``1``, then multiply by the desired speed. This means no more fast
  202. diagonal movement.
  203. .. tip:: If you've never used vector math before, or need a refresher, you can
  204. see an explanation of vector usage in Godot at :ref:`doc_vector_math`.
  205. It's good to know but won't be necessary for the rest of this tutorial.
  206. We also check whether the player is moving so we can call ``play()`` or
  207. ``stop()`` on the AnimatedSprite2D.
  208. .. tip:: ``$`` is shorthand for ``get_node()``. So in the code above,
  209. ``$AnimatedSprite2D.play()`` is the same as
  210. ``get_node("AnimatedSprite2D").play()``.
  211. In GDScript, ``$`` returns the node at the relative path from the
  212. current node, or returns ``null`` if the node is not found. Since
  213. AnimatedSprite2D is a child of the current node, we can use
  214. ``$AnimatedSprite2D``.
  215. Now that we have a movement direction, we can update the player's position. We
  216. can also use ``clamp()`` to prevent it from leaving the screen. *Clamping* a
  217. value means restricting it to a given range. Add the following to the bottom of
  218. the ``_process`` function (make sure it's not indented under the `else`):
  219. .. tabs::
  220. .. code-tab:: gdscript GDScript
  221. position += velocity * delta
  222. position = position.clamp(Vector2.ZERO, screen_size)
  223. .. code-tab:: csharp
  224. Position += velocity * (float)delta;
  225. Position = new Vector2(
  226. x: Mathf.Clamp(Position.X, 0, ScreenSize.X),
  227. y: Mathf.Clamp(Position.Y, 0, ScreenSize.Y)
  228. );
  229. .. code-tab:: cpp
  230. godot::Vector2 position = get_position();
  231. position += velocity * (real_t)p_delta;
  232. position.x = godot::Math::clamp(position.x, (real_t)0.0, _screen_size.x);
  233. position.y = godot::Math::clamp(position.y, (real_t)0.0, _screen_size.y);
  234. set_position(position);
  235. .. tip:: The `delta` parameter in the `_process()` function refers to the *frame
  236. length* - the amount of time that the previous frame took to complete.
  237. Using this value ensures that your movement will remain consistent even
  238. if the frame rate changes.
  239. Click "Play Scene" (:kbd:`F6`, :kbd:`Cmd + R` on macOS) and confirm you can move
  240. the player around the screen in all directions.
  241. .. warning:: If you get an error in the "Debugger" panel that says
  242. ``Attempt to call function 'play' in base 'null instance' on a null
  243. instance``
  244. this likely means you spelled the name of the AnimatedSprite2D node
  245. wrong. Node names are case-sensitive and ``$NodeName`` must match
  246. the name you see in the scene tree.
  247. Choosing animations
  248. ~~~~~~~~~~~~~~~~~~~
  249. Now that the player can move, we need to change which animation the
  250. AnimatedSprite2D is playing based on its direction. We have the "walk" animation,
  251. which shows the player walking to the right. This animation should be flipped
  252. horizontally using the ``flip_h`` property for left movement. We also have the
  253. "up" animation, which should be flipped vertically with ``flip_v`` for downward
  254. movement. Let's place this code at the end of the ``_process()`` function:
  255. .. tabs::
  256. .. code-tab:: gdscript GDScript
  257. if velocity.x != 0:
  258. $AnimatedSprite2D.animation = "walk"
  259. $AnimatedSprite2D.flip_v = false
  260. # See the note below about boolean assignment.
  261. $AnimatedSprite2D.flip_h = velocity.x < 0
  262. elif velocity.y != 0:
  263. $AnimatedSprite2D.animation = "up"
  264. $AnimatedSprite2D.flip_v = velocity.y > 0
  265. .. code-tab:: csharp
  266. if (velocity.X != 0)
  267. {
  268. animatedSprite2D.Animation = "walk";
  269. animatedSprite2D.FlipV = false;
  270. // See the note below about boolean assignment.
  271. animatedSprite2D.FlipH = velocity.X < 0;
  272. }
  273. else if (velocity.Y != 0)
  274. {
  275. animatedSprite2D.Animation = "up";
  276. animatedSprite2D.FlipV = velocity.Y > 0;
  277. }
  278. .. code-tab:: cpp
  279. if (velocity.x != 0) {
  280. _animated_sprite->set_animation("walk");
  281. _animated_sprite->set_flip_v(false);
  282. // See the note below about boolean assignment.
  283. _animated_sprite->set_flip_h(velocity.x < 0);
  284. } else if (velocity.y != 0) {
  285. _animated_sprite->set_animation("up");
  286. _animated_sprite->set_flip_v(velocity.y > 0);
  287. }
  288. .. Note:: The boolean assignments in the code above are a common shorthand for
  289. programmers. Since we're doing a comparison test (boolean) and also
  290. *assigning* a boolean value, we can do both at the same time. Consider
  291. this code versus the one-line boolean assignment above:
  292. .. tabs::
  293. .. code-tab :: gdscript GDScript
  294. if velocity.x < 0:
  295. $AnimatedSprite2D.flip_h = true
  296. else:
  297. $AnimatedSprite2D.flip_h = false
  298. .. code-tab:: csharp
  299. if (velocity.X < 0)
  300. {
  301. animatedSprite2D.FlipH = true;
  302. }
  303. else
  304. {
  305. animatedSprite2D.FlipH = false;
  306. }
  307. Play the scene again and check that the animations are correct in each of the
  308. directions.
  309. .. tip:: A common mistake here is to type the names of the animations wrong. The
  310. animation names in the SpriteFrames panel must match what you type in
  311. the code. If you named the animation ``"Walk"``, you must also use a
  312. capital "W" in the code.
  313. When you're sure the movement is working correctly, add this line to
  314. ``_ready()``, so the player will be hidden when the game starts:
  315. .. tabs::
  316. .. code-tab:: gdscript GDScript
  317. hide()
  318. .. code-tab:: csharp
  319. Hide();
  320. .. code-tab:: cpp
  321. hide();
  322. Preparing for collisions
  323. ~~~~~~~~~~~~~~~~~~~~~~~~
  324. We want ``Player`` to detect when it's hit by an enemy, but we haven't made any
  325. enemies yet! That's OK, because we're going to use Godot's *signal*
  326. functionality to make it work.
  327. Add the following at the top of the script. If you're using GDScript, add it after
  328. ``extends Area2D``. If you're using C#, add it after ``public partial class Player : Area2D``:
  329. .. tabs::
  330. .. code-tab:: gdscript GDScript
  331. signal hit
  332. .. code-tab:: csharp
  333. // Don't forget to rebuild the project so the editor knows about the new signal.
  334. [Signal]
  335. public delegate void HitEventHandler();
  336. .. code-tab:: cpp
  337. // This code goes in `player.cpp`.
  338. // We need to register the signal here, and while we're here, we can also
  339. // register the other methods and register the speed property.
  340. void Player::_register_methods() {
  341. godot::register_method("_ready", &Player::_ready);
  342. godot::register_method("_process", &Player::_process);
  343. godot::register_method("start", &Player::start);
  344. godot::register_method("_on_body_entered", &Player::_on_body_entered);
  345. godot::register_property("speed", &Player::speed, (real_t)400.0);
  346. // This below line is the signal.
  347. godot::register_signal<Player>("hit", godot::Dictionary());
  348. }
  349. This defines a custom signal called "hit" that we will have our player emit
  350. (send out) when it collides with an enemy. We will use ``Area2D`` to detect the
  351. collision. Select the ``Player`` node and click the "Node" tab next to the
  352. Inspector tab to see the list of signals the player can emit:
  353. .. image:: img/player_signals.webp
  354. Notice our custom "hit" signal is there as well! Since our enemies are going to
  355. be ``RigidBody2D`` nodes, we want the ``body_entered(body: Node2D)`` signal. This
  356. signal will be emitted when a body contacts the player. Click "Connect.." and
  357. the "Connect a Signal" window appears. We don't need to change any of these
  358. settings so click "Connect" again. Godot will automatically create a function in
  359. your player's script.
  360. .. image:: img/player_signal_connection.webp
  361. Note the green icon indicating that a signal is connected to this function. Add
  362. this code to the function:
  363. .. tabs::
  364. .. code-tab:: gdscript GDScript
  365. func _on_body_entered(body):
  366. hide() # Player disappears after being hit.
  367. hit.emit()
  368. # Must be deferred as we can't change physics properties on a physics callback.
  369. $CollisionShape2D.set_deferred("disabled", true)
  370. .. code-tab:: csharp
  371. private void OnBodyEntered(Node2D body)
  372. {
  373. Hide(); // Player disappears after being hit.
  374. EmitSignal(SignalName.Hit);
  375. // Must be deferred as we can't change physics properties on a physics callback.
  376. GetNode<CollisionShape2D>("CollisionShape2D").SetDeferred(CollisionShape2D.PropertyName.Disabled, true);
  377. }
  378. .. code-tab:: cpp
  379. // This code goes in `player.cpp`.
  380. void Player::_on_body_entered(godot::Node2D *_body) {
  381. hide(); // Player disappears after being hit.
  382. emit_signal("hit");
  383. // Must be deferred as we can't change physics properties on a physics callback.
  384. _collision_shape->set_deferred("disabled", true);
  385. }
  386. Each time an enemy hits the player, the signal is going to be emitted. We need
  387. to disable the player's collision so that we don't trigger the ``hit`` signal
  388. more than once.
  389. .. Note:: Disabling the area's collision shape can cause an error if it happens
  390. in the middle of the engine's collision processing. Using
  391. ``set_deferred()`` tells Godot to wait to disable the shape until it's
  392. safe to do so.
  393. The last piece is to add a function we can call to reset the player when
  394. starting a new game.
  395. .. tabs::
  396. .. code-tab:: gdscript GDScript
  397. func start(pos):
  398. position = pos
  399. show()
  400. $CollisionShape2D.disabled = false
  401. .. code-tab:: csharp
  402. public void Start(Vector2 position)
  403. {
  404. Position = position;
  405. Show();
  406. GetNode<CollisionShape2D>("CollisionShape2D").Disabled = false;
  407. }
  408. .. code-tab:: cpp
  409. // This code goes in `player.cpp`.
  410. void Player::start(const godot::Vector2 p_position) {
  411. set_position(p_position);
  412. show();
  413. _collision_shape->set_disabled(false);
  414. }
  415. With the player working, we'll work on the enemy in the next lesson.