simple_2d_game.rst 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. .. _doc_simple_2d_game:
  2. Simple 2D game
  3. ==============
  4. Pong
  5. ~~~~
  6. In this simple tutorial, a basic game of Pong will be created. There are
  7. plenty of more complex examples in the demos included with the engine,
  8. but this should get one introduced to basic functionality for 2D Games.
  9. Run 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 initalize our two member variables.
  84. ::
  85. extends Node2D
  86. # Member variables
  87. var screen_size
  88. var pad_size
  89. func _ready():
  90. screen_size = get_viewport_rect().size
  91. pad_size = get_node("left").get_texture().get_size()
  92. set_process(true)
  93. We initialize the ``pad_size`` variable by getting one of the pads nodes
  94. (the left one here), and obtain its texture size. The ``screen_size`` is
  95. initialized using the ``get_viewport_rect()`` which returns a Rect
  96. object corresponding to the game window, and we store its size.
  97. Now, we need to add some other members to our script in order to make
  98. our ball move.
  99. ::
  100. extends Node2D
  101. # Member variables
  102. var screen_size
  103. var pad_size
  104. # Constant for pad speed (in pixels/second)
  105. const INITIAL_BALL_SPEED = 80
  106. # Speed of the ball (also in pixels/second)
  107. var ball_speed = INITIAL_BALL_SPEED
  108. # Constant for pads speed
  109. const PAD_SPEED = 150
  110. func _ready():
  111. screen_size = get_viewport_rect().size
  112. pad_size = get_node("left").get_texture().get_size()
  113. set_process(true)
  114. Finally, the ``_process()`` function. All the code below is contained by
  115. this function.
  116. We have to init some useful values for computation. The first one is the
  117. ball position (from the node), the second one is the rectangle
  118. (``Rect2``) for each pad. These rectangles will be used for collisions
  119. tests between the ball and the pads. Sprites center their textures by
  120. default, so a small adjustment of ``pad_size / 2`` must be added.
  121. ::
  122. func _process(delta):
  123. var ball_pos = get_node("ball").get_pos()
  124. var left_rect = Rect2( get_node("left").get_pos() - pad_size*0.5, pad_size )
  125. var right_rect = Rect2( get_node("right").get_pos() - pad_size*0.5, pad_size )
  126. Now, let's add some movement to the ball in the ``_process()`` function.
  127. Since the ball position is stored in the ``ball_pos`` variable,
  128. integrating it is simple:
  129. ::
  130. # Integrate new ball postion
  131. ball_pos += direction * ball_speed * delta
  132. This code line is called at each iteration of the ``_process()``
  133. function. That means the ball position will be updated at each new
  134. frame.
  135. Then, now that the ball has a new position, we need to test if it
  136. collides with anything, that is the window borders and the pads. First,
  137. the floor and the roof:
  138. ::
  139. # Flip when touching roof or floor
  140. if ((ball_pos.y < 0 and direction.y < 0) or (ball_pos.y > screen_size.y and direction.y > 0)):
  141. direction.y = -direction.y
  142. Second, the pads: if one of the pads was touched, we need to invert the
  143. direction of the ball on the X axis so it goes back, and define a new
  144. random Y direction using ``randf()`` function. We also increase its
  145. speed a little.
  146. ::
  147. # Flip, change direction and increase speed when touching pads
  148. if ((left_rect.has_point(ball_pos) and direction.x < 0) or (right_rect.has_point(ball_pos) and direction.x > 0)):
  149. direction.x = -direction.x
  150. direction.y = randf()*2.0 - 1
  151. direction = direction.normalized()
  152. ball_speed *= 1.1
  153. If the ball went out of the screen, it's game over. That is, we test if
  154. the X position of the ball is less than 0 or greater than the screen
  155. width. If so, the game then restarts:
  156. ::
  157. # Check gameover
  158. if (ball_pos.x < 0 or ball_pos.x > screen_size.x):
  159. ball_pos = screen_size*0.5
  160. ball_speed = INITIAL_BALL_SPEED
  161. direction = Vector2(-1, 0)
  162. Once everything was done with the ball, the node is updated with the new
  163. position which was computed before:
  164. ::
  165. get_node("ball").set_pos(ball_pos)
  166. Pads movement: we only update the pads according to player input. This
  167. is done using the Input class:
  168. ::
  169. # Move left pad
  170. var left_pos = get_node("left").get_pos()
  171. if (left_pos.y > 0 and Input.is_action_pressed("left_move_up")):
  172. left_pos.y += -PAD_SPEED * delta
  173. if (left_pos.y < screen_size.y and Input.is_action_pressed("left_move_down")):
  174. left_pos.y += PAD_SPEED * delta
  175. get_node("left").set_pos(left_pos)
  176. # Move right pad
  177. var right_pos = get_node("right").get_pos()
  178. if (right_pos.y > 0 and Input.is_action_pressed("right_move_up")):
  179. right_pos.y += -PAD_SPEED * delta
  180. if (right_pos.y < screen_size.y and Input.is_action_pressed("right_move_down")):
  181. right_pos.y += PAD_SPEED * delta
  182. get_node("right").set_pos(right_pos)
  183. We use the 4 actions previously defined in the Input actions setup
  184. section. When the player activates the according key, the corresponding
  185. action is triggered. When the action is triggered, we simply compute a
  186. new position for the pad in the wished direction. Finally, we set this
  187. new position to the node.
  188. And that's it! A simple Pong was written with a few lines of code.