diff --git a/getting_started/first_3d_game/09.adding_animations.rst b/getting_started/first_3d_game/09.adding_animations.rst new file mode 100644 index 000000000..0d00e9a17 --- /dev/null +++ b/getting_started/first_3d_game/09.adding_animations.rst @@ -0,0 +1,373 @@ +Character animation +=================== + +In this final lesson, we'll use Godot's built-in animation tools to make our +characters float and flap. You'll learn to design animations in the editor and +use code to make your game feel alive. + +|image0| + +We'll start with an introduction to using the animation editor. + +Using the animation editor +-------------------------- + +The engine comes with tools to author animations in the editor. You can then use +the code to play and control them at runtime. + +Open the player scene, select the player node, and add an animation player node. + +The *Animation* dock appears in the bottom panel. + +|image1| + +It features a toolbar and the animation drop-down menu at the top, a track +editor in the middle that's currently empty, and filter, snap, and zoom options +at the bottom. + +Let's create an animation. Click on *Animation -> New*. + +|image2| + +Name the animation "float". + +|image3| + +Once you created the animation, the timeline appears with numbers representing +time in seconds. + +|image4| + +We want the animation to start playback automatically at the start of the game. +Also, it should loop. + +To do so, you can click the button with an "A+" icon in the animation toolbar +and the looping arrows, respectively. + +|image5| + +You can also pin the animation editor by clicking the pin icon in the top-right. +This prevents it from folding when you click on the viewport and deselect the +nodes. + +|image6| + +Set the animation duration to ``1.2`` seconds in the top-right of the dock. + +|image7| + +You should see the gray ribbon widen a bit. It shows you the start and end of +your animation and the vertical blue line is your time cursor. + +|image8| + +You can click and drag the slider in the bottom-right to zoom in and out of the +timeline. + +|image9| + +The float animation +------------------- + +With the animation player node, you can animate most properties on as many nodes +as you need. Notice the key icon next to properties in the *Inspector*. You can +click any of them to create a keyframe, a time and value pair for the +corresponding property. The keyframe gets inserted where your time cursor is in +the timeline. + +Let's insert our first keys. Here, we will animate both the translation and the +rotation of the *Character* node. + +Select the *Character* and click the key icon next to *Translation* in the +*Inspector*. Do the same for *Rotation Degrees*. + +|image10| + +Two tracks appear in the editor with a diamond icon representing each keyframe. + +|image11| + +You can click and drag on the diamonds to move them in time. Move the +translation key to ``0.1`` seconds and the rotation key to ``0.2`` seconds. + +|image12| + +Move the time cursor to ``0.5`` seconds by clicking and dragging on the gray +timeline. In the *Inspector*, set the *Translation*'s *Y* axis to about +``0.65`` meters and the *Rotation Degrees*' *X* axis to ``8``. + +|image13| + +Create a keyframe for both properties and shift the translation key to ``0.7`` +seconds by dragging it on the timeline. + +|image14| + +.. note:: + + A lecture on the principles of animation is beyond the scope of this + tutorial. Just note that you don't want to time and space everything evenly. + Instead, animators play with timing and spacing, two core animation + principles. You want to offset and contrast in your character's motion to + make them feel alive. + +Move the time cursor to the end of the animation, at ``1.2`` seconds. Set the Y +translation to about ``0.35`` and the X rotation to ``-9`` degrees. Once again, +create a key for both properties. + +You can preview the result by clicking the play button or pressing Shift +D. Click the stop button or press S to stop playback. + +|image15| + +You can see that the engine interpolates between your keyframes to produce a +continuous animation. At the moment, though, the motion feels very robotic. This +is because the default interpolation is linear, causing constant transitions, +unlike how living things move in the real world. + +We can control the transition between keyframes using easing curves. + +Click and drag around the first two keys in the timeline to box select them. + +|image16| + +You can edit the properties of both keys simultaneously in the *Inspector*, +where you can see an *Easing* property. + +|image17| + +Click and drag on the curve, pulling it towards the left. This will make it +ease-out, that is to say, transition fast initially and slow down as the time +cursor reaches the next keyframe. + +|image18| + +Play the animation again to see the difference. The first half should already +feel a bit bouncier. + +Apply an ease-out to the second keyframe in the rotation track. + +|image19| + +Do the opposite for the second translation keyframe, dragging it to the right. + +|image20| + +Your animation should look something like this. + +|image21| + +.. note:: + + Animations update the properties of the animated nodes every frame, + overriding initial values. If we directly animated the *Player* node, it + would prevent us from moving it in code. This is where the *Pivot* node + comes in handy: even though we animated the *Character*, we can still move + and rotate the *Pivot* and layer changes on top of the animation in a + script. + +If you play the game, the player's creature will now float! + +If the creature is a little too close to the floor, you can move the *Pivot* up +to offset it. + +Controlling the animation in code +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We can use code to control the animation playback based on the player's input. +Let's change the animation speed when the character is moving. + +Open the *Player*'s script by clicking the script icon next to it. + +|image22| + +In ``_physics_process()``, after the line where we check the ``direction`` +vector, add the following code. + +:: + + func _physics_process(delta): + #... + #if direction.length() > 0: + #... + $AnimationPlayer.playback_speed = 4 + else: + $AnimationPlayer.playback_speed = 1 + +This code makes it so when the player moves, we multiply the playback speed by +``4``. When they stop, we reset it to normal. + +We mentioned that the pivot could layer transforms on top of the animation. We +can make the character arc when jumping using the following line of code. Add it +at the end of ``_physics_process()``. + +:: + + func _physics_process(delta): + #... + $Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse + +Animating the mobs +------------------ + +Here's another nice trick with animations in Godot: as long as you use a similar +node structure, you can copy them to different scenes. + +For example, both the *Mob* and the *Player* scenes have a *Pivot* and a +*Character* node, so we can reuse animations between them. + +We're going to duplicate the animation using a feature called "merge from +scene". + +Open the *Mob* scene, right-click on the *Mob* node and select *Merge From +Scene*. + +|image23| + +Double-click ``Player.tscn`` to open it and import the *AnimationPlayer*. That's +it; all monsters will now play the float animation. + +We can change the playback speed based on the creature's ``random_speed``. Open +the *Mob*'s script and at the end of the ``initialize()`` function, add the +following line. + +:: + + func initialize(start_position, player_position): + #... + $AnimationPlayer.playback_speed = random_speed / min_speed + +And with that, you finished coding your first complete 3D game. + +**Congratulations**! + +In the next part, we'll quickly recap what you learned and give you some links +to keep learning more. But for now, here are the complete ``Player.gd`` and +``Mob.gd`` so you can check your code against them. + +Here's the *Player* script. + +:: + + extends KinematicBody + + # Emitted when the player was hit by a mob. + signal hit + + # How fast the player moves in meters per second. + export var speed = 14 + # The downward acceleration when in the air, in meters per second per second. + export var fall_acceleration = 75 + # Vertical impulse applied to the character upon jumping in meters per second. + export var jump_impulse = 20 + # Vertical impulse applied to the character upon bouncing over a mob in meters per second. + export var bounce_impulse = 16 + + var velocity = Vector3.ZERO + + + func _physics_process(delta): + var direction = Vector3.ZERO + + if Input.is_action_pressed("move_right"): + direction.x += 1 + if Input.is_action_pressed("move_left"): + direction.x -= 1 + if Input.is_action_pressed("move_down"): + direction.z += 1 + if Input.is_action_pressed("move_up"): + direction.z -= 1 + + if direction.length() > 0: + direction = direction.normalized() + $Pivot.look_at(translation + direction, Vector3.UP) + $AnimationPlayer.playback_speed = 4 + else: + $AnimationPlayer.playback_speed = 1 + + velocity.x = direction.x * speed + velocity.z = direction.z * speed + + # Jumping + if is_on_floor() and Input.is_action_just_pressed("jump"): + velocity.y += jump_impulse + + velocity.y -= fall_acceleration * delta + velocity = move_and_slide(velocity, Vector3.UP) + + for index in range(get_slide_count()): + var collision = get_slide_collision(index) + if collision.collider.is_in_group("mob"): + var mob = collision.collider + if Vector3.UP.dot(collision.normal) > 0.1: + mob.squash() + velocity.y = bounce_impulse + + $Pivot.rotation.x = PI / 6 * velocity.y / jump_impulse + + + func die(): + emit_signal("hit") + queue_free() + + + func _on_MobDetector_body_entered(_body): + die() + +And the *Mob*'s script. + +:: + + extends KinematicBody + + # Minimum speed of the mob in meters per second. + export var min_speed = 10 + # Maximum speed of the mob in meters per second. + export var max_speed = 18 + + var velocity = Vector3.ZERO + + + func _physics_process(_delta): + move_and_slide(velocity) + + + func initialize(start_position, player_position): + translation = start_position + look_at(player_position, Vector3.UP) + rotate_y(rand_range(-PI / 4, PI / 4)) + + var random_speed = rand_range(min_speed, max_speed) + velocity = Vector3.FORWARD * random_speed + velocity = velocity.rotated(Vector3.UP, rotation.y) + + $AnimationPlayer.playback_speed = random_speed / min_speed + + + func _on_VisibilityNotifier_screen_exited(): + queue_free() + +.. |image0| image:: img/squash-the-creeps-final.gif +.. |image1| image:: img/09.adding_animations/01.animation_player_dock.png +.. |image2| image:: img/09.adding_animations/02.new_animation.png +.. |image3| image:: img/09.adding_animations/03.float_name.png +.. |image4| image:: img/09.adding_animations/03.timeline.png +.. |image5| image:: img/09.adding_animations/04.autoplay_and_loop.png +.. |image6| image:: img/09.adding_animations/05.pin_icon.png +.. |image7| image:: img/09.adding_animations/06.animation_duration.png +.. |image8| image:: img/09.adding_animations/07.editable_timeline.png +.. |image9| image:: img/09.adding_animations/08.zoom_slider.png +.. |image10| image:: img/09.adding_animations/09.creating_first_keyframe.png +.. |image11| image:: img/09.adding_animations/10.initial_keys.png +.. |image12| image:: img/09.adding_animations/11.moving_keys.png +.. |image13| image:: img/09.adding_animations/12.second_keys_values.png +.. |image14| image:: img/09.adding_animations/13.second_keys.png +.. |image15| image:: img/09.adding_animations/14.play_button.png +.. |image16| image:: img/09.adding_animations/15.box_select.png +.. |image17| image:: img/09.adding_animations/16.easing_property.png +.. |image18| image:: img/09.adding_animations/17.ease_out.png +.. |image19| image:: img/09.adding_animations/18.ease_out_second_rotation_key.png +.. |image20| image:: img/09.adding_animations/19.ease_in_second_translation_key.png +.. |image21| image:: img/09.adding_animations/20.float_animation.gif +.. |image22| image:: img/09.adding_animations/21.script_icon.png +.. |image23| image:: img/09.adding_animations/22.merge_from_scene.png diff --git a/getting_started/first_3d_game/img/09.adding_animations/01.animation_player_dock.png b/getting_started/first_3d_game/img/09.adding_animations/01.animation_player_dock.png new file mode 100644 index 000000000..2a7aa98e9 Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/01.animation_player_dock.png differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/02.new_animation.png b/getting_started/first_3d_game/img/09.adding_animations/02.new_animation.png new file mode 100644 index 000000000..6247212f1 Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/02.new_animation.png differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/03.float_name.png b/getting_started/first_3d_game/img/09.adding_animations/03.float_name.png new file mode 100644 index 000000000..c095ba8c4 Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/03.float_name.png differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/03.timeline.png b/getting_started/first_3d_game/img/09.adding_animations/03.timeline.png new file mode 100644 index 000000000..7184bf394 Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/03.timeline.png differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/04.autoplay_and_loop.png b/getting_started/first_3d_game/img/09.adding_animations/04.autoplay_and_loop.png new file mode 100644 index 000000000..447dba35c Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/04.autoplay_and_loop.png differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/05.pin_icon.png b/getting_started/first_3d_game/img/09.adding_animations/05.pin_icon.png new file mode 100644 index 000000000..a48cd74be Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/05.pin_icon.png differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/06.animation_duration.png b/getting_started/first_3d_game/img/09.adding_animations/06.animation_duration.png new file mode 100644 index 000000000..45e76f69e Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/06.animation_duration.png differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/07.editable_timeline.png b/getting_started/first_3d_game/img/09.adding_animations/07.editable_timeline.png new file mode 100644 index 000000000..57ddb25fb Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/07.editable_timeline.png differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/08.zoom_slider.png b/getting_started/first_3d_game/img/09.adding_animations/08.zoom_slider.png new file mode 100644 index 000000000..b9436f5a6 Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/08.zoom_slider.png differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/09.creating_first_keyframe.png b/getting_started/first_3d_game/img/09.adding_animations/09.creating_first_keyframe.png new file mode 100644 index 000000000..fc7145f4b Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/09.creating_first_keyframe.png differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/10.initial_keys.png b/getting_started/first_3d_game/img/09.adding_animations/10.initial_keys.png new file mode 100644 index 000000000..f4fc31f51 Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/10.initial_keys.png differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/11.moving_keys.png b/getting_started/first_3d_game/img/09.adding_animations/11.moving_keys.png new file mode 100644 index 000000000..d31bbc2d0 Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/11.moving_keys.png differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/12.second_keys_values.png b/getting_started/first_3d_game/img/09.adding_animations/12.second_keys_values.png new file mode 100644 index 000000000..25129dd0c Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/12.second_keys_values.png differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/13.second_keys.png b/getting_started/first_3d_game/img/09.adding_animations/13.second_keys.png new file mode 100644 index 000000000..f4ef71446 Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/13.second_keys.png differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/14.play_button.png b/getting_started/first_3d_game/img/09.adding_animations/14.play_button.png new file mode 100644 index 000000000..2dc4ef0e2 Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/14.play_button.png differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/15.box_select.png b/getting_started/first_3d_game/img/09.adding_animations/15.box_select.png new file mode 100644 index 000000000..c0f528afb Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/15.box_select.png differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/16.easing_property.png b/getting_started/first_3d_game/img/09.adding_animations/16.easing_property.png new file mode 100644 index 000000000..6f78c8542 Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/16.easing_property.png differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/17.ease_out.png b/getting_started/first_3d_game/img/09.adding_animations/17.ease_out.png new file mode 100644 index 000000000..49dc184c0 Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/17.ease_out.png differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/18.ease_out_second_rotation_key.png b/getting_started/first_3d_game/img/09.adding_animations/18.ease_out_second_rotation_key.png new file mode 100644 index 000000000..284eef5d3 Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/18.ease_out_second_rotation_key.png differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/19.ease_in_second_translation_key.png b/getting_started/first_3d_game/img/09.adding_animations/19.ease_in_second_translation_key.png new file mode 100644 index 000000000..72c58e88d Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/19.ease_in_second_translation_key.png differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/20.float_animation.gif b/getting_started/first_3d_game/img/09.adding_animations/20.float_animation.gif new file mode 100644 index 000000000..70cff7f32 Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/20.float_animation.gif differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/21.script_icon.png b/getting_started/first_3d_game/img/09.adding_animations/21.script_icon.png new file mode 100644 index 000000000..46e9f2f57 Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/21.script_icon.png differ diff --git a/getting_started/first_3d_game/img/09.adding_animations/22.merge_from_scene.png b/getting_started/first_3d_game/img/09.adding_animations/22.merge_from_scene.png new file mode 100644 index 000000000..2739a1afc Binary files /dev/null and b/getting_started/first_3d_game/img/09.adding_animations/22.merge_from_scene.png differ diff --git a/getting_started/first_3d_game/index.rst b/getting_started/first_3d_game/index.rst index 2da9e3c21..90555e084 100644 --- a/getting_started/first_3d_game/index.rst +++ b/getting_started/first_3d_game/index.rst @@ -62,5 +62,6 @@ Contents 06.jump_and_squash 07.killing_player 08.score_and_replay + 09.adding_animations .. |image0| image:: img/squash-the-creeps-final.gif