simple_2d_game.rst 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. .. _doc_simple_2d_game:
  2. Simple 2D game
  3. ==============
  4. Pong
  5. ~~~~
  6. In this tutorial, a basic game of Pong will be created. There are plenty
  7. of more complex examples in the demos included with the engine, but this
  8. should get you introduced to the basic functionalities for 2D Games.
  9. To begin with, run the Godot Engine and start a new project.
  10. Assets
  11. ~~~~~~
  12. Some assets are included for this tutorial:
  13. :download:`pong_assets.zip </files/pong_assets.zip>`. Unzip its content
  14. in your project folder.
  15. Scene setup
  16. ~~~~~~~~~~~
  17. For the sake of the old times, the game will be in 640x400 pixels
  18. resolution. This can be configured in the Project Settings (see
  19. :ref:`doc_scenes_and_nodes-configuring_the_project`) under Scene/Project
  20. settings menu. The default background color should be set to black:
  21. .. image:: /img/clearcolor.png
  22. Create a :ref:`class_Node2D` node for the project root. Node2D is the
  23. base type for the 2D engine. After this, add some sprites
  24. (:ref:`class_Sprite` node) for the left and right paddles, the separator
  25. and ball. You can set a custom name for each node, and set the texture
  26. for each sprite in the Inspector.
  27. .. image:: /img/pong_nodes.png
  28. Set nodes positions:
  29. - "left" node: (67, 183)
  30. - "right" node: (577, 187)
  31. - "separator" node: (320, 200)
  32. - "ball" node: (320, 188)
  33. The final scene layout should look similar to this (note: the ball is in
  34. the middle!):
  35. .. image:: /img/pong_layout.png
  36. Save the scene as "pong.tscn" and set it as the main scene in the
  37. project
  38. properties.
  39. .. _doc_simple_2d_game-input_actions_setup:
  40. Input actions setup
  41. ~~~~~~~~~~~~~~~~~~~
  42. Video games can be played using various input methods: Keyboard, Joypad,
  43. Mouse, Touchscreen (multitouch)... Godot is able to use them all.
  44. However, it would be interesting to define the inputs as "Input Actions"
  45. instead of hardware actions that you'd manage separately. This way, any
  46. input method can be used: each of them only require the user to connect
  47. buttons to game actions that you defined.
  48. This is Pong. The only input that matters is for the pads going up and
  49. down.
  50. Open the project properties dialog again (Scene/Project settings), but
  51. this time move to the
  52. "Input Map" tab.
  53. In this tab, add 4 actions:
  54. ``left_move_up``, ``left_move_down``, ``right_move_up``,
  55. ``right_move_down``.
  56. Assign the keys that you desire. A/Z (for the left player) and Up/Down
  57. (for the right player) as keys
  58. should work in most cases.
  59. .. image:: /img/inputmap.png
  60. Script
  61. ~~~~~~
  62. Create a script for the root node of the scene and open it (as explained
  63. in :ref:`doc_scripting-adding_a_script`). This script inherits Node2D:
  64. ::
  65. extends Node2D
  66. func _ready():
  67. pass
  68. First things first, we need to define some members for our script so it
  69. can store useful values. Such values are the dimensions of the screen, the pad
  70. and the initial direction of the ball.
  71. ::
  72. extends Node2D
  73. # Member variables
  74. var screen_size
  75. var pad_size
  76. var direction = Vector2(1.0, 0.0)
  77. func _ready():
  78. pass
  79. As you know, the ``_ready()`` function is the first function called
  80. (after ``_enter_tree()`` which we don't need here). In this function,
  81. two things have to be done. The first one is to enable
  82. processing: this is the purpose of the ``set_process(true)`` function.
  83. The second one is to initialize our two member variables.
  84. ::
  85. extends Node2D
  86. # Member variables
  87. var screen_size
  88. var pad_size
  89. var direction = Vector2(1.0, 0.0)
  90. func _ready():
  91. screen_size = get_viewport_rect().size
  92. pad_size = get_node("left").get_texture().get_size()
  93. set_process(true)
  94. We initialize the ``pad_size`` variable by getting one of the pads nodes
  95. (the left one here), and obtain its texture size. The ``screen_size`` is
  96. initialized using the ``get_viewport_rect()`` which returns a Rect
  97. object corresponding to the game window, and we store its size.
  98. Now, we need to add some other members to our script in order to make
  99. our ball move.
  100. ::
  101. extends Node2D
  102. # Member variables
  103. var screen_size
  104. var pad_size
  105. var direction = Vector2(1.0, 0.0)
  106. # Constant for ball speed (in pixels/second)
  107. const INITIAL_BALL_SPEED = 80
  108. # Speed of the ball (also in pixels/second)
  109. var ball_speed = INITIAL_BALL_SPEED
  110. # Constant for pads speed
  111. const PAD_SPEED = 150
  112. func _ready():
  113. screen_size = get_viewport_rect().size
  114. pad_size = get_node("left").get_texture().get_size()
  115. set_process(true)
  116. Finally, the ``_process()`` function. All the code below is contained by
  117. this function.
  118. We have to init some useful values for computation. The first one is the
  119. ball position (from the node), the second one is the rectangle
  120. (``Rect2``) for each pad. These rectangles will be used for collision
  121. tests between the ball and the pads. Sprites center their textures by
  122. default, so a small adjustment of ``pad_size / 2`` must be added.
  123. ::
  124. func _process(delta):
  125. var ball_pos = get_node("ball").get_pos()
  126. var left_rect = Rect2( get_node("left").get_pos() - pad_size*0.5, pad_size )
  127. var right_rect = Rect2( get_node("right").get_pos() - pad_size*0.5, pad_size )
  128. Now, let's add some movement to the ball in the ``_process()`` function.
  129. Since the ball position is stored in the ``ball_pos`` variable,
  130. integrating it is simple:
  131. ::
  132. # Integrate new ball position
  133. ball_pos += direction * ball_speed * delta
  134. This code line is called at each iteration of the ``_process()``
  135. function. That means the ball position will be updated at each new
  136. frame.
  137. Now that the ball has a new position, we need to test if it
  138. collides with anything, that is the window borders and the pads. First,
  139. the floor and the roof:
  140. ::
  141. # Flip when touching roof or floor
  142. if ((ball_pos.y < 0 and direction.y < 0) or (ball_pos.y > screen_size.y and direction.y > 0)):
  143. direction.y = -direction.y
  144. Second, the pads: if one of the pads is touched, we need to invert the
  145. direction of the ball on the X axis so it goes back, and define a new
  146. random Y direction using the ``randf()`` function. We also increase its
  147. speed a little.
  148. ::
  149. # Flip, change direction and increase speed when touching pads
  150. if ((left_rect.has_point(ball_pos) and direction.x < 0) or (right_rect.has_point(ball_pos) and direction.x > 0)):
  151. direction.x = -direction.x
  152. direction.y = randf()*2.0 - 1
  153. direction = direction.normalized()
  154. ball_speed *= 1.1
  155. Finally, if the ball went out of the screen, it's game over. That is, we test if
  156. the X position of the ball is less than 0 or greater than the screen
  157. width. If so, the game restarts:
  158. ::
  159. # Check gameover
  160. if (ball_pos.x < 0 or ball_pos.x > screen_size.x):
  161. ball_pos = screen_size*0.5
  162. ball_speed = INITIAL_BALL_SPEED
  163. direction = Vector2(-1, 0)
  164. Once everything is done, the node is updated with the new position of
  165. the ball, which was computed before:
  166. ::
  167. get_node("ball").set_pos(ball_pos)
  168. Next, we allow the pads to move. We only update their position according
  169. to player input. This is done using the Input class:
  170. ::
  171. # Move left pad
  172. var left_pos = get_node("left").get_pos()
  173. if (left_pos.y > 0 and Input.is_action_pressed("left_move_up")):
  174. left_pos.y += -PAD_SPEED * delta
  175. if (left_pos.y < screen_size.y and Input.is_action_pressed("left_move_down")):
  176. left_pos.y += PAD_SPEED * delta
  177. get_node("left").set_pos(left_pos)
  178. # Move right pad
  179. var right_pos = get_node("right").get_pos()
  180. if (right_pos.y > 0 and Input.is_action_pressed("right_move_up")):
  181. right_pos.y += -PAD_SPEED * delta
  182. if (right_pos.y < screen_size.y and Input.is_action_pressed("right_move_down")):
  183. right_pos.y += PAD_SPEED * delta
  184. get_node("right").set_pos(right_pos)
  185. We use the four actions previously defined in the Input actions setup
  186. section above. When the player activates the respective key, the
  187. corresponding action is triggered. As soon as this happens, we simply
  188. compute a new position for the pad in the desired direction and apply it
  189. to the node.
  190. That's it! A simple Pong was written with a few lines of code.