09.adding_animations.rst 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. .. _doc_first_3d_game_character_animation:
  2. Character animation
  3. ===================
  4. In this final lesson, we'll use Godot's built-in animation tools to make our
  5. characters float and flap. You'll learn to design animations in the editor and
  6. use code to make your game feel alive.
  7. |image0|
  8. We'll start with an introduction to using the animation editor.
  9. Using the animation editor
  10. --------------------------
  11. The engine comes with tools to author animations in the editor. You can then use
  12. the code to play and control them at runtime.
  13. Open the player scene, select the player node, and add an *AnimationPlayer* node.
  14. The *Animation* dock appears in the bottom panel.
  15. |image1|
  16. It features a toolbar and the animation drop-down menu at the top, a track
  17. editor in the middle that's currently empty, and filter, snap, and zoom options
  18. at the bottom.
  19. Let's create an animation. Click on *Animation -> New*.
  20. |image2|
  21. Name the animation "float".
  22. |image3|
  23. Once you created the animation, the timeline appears with numbers representing
  24. time in seconds.
  25. |image4|
  26. We want the animation to start playback automatically at the start of the game.
  27. Also, it should loop.
  28. To do so, you can click the button with an "A+" icon in the animation toolbar
  29. and the looping arrows, respectively.
  30. |image5|
  31. You can also pin the animation editor by clicking the pin icon in the top-right.
  32. This prevents it from folding when you click on the viewport and deselect the
  33. nodes.
  34. |image6|
  35. Set the animation duration to ``1.2`` seconds in the top-right of the dock.
  36. |image7|
  37. You should see the gray ribbon widen a bit. It shows you the start and end of
  38. your animation and the vertical blue line is your time cursor.
  39. |image8|
  40. You can click and drag the slider in the bottom-right to zoom in and out of the
  41. timeline.
  42. |image9|
  43. The float animation
  44. -------------------
  45. With the animation player node, you can animate most properties on as many nodes
  46. as you need. Notice the key icon next to properties in the *Inspector*. You can
  47. click any of them to create a keyframe, a time and value pair for the
  48. corresponding property. The keyframe gets inserted where your time cursor is in
  49. the timeline.
  50. Let's insert our first keys. Here, we will animate both the translation and the
  51. rotation of the *Character* node.
  52. Select the *Character* and click the key icon next to *Translation* in the
  53. *Inspector*. Do the same for *Rotation Degrees*.
  54. |image10|
  55. Two tracks appear in the editor with a diamond icon representing each keyframe.
  56. |image11|
  57. You can click and drag on the diamonds to move them in time. Move the
  58. translation key to ``0.2`` seconds and the rotation key to ``0.1`` seconds.
  59. |image12|
  60. Move the time cursor to ``0.5`` seconds by clicking and dragging on the gray
  61. timeline. In the *Inspector*, set the *Translation*'s *Y* axis to about
  62. ``0.65`` meters and the *Rotation Degrees*' *X* axis to ``8``.
  63. |image13|
  64. Create a keyframe for both properties and shift the translation key to ``0.7``
  65. seconds by dragging it on the timeline.
  66. |image14|
  67. .. note::
  68. A lecture on the principles of animation is beyond the scope of this
  69. tutorial. Just note that you don't want to time and space everything evenly.
  70. Instead, animators play with timing and spacing, two core animation
  71. principles. You want to offset and contrast in your character's motion to
  72. make them feel alive.
  73. Move the time cursor to the end of the animation, at ``1.2`` seconds. Set the Y
  74. translation to about ``0.35`` and the X rotation to ``-9`` degrees. Once again,
  75. create a key for both properties.
  76. You can preview the result by clicking the play button or pressing :kbd:`Shift + D`.
  77. Click the stop button or press :kbd:`S` to stop playback.
  78. |image15|
  79. You can see that the engine interpolates between your keyframes to produce a
  80. continuous animation. At the moment, though, the motion feels very robotic. This
  81. is because the default interpolation is linear, causing constant transitions,
  82. unlike how living things move in the real world.
  83. We can control the transition between keyframes using easing curves.
  84. Click and drag around the first two keys in the timeline to box select them.
  85. |image16|
  86. You can edit the properties of both keys simultaneously in the *Inspector*,
  87. where you can see an *Easing* property.
  88. |image17|
  89. Click and drag on the curve, pulling it towards the left. This will make it
  90. ease-out, that is to say, transition fast initially and slow down as the time
  91. cursor reaches the next keyframe.
  92. |image18|
  93. Play the animation again to see the difference. The first half should already
  94. feel a bit bouncier.
  95. Apply an ease-out to the second keyframe in the rotation track.
  96. |image19|
  97. Do the opposite for the second translation keyframe, dragging it to the right.
  98. |image20|
  99. Your animation should look something like this.
  100. |image21|
  101. .. note::
  102. Animations update the properties of the animated nodes every frame,
  103. overriding initial values. If we directly animated the *Player* node, it
  104. would prevent us from moving it in code. This is where the *Pivot* node
  105. comes in handy: even though we animated the *Character*, we can still move
  106. and rotate the *Pivot* and layer changes on top of the animation in a
  107. script.
  108. If you play the game, the player's creature will now float!
  109. If the creature is a little too close to the floor, you can move the *Pivot* up
  110. to offset it.
  111. Controlling the animation in code
  112. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  113. We can use code to control the animation playback based on the player's input.
  114. Let's change the animation speed when the character is moving.
  115. Open the *Player*'s script by clicking the script icon next to it.
  116. |image22|
  117. In ``_physics_process()``, after the line where we check the ``direction``
  118. vector, add the following code.
  119. .. tabs::
  120. .. code-tab:: gdscript GDScript
  121. func _physics_process(delta):
  122. #...
  123. #if direction != Vector3.ZERO:
  124. #...
  125. $AnimationPlayer.playback_speed = 4
  126. else:
  127. $AnimationPlayer.playback_speed = 1
  128. .. code-tab:: csharp
  129. public override void _PhysicsProcess(float delta)
  130. {
  131. // ...
  132. if (direction != Vector3.Zero)
  133. {
  134. // ...
  135. GetNode<AnimationPlayer>("AnimationPlayer").PlaybackSpeed = 4;
  136. }
  137. else
  138. {
  139. GetNode<AnimationPlayer>("AnimationPlayer").PlaybackSpeed = 1;
  140. }
  141. }
  142. This code makes it so when the player moves, we multiply the playback speed by
  143. ``4``. When they stop, we reset it to normal.
  144. We mentioned that the pivot could layer transforms on top of the animation. We
  145. can make the character arc when jumping using the following line of code. Add it
  146. at the end of ``_physics_process()``.
  147. .. tabs::
  148. .. code-tab:: gdscript GDScript
  149. func _physics_process(delta):
  150. #...
  151. $Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse
  152. .. code-tab:: csharp
  153. public override void _PhysicsProcess(float delta)
  154. {
  155. // ...
  156. var pivot = GetNode<Spatial>("Pivot");
  157. pivot.Rotation = new Vector3(Mathf.Pi / 6f * _velocity.y / JumpImpulse, pivot.Rotation.y, pivot.Rotation.z);
  158. }
  159. Animating the mobs
  160. ------------------
  161. Here's another nice trick with animations in Godot: as long as you use a similar
  162. node structure, you can copy them to different scenes.
  163. For example, both the *Mob* and the *Player* scenes have a *Pivot* and a
  164. *Character* node, so we can reuse animations between them.
  165. Open the *Player* scene, select the animation player node and open the "float" animation.
  166. Next, click on **Animation > Copy**. Then open ``Mob.tscn`` and open its animation
  167. player. Click **Animation > Paste**. That's it; all monsters will now play the float
  168. animation.
  169. We can change the playback speed based on the creature's ``random_speed``. Open
  170. the *Mob*'s script and at the end of the ``initialize()`` function, add the
  171. following line.
  172. .. tabs::
  173. .. code-tab:: gdscript GDScript
  174. func initialize(start_position, player_position):
  175. #...
  176. $AnimationPlayer.playback_speed = random_speed / min_speed
  177. .. code-tab:: csharp
  178. public void Initialize(Vector3 startPosition, Vector3 playerPosition)
  179. {
  180. // ...
  181. GetNode<AnimationPlayer>("AnimationPlayer").PlaybackSpeed = randomSpeed / MinSpeed;
  182. }
  183. And with that, you finished coding your first complete 3D game.
  184. **Congratulations**!
  185. In the next part, we'll quickly recap what you learned and give you some links
  186. to keep learning more. But for now, here are the complete ``Player.gd`` and
  187. ``Mob.gd`` so you can check your code against them.
  188. Here's the *Player* script.
  189. .. tabs::
  190. .. code-tab:: gdscript GDScript
  191. extends KinematicBody
  192. # Emitted when the player was hit by a mob.
  193. signal hit
  194. # How fast the player moves in meters per second.
  195. export var speed = 14
  196. # The downward acceleration when in the air, in meters per second per second.
  197. export var fall_acceleration = 75
  198. # Vertical impulse applied to the character upon jumping in meters per second.
  199. export var jump_impulse = 20
  200. # Vertical impulse applied to the character upon bouncing over a mob in meters per second.
  201. export var bounce_impulse = 16
  202. var velocity = Vector3.ZERO
  203. func _physics_process(delta):
  204. var direction = Vector3.ZERO
  205. if Input.is_action_pressed("move_right"):
  206. direction.x += 1
  207. if Input.is_action_pressed("move_left"):
  208. direction.x -= 1
  209. if Input.is_action_pressed("move_back"):
  210. direction.z += 1
  211. if Input.is_action_pressed("move_forward"):
  212. direction.z -= 1
  213. if direction != Vector3.ZERO:
  214. direction = direction.normalized()
  215. $Pivot.look_at(translation + direction, Vector3.UP)
  216. $AnimationPlayer.playback_speed = 4
  217. else:
  218. $AnimationPlayer.playback_speed = 1
  219. velocity.x = direction.x * speed
  220. velocity.z = direction.z * speed
  221. # Jumping
  222. if is_on_floor() and Input.is_action_just_pressed("jump"):
  223. velocity.y += jump_impulse
  224. velocity.y -= fall_acceleration * delta
  225. velocity = move_and_slide(velocity, Vector3.UP)
  226. for index in range(get_slide_count()):
  227. var collision = get_slide_collision(index)
  228. if collision.collider.is_in_group("mob"):
  229. var mob = collision.collider
  230. if Vector3.UP.dot(collision.normal) > 0.1:
  231. mob.squash()
  232. velocity.y = bounce_impulse
  233. $Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse
  234. func die():
  235. emit_signal("hit")
  236. queue_free()
  237. func _on_MobDetector_body_entered(_body):
  238. die()
  239. .. code-tab:: csharp
  240. public class Player : KinematicBody
  241. {
  242. // Emitted when the player was hit by a mob.
  243. [Signal]
  244. public delegate void Hit();
  245. // How fast the player moves in meters per second.
  246. [Export]
  247. public int Speed = 14;
  248. // The downward acceleration when in the air, in meters per second squared.
  249. [Export]
  250. public int FallAcceleration = 75;
  251. // Vertical impulse applied to the character upon jumping in meters per second.
  252. [Export]
  253. public int JumpImpulse = 20;
  254. // Vertical impulse applied to the character upon bouncing over a mob in meters per second.
  255. [Export]
  256. public int BounceImpulse = 16;
  257. private Vector3 _velocity = Vector3.Zero;
  258. public override void _PhysicsProcess(float delta)
  259. {
  260. var direction = Vector3.Zero;
  261. if (Input.IsActionPressed("move_right"))
  262. {
  263. direction.x += 1f;
  264. }
  265. if (Input.IsActionPressed("move_left"))
  266. {
  267. direction.x -= 1f;
  268. }
  269. if (Input.IsActionPressed("move_back"))
  270. {
  271. direction.z += 1f;
  272. }
  273. if (Input.IsActionPressed("move_forward"))
  274. {
  275. direction.z -= 1f;
  276. }
  277. if (direction != Vector3.Zero)
  278. {
  279. direction = direction.Normalized();
  280. GetNode<Spatial>("Pivot").LookAt(Translation + direction, Vector3.Up);
  281. GetNode<AnimationPlayer>("AnimationPlayer").PlaybackSpeed = 4;
  282. }
  283. else
  284. {
  285. GetNode<AnimationPlayer>("AnimationPlayer").PlaybackSpeed = 1;
  286. }
  287. _velocity.x = direction.x * Speed;
  288. _velocity.z = direction.z * Speed;
  289. // Jumping.
  290. if (IsOnFloor() && Input.IsActionJustPressed("jump"))
  291. {
  292. _velocity.y += JumpImpulse;
  293. }
  294. _velocity.y -= FallAcceleration * delta;
  295. _velocity = MoveAndSlide(_velocity, Vector3.Up);
  296. for (int index = 0; index < GetSlideCount(); index++)
  297. {
  298. KinematicCollision collision = GetSlideCollision(index);
  299. if (collision.Collider is Mob mob && mob.IsInGroup("mob"))
  300. {
  301. if (Vector3.Up.Dot(collision.Normal) > 0.1f)
  302. {
  303. mob.Squash();
  304. _velocity.y = BounceImpulse;
  305. }
  306. }
  307. }
  308. var pivot = GetNode<Spatial>("Pivot");
  309. pivot.Rotation = new Vector3(Mathf.Pi / 6f * _velocity.y / JumpImpulse, pivot.Rotation.y, pivot.Rotation.z);
  310. }
  311. private void Die()
  312. {
  313. EmitSignal(nameof(Hit));
  314. QueueFree();
  315. }
  316. public void OnMobDetectorBodyEntered(Node body)
  317. {
  318. Die();
  319. }
  320. }
  321. And the *Mob*'s script.
  322. .. tabs::
  323. .. code-tab:: gdscript GDScript
  324. extends KinematicBody
  325. # Emitted when the player jumped on the mob.
  326. signal squashed
  327. # Minimum speed of the mob in meters per second.
  328. export var min_speed = 10
  329. # Maximum speed of the mob in meters per second.
  330. export var max_speed = 18
  331. var velocity = Vector3.ZERO
  332. func _physics_process(_delta):
  333. move_and_slide(velocity)
  334. func initialize(start_position, player_position):
  335. look_at_from_position(start_position, player_position, Vector3.UP)
  336. rotate_y(rand_range(-PI / 4, PI / 4))
  337. var random_speed = rand_range(min_speed, max_speed)
  338. velocity = Vector3.FORWARD * random_speed
  339. velocity = velocity.rotated(Vector3.UP, rotation.y)
  340. $AnimationPlayer.playback_speed = random_speed / min_speed
  341. func squash():
  342. emit_signal("squashed")
  343. queue_free()
  344. func _on_VisibilityNotifier_screen_exited():
  345. queue_free()
  346. .. code-tab:: csharp
  347. public class Mob : KinematicBody
  348. {
  349. // Emitted when the played jumped on the mob.
  350. [Signal]
  351. public delegate void Squashed();
  352. // Minimum speed of the mob in meters per second
  353. [Export]
  354. public int MinSpeed = 10;
  355. // Maximum speed of the mob in meters per second
  356. [Export]
  357. public int MaxSpeed = 18;
  358. private Vector3 _velocity = Vector3.Zero;
  359. public override void _PhysicsProcess(float delta)
  360. {
  361. MoveAndSlide(_velocity);
  362. }
  363. public void Initialize(Vector3 startPosition, Vector3 playerPosition)
  364. {
  365. LookAtFromPosition(startPosition, playerPosition, Vector3.Up);
  366. RotateY((float)GD.RandRange(-Mathf.Pi / 4.0, Mathf.Pi / 4.0));
  367. float randomSpeed = (float)GD.RandRange(MinSpeed, MaxSpeed);
  368. _velocity = Vector3.Forward * randomSpeed;
  369. _velocity = _velocity.Rotated(Vector3.Up, Rotation.y);
  370. GetNode<AnimationPlayer>("AnimationPlayer").PlaybackSpeed = randomSpeed / MinSpeed;
  371. }
  372. public void Squash()
  373. {
  374. EmitSignal(nameof(Squashed));
  375. QueueFree();
  376. }
  377. public void OnVisibilityNotifierScreenExited()
  378. {
  379. QueueFree();
  380. }
  381. }
  382. .. |image0| image:: img/squash-the-creeps-final.gif
  383. .. |image1| image:: img/09.adding_animations/01.animation_player_dock.png
  384. .. |image2| image:: img/09.adding_animations/02.new_animation.png
  385. .. |image3| image:: img/09.adding_animations/03.float_name.png
  386. .. |image4| image:: img/09.adding_animations/03.timeline.png
  387. .. |image5| image:: img/09.adding_animations/04.autoplay_and_loop.png
  388. .. |image6| image:: img/09.adding_animations/05.pin_icon.png
  389. .. |image7| image:: img/09.adding_animations/06.animation_duration.png
  390. .. |image8| image:: img/09.adding_animations/07.editable_timeline.png
  391. .. |image9| image:: img/09.adding_animations/08.zoom_slider.png
  392. .. |image10| image:: img/09.adding_animations/09.creating_first_keyframe.png
  393. .. |image11| image:: img/09.adding_animations/10.initial_keys.png
  394. .. |image12| image:: img/09.adding_animations/11.moving_keys.png
  395. .. |image13| image:: img/09.adding_animations/12.second_keys_values.png
  396. .. |image14| image:: img/09.adding_animations/13.second_keys.png
  397. .. |image15| image:: img/09.adding_animations/14.play_button.png
  398. .. |image16| image:: img/09.adding_animations/15.box_select.png
  399. .. |image17| image:: img/09.adding_animations/16.easing_property.png
  400. .. |image18| image:: img/09.adding_animations/17.ease_out.png
  401. .. |image19| image:: img/09.adding_animations/18.ease_out_second_rotation_key.png
  402. .. |image20| image:: img/09.adding_animations/19.ease_in_second_translation_key.png
  403. .. |image21| image:: img/09.adding_animations/20.float_animation.gif
  404. .. |image22| image:: img/09.adding_animations/21.script_icon.png