09.adding_animations.rst 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  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 :ref:`AnimationPlayer <class_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've 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 autoplay button (|Autoplay|) 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 position and the
  51. rotation of the ``Character`` node.
  52. Select the ``Character`` and in the *Inspector* expand the *Transform* section. Click the key icon next to *Position*, and *Rotation*.
  53. |image10|
  54. .. image:: img/09.adding_animations/curves.webp
  55. For this tutorial, just create RESET Track(s) which is the default choice
  56. Two tracks appear in the editor with a diamond icon representing each keyframe.
  57. |image11|
  58. You can click and drag on the diamonds to move them in time. Move the
  59. position key to ``0.3`` seconds and the rotation key to ``0.1`` seconds.
  60. |image12|
  61. Move the time cursor to ``0.5`` seconds by clicking and dragging on the gray
  62. timeline.
  63. |timeline_05_click|
  64. In the *Inspector*, set the *Position*'s *Y* axis to ``0.65`` meters and the
  65. *Rotation*' *X* axis to ``8``.
  66. If you don't see the properties in the *Inspector* panel, first click on the
  67. ``Character`` node again in the *Scene* dock.
  68. |image13|
  69. Create a keyframe for both properties
  70. |second_keys_both|
  71. Now, move the position keyframe to ``0.7``
  72. seconds by dragging it on the timeline.
  73. |image14|
  74. .. note::
  75. A lecture on the principles of animation is beyond the scope of this
  76. tutorial. Just note that you don't want to time and space everything evenly.
  77. Instead, animators play with timing and spacing, two core animation
  78. principles. You want to offset and contrast in your character's motion to
  79. make them feel alive.
  80. Move the time cursor to the end of the animation, at ``1.2`` seconds. Set the Y
  81. position to about ``0.35`` and the X rotation to ``-9`` degrees. Once again,
  82. create a key for both properties.
  83. |animation_final_keyframes|
  84. You can preview the result by clicking the play button or pressing :kbd:`Shift + D`.
  85. Click the stop button or press :kbd:`S` to stop playback.
  86. |image15|
  87. You can see that the engine interpolates between your keyframes to produce a
  88. continuous animation. At the moment, though, the motion feels very robotic. This
  89. is because the default interpolation is linear, causing constant transitions,
  90. unlike how living things move in the real world.
  91. We can control the transition between keyframes using easing curves.
  92. Click and drag around the first two keys in the timeline to box select them.
  93. |image16|
  94. You can edit the properties of both keys simultaneously in the *Inspector*,
  95. where you can see an *Easing* property.
  96. |image17|
  97. Click and drag on the curve, pulling it towards the left. This will make it
  98. ease-out, that is to say, transition fast initially and slow down as the time
  99. cursor reaches the next keyframe.
  100. |image18|
  101. Play the animation again to see the difference. The first half should already
  102. feel a bit bouncier.
  103. Apply an ease-out to the second keyframe in the rotation track.
  104. |image19|
  105. Do the opposite for the second position keyframe, dragging it to the right.
  106. |image20|
  107. Your animation should look something like this.
  108. |image21|
  109. .. note::
  110. Animations update the properties of the animated nodes every frame,
  111. overriding initial values. If we directly animated the *Player* node, it
  112. would prevent us from moving it in code. This is where the *Pivot* node
  113. comes in handy: even though we animated the *Character*, we can still move
  114. and rotate the *Pivot* and layer changes on top of the animation in a
  115. script.
  116. If you play the game, the player's creature will now float!
  117. If the creature is a little too close to the floor, you can move the ``Pivot`` up
  118. to offset it.
  119. Controlling the animation in code
  120. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  121. We can use code to control the animation playback based on the player's input.
  122. Let's change the animation speed when the character is moving.
  123. Open the ``Player``'s script by clicking the script icon next to it.
  124. |image22|
  125. In ``_physics_process()``, after the line where we check the ``direction``
  126. vector, add the following code.
  127. .. tabs::
  128. .. code-tab:: gdscript GDScript
  129. func _physics_process(delta):
  130. #...
  131. if direction != Vector3.ZERO:
  132. #...
  133. $AnimationPlayer.speed_scale = 4
  134. else:
  135. $AnimationPlayer.speed_scale = 1
  136. .. code-tab:: csharp
  137. public override void _PhysicsProcess(double delta)
  138. {
  139. // ...
  140. if (direction != Vector3.Zero)
  141. {
  142. // ...
  143. GetNode<AnimationPlayer>("AnimationPlayer").SpeedScale = 4;
  144. }
  145. else
  146. {
  147. GetNode<AnimationPlayer>("AnimationPlayer").SpeedScale = 1;
  148. }
  149. }
  150. This code makes it so when the player moves, we multiply the playback speed by
  151. ``4``. When they stop, we reset it to normal.
  152. We mentioned that the ``Pivot`` could layer transforms on top of the animation. We
  153. can make the character arc when jumping using the following line of code. Add it
  154. at the end of ``_physics_process()``.
  155. .. tabs::
  156. .. code-tab:: gdscript GDScript
  157. func _physics_process(delta):
  158. #...
  159. $Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse
  160. .. code-tab:: csharp
  161. public override void _PhysicsProcess(double delta)
  162. {
  163. // ...
  164. var pivot = GetNode<Node3D>("Pivot");
  165. pivot.Rotation = new Vector3(Mathf.Pi / 6.0f * Velocity.Y / JumpImpulse, pivot.Rotation.Y, pivot.Rotation.Z);
  166. }
  167. Animating the mobs
  168. ------------------
  169. Here's another nice trick with animations in Godot: as long as you use a similar
  170. node structure, you can copy them to different scenes.
  171. For example, both the ``Mob`` and the ``Player`` scenes have a ``Pivot`` and a
  172. ``Character`` node, so we can reuse animations between them.
  173. Open the *Player* scene, select the AnimationPlayer node and then click on
  174. **Animation > Manage Animations...**. Click the *Copy animation to clipboard* button
  175. (two small squares) alongside the *float* animation. Click OK to close the window.
  176. Then open ``mob.tscn``, create an :ref:`AnimationPlayer <class_AnimationPlayer>` child
  177. node and select it. Click **Animation > Manage Animations**, then **Add Library**. You
  178. should see the message "Global library will be created." Leave the text field blank and
  179. click OK. Click the *Paste* icon (clipboard) and it should appear in the window. Click OK
  180. to close the window.
  181. Next, make sure that the autoplay button (|Autoplay|) and the looping
  182. arrows (Animation looping) are also turned on in the animation editor in the bottom panel.
  183. That's it; all monsters will now play the float animation.
  184. We can change the playback speed based on the creature's ``random_speed``. Open
  185. the *Mob*'s script and at the end of the ``initialize()`` function, add the following line.
  186. .. tabs::
  187. .. code-tab:: gdscript GDScript
  188. func initialize(start_position, player_position):
  189. #...
  190. $AnimationPlayer.speed_scale = random_speed / min_speed
  191. .. code-tab:: csharp
  192. public void Initialize(Vector3 startPosition, Vector3 playerPosition)
  193. {
  194. // ...
  195. GetNode<AnimationPlayer>("AnimationPlayer").SpeedScale = randomSpeed / MinSpeed;
  196. }
  197. And with that, you finished coding your first complete 3D game.
  198. **Congratulations**!
  199. In the next part, we'll quickly recap what you learned and give you some links
  200. to keep learning more. But for now, here are the complete ``player.gd`` and
  201. ``mob.gd`` so you can check your code against them.
  202. Here's the *Player* script.
  203. .. tabs::
  204. .. code-tab:: gdscript GDScript
  205. extends CharacterBody3D
  206. signal hit
  207. # How fast the player moves in meters per second.
  208. @export var speed = 14
  209. # The downward acceleration while in the air, in meters per second squared.
  210. @export var fall_acceleration = 75
  211. # Vertical impulse applied to the character upon jumping in meters per second.
  212. @export var jump_impulse = 20
  213. # Vertical impulse applied to the character upon bouncing over a mob
  214. # in meters per second.
  215. @export var bounce_impulse = 16
  216. var target_velocity = Vector3.ZERO
  217. func _physics_process(delta):
  218. # We create a local variable to store the input direction
  219. var direction = Vector3.ZERO
  220. # We check for each move input and update the direction accordingly
  221. if Input.is_action_pressed("move_right"):
  222. direction.x = direction.x + 1
  223. if Input.is_action_pressed("move_left"):
  224. direction.x = direction.x - 1
  225. if Input.is_action_pressed("move_back"):
  226. # Notice how we are working with the vector's x and z axes.
  227. # In 3D, the XZ plane is the ground plane.
  228. direction.z = direction.z + 1
  229. if Input.is_action_pressed("move_forward"):
  230. direction.z = direction.z - 1
  231. # Prevent diagonal movement being very fast
  232. if direction != Vector3.ZERO:
  233. direction = direction.normalized()
  234. $Pivot.look_at(position + direction,Vector3.UP)
  235. $AnimationPlayer.speed_scale = 4
  236. else:
  237. $AnimationPlayer.speed_scale = 1
  238. # Ground Velocity
  239. target_velocity.x = direction.x * speed
  240. target_velocity.z = direction.z * speed
  241. # Vertical Velocity
  242. if not is_on_floor(): # If in the air, fall towards the floor
  243. target_velocity.y = target_velocity.y - (fall_acceleration * delta)
  244. # Jumping.
  245. if is_on_floor() and Input.is_action_just_pressed("jump"):
  246. target_velocity.y = jump_impulse
  247. # Iterate through all collisions that occurred this frame
  248. # in C this would be for(int i = 0; i < collisions.Count; i++)
  249. for index in range(get_slide_collision_count()):
  250. # We get one of the collisions with the player
  251. var collision = get_slide_collision(index)
  252. # If the collision is with ground
  253. if collision.get_collider() == null:
  254. continue
  255. # If the collider is with a mob
  256. if collision.get_collider().is_in_group("mob"):
  257. var mob = collision.get_collider()
  258. # we check that we are hitting it from above.
  259. if Vector3.UP.dot(collision.get_normal()) > 0.1:
  260. # If so, we squash it and bounce.
  261. mob.squash()
  262. target_velocity.y = bounce_impulse
  263. # Prevent further duplicate calls.
  264. break
  265. # Moving the Character
  266. velocity = target_velocity
  267. move_and_slide()
  268. $Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse
  269. # And this function at the bottom.
  270. func die():
  271. hit.emit()
  272. queue_free()
  273. func _on_mob_detector_body_entered(body):
  274. die()
  275. .. code-tab:: csharp
  276. using Godot;
  277. public partial class Player : CharacterBody3D
  278. {
  279. // Emitted when the player was hit by a mob.
  280. [Signal]
  281. public delegate void HitEventHandler();
  282. // How fast the player moves in meters per second.
  283. [Export]
  284. public int Speed { get; set; } = 14;
  285. // The downward acceleration when in the air, in meters per second squared.
  286. [Export]
  287. public int FallAcceleration { get; set; } = 75;
  288. // Vertical impulse applied to the character upon jumping in meters per second.
  289. [Export]
  290. public int JumpImpulse { get; set; } = 20;
  291. // Vertical impulse applied to the character upon bouncing over a mob in meters per second.
  292. [Export]
  293. public int BounceImpulse { get; set; } = 16;
  294. private Vector3 _targetVelocity = Vector3.Zero;
  295. public override void _PhysicsProcess(double delta)
  296. {
  297. // We create a local variable to store the input direction.
  298. var direction = Vector3.Zero;
  299. // We check for each move input and update the direction accordingly.
  300. if (Input.IsActionPressed("move_right"))
  301. {
  302. direction.X += 1.0f;
  303. }
  304. if (Input.IsActionPressed("move_left"))
  305. {
  306. direction.X -= 1.0f;
  307. }
  308. if (Input.IsActionPressed("move_back"))
  309. {
  310. // Notice how we are working with the vector's X and Z axes.
  311. // In 3D, the XZ plane is the ground plane.
  312. direction.Z += 1.0f;
  313. }
  314. if (Input.IsActionPressed("move_forward"))
  315. {
  316. direction.Z -= 1.0f;
  317. }
  318. // Prevent diagonal movement being very fast.
  319. if (direction != Vector3.Zero)
  320. {
  321. direction = direction.Normalized();
  322. GetNode<Node3D>("Pivot").LookAt(Position + direction, Vector3.Up);
  323. GetNode<AnimationPlayer>("AnimationPlayer").PlaybackSpeed = 4;
  324. }
  325. else
  326. {
  327. GetNode<AnimationPlayer>("AnimationPlayer").PlaybackSpeed = 1;
  328. }
  329. // Ground velocity
  330. _targetVelocity.X = direction.X * Speed;
  331. _targetVelocity.Z = direction.Z * Speed;
  332. // Vertical velocity
  333. if (!IsOnFloor())
  334. {
  335. _targetVelocity.Y -= FallAcceleration * (float)delta;
  336. }
  337. // Jumping.
  338. if (IsOnFloor() && Input.IsActionJustPressed("jump"))
  339. {
  340. _targetVelocity.Y += JumpImpulse;
  341. }
  342. // Iterate through all collisions that occurred this frame.
  343. for (int index = 0; index < GetSlideCollisionCount(); index++)
  344. {
  345. // We get one of the collisions with the player.
  346. KinematicCollision3D collision = GetSlideCollision(index);
  347. // If the collision is with a mob.
  348. if (collision.GetCollider() is Mob mob)
  349. {
  350. // We check that we are hitting it from above.
  351. if (Vector3.Up.Dot(collision.GetNormal()) > 0.1f)
  352. {
  353. // If so, we squash it and bounce.
  354. mob.Squash();
  355. _targetVelocity.Y = BounceImpulse;
  356. // Prevent further duplicate calls.
  357. break;
  358. }
  359. }
  360. }
  361. // Moving the character
  362. Velocity = _targetVelocity;
  363. MoveAndSlide();
  364. var pivot = GetNode<Node3D>("Pivot");
  365. pivot.Rotation = new Vector3(Mathf.Pi / 6.0f * Velocity.Y / JumpImpulse, pivot.Rotation.Y, pivot.Rotation.Z);
  366. }
  367. private void Die()
  368. {
  369. EmitSignal(SignalName.Hit);
  370. QueueFree();
  371. }
  372. private void OnMobDetectorBodyEntered(Node body)
  373. {
  374. Die();
  375. }
  376. }
  377. And the *Mob*'s script.
  378. .. tabs::
  379. .. code-tab:: gdscript GDScript
  380. extends CharacterBody3D
  381. # Minimum speed of the mob in meters per second.
  382. @export var min_speed = 10
  383. # Maximum speed of the mob in meters per second.
  384. @export var max_speed = 18
  385. # Emitted when the player jumped on the mob
  386. signal squashed
  387. func _physics_process(_delta):
  388. move_and_slide()
  389. # This function will be called from the Main scene.
  390. func initialize(start_position, player_position):
  391. # We position the mob by placing it at start_position
  392. # and rotate it towards player_position, so it looks at the player.
  393. look_at_from_position(start_position, player_position, Vector3.UP)
  394. # Rotate this mob randomly within range of -45 and +45 degrees,
  395. # so that it doesn't move directly towards the player.
  396. rotate_y(randf_range(-PI / 4, PI / 4))
  397. # We calculate a random speed (integer)
  398. var random_speed = randi_range(min_speed, max_speed)
  399. # We calculate a forward velocity that represents the speed.
  400. velocity = Vector3.FORWARD * random_speed
  401. # We then rotate the velocity vector based on the mob's Y rotation
  402. # in order to move in the direction the mob is looking.
  403. velocity = velocity.rotated(Vector3.UP, rotation.y)
  404. $AnimationPlayer.speed_scale = random_speed / min_speed
  405. func _on_visible_on_screen_notifier_3d_screen_exited():
  406. queue_free()
  407. func squash():
  408. squashed.emit()
  409. queue_free() # Destroy this node
  410. .. code-tab:: csharp
  411. using Godot;
  412. public partial class Mob : CharacterBody3D
  413. {
  414. // Emitted when the played jumped on the mob.
  415. [Signal]
  416. public delegate void SquashedEventHandler();
  417. // Minimum speed of the mob in meters per second
  418. [Export]
  419. public int MinSpeed { get; set; } = 10;
  420. // Maximum speed of the mob in meters per second
  421. [Export]
  422. public int MaxSpeed { get; set; } = 18;
  423. public override void _PhysicsProcess(double delta)
  424. {
  425. MoveAndSlide();
  426. }
  427. // This function will be called from the Main scene.
  428. public void Initialize(Vector3 startPosition, Vector3 playerPosition)
  429. {
  430. // We position the mob by placing it at startPosition
  431. // and rotate it towards playerPosition, so it looks at the player.
  432. LookAtFromPosition(startPosition, playerPosition, Vector3.Up);
  433. // Rotate this mob randomly within range of -45 and +45 degrees,
  434. // so that it doesn't move directly towards the player.
  435. RotateY((float)GD.RandRange(-Mathf.Pi / 4.0, Mathf.Pi / 4.0));
  436. // We calculate a random speed (integer).
  437. int randomSpeed = GD.RandRange(MinSpeed, MaxSpeed);
  438. // We calculate a forward velocity that represents the speed.
  439. Velocity = Vector3.Forward * randomSpeed;
  440. // We then rotate the velocity vector based on the mob's Y rotation
  441. // in order to move in the direction the mob is looking.
  442. Velocity = Velocity.Rotated(Vector3.Up, Rotation.Y);
  443. GetNode<AnimationPlayer>("AnimationPlayer").SpeedScale = randomSpeed / MinSpeed;
  444. }
  445. public void Squash()
  446. {
  447. EmitSignal(SignalName.Squashed);
  448. QueueFree(); // Destroy this node
  449. }
  450. private void OnVisibilityNotifierScreenExited()
  451. {
  452. QueueFree();
  453. }
  454. }
  455. .. |image0| image:: img/squash-the-creeps-final.gif
  456. .. |image1| image:: img/09.adding_animations/animation_player_dock.webp
  457. .. |image2| image:: img/09.adding_animations/02.new_animation.webp
  458. .. |image3| image:: img/09.adding_animations/03.float_name.png
  459. .. |image4| image:: img/09.adding_animations/03.timeline.png
  460. .. |image5| image:: img/09.adding_animations/04.autoplay_and_loop.png
  461. .. |image6| image:: img/09.adding_animations/05.pin_icon.png
  462. .. |image7| image:: img/09.adding_animations/06.animation_duration.webp
  463. .. |image8| image:: img/09.adding_animations/07.editable_timeline.webp
  464. .. |image9| image:: img/09.adding_animations/08.zoom_slider.webp
  465. .. |image10| image:: img/09.adding_animations/09.creating_first_keyframe.webp
  466. .. |image11| image:: img/09.adding_animations/10.initial_keys.webp
  467. .. |image12| image:: img/09.adding_animations/11.moving_keys.webp
  468. .. |image13| image:: img/09.adding_animations/12.second_keys_values.webp
  469. .. |image14| image:: img/09.adding_animations/13.second_keys.webp
  470. .. |image15| image:: img/09.adding_animations/14.play_button.png
  471. .. |image16| image:: img/09.adding_animations/15.box_select.webp
  472. .. |image17| image:: img/09.adding_animations/16.easing_property.png
  473. .. |image18| image:: img/09.adding_animations/17.ease_out.png
  474. .. |image19| image:: img/09.adding_animations/18.ease_out_second_rotation_key.png
  475. .. |image20| image:: img/09.adding_animations/19.ease_in_second_translation_key.png
  476. .. |image21| image:: img/09.adding_animations/20.float_animation.gif
  477. .. |image22| image:: img/09.adding_animations/21.script_icon.png
  478. .. |animation_final_keyframes| image:: img/09.adding_animations/animation_final_keyframes.webp
  479. .. |second_keys_both| image:: img/09.adding_animations/second_keys_both.webp
  480. .. |timeline_05_click| image:: img/09.adding_animations/timeline_05_click.webp
  481. .. |Autoplay| image:: img/09.adding_animations/autoplay_button.webp