Browse Source

Write 3D tutorial part 04.mob_scene.rst

Nathan Lovato 4 years ago
parent
commit
a0622e19d4

+ 231 - 0
getting_started/first_3d_game/04.mob_scene.rst

@@ -0,0 +1,231 @@
+.. _doc_designing_the_mob_scene:
+
+Designing the mob scene
+=======================
+
+In this part, you're going to code the monsters, which we'll call mobs. In the
+next lesson, we'll spawn them randomly around the playable area.
+
+Let's design the monsters themselves in a new scene. The node structure is going
+to be similar to the *Player* scene.
+
+Create a scene with, once again, a *KinematicBody* node as its root. Name it
+*Mob*. Add a *Spatial* node as a child of it, name it *Pivot*. And drag and drop
+the file "mob.glb "from the *FileSystem* dock onto the *Pivot* to add the
+monster's 3D model to the scene. You can rename the newly created *mob* node
+into *Character*.
+
+|image0|
+
+We need a collision shape for our body to work. Right-click on the *Mob* node,
+the scene's root, and click *Add Child Node*.
+
+|image1|
+
+Add a *CollisionShape*.
+
+|image2|
+
+In the *Inspector*, assign a *BoxShape* to the *Shape* property.
+
+|image3|
+
+We should change its size to fit the 3D model better. You can do so
+interactively by clicking and dragging on the orange dots.
+
+The box should touch the floor and be a little thinner than the model. Physics
+engines work in such a way that if the player's sphere touches even the box's
+corner, a collision will occur. If the box is a little too big compared to the
+3D model, you may die at a distance from the monster, and the game will feel
+unfair to the players.
+
+|image4|
+
+Notice that my box is taller than the monster. It is okay in this game because
+we're looking at the scene from above and using a fixed perspective. Collision
+shapes don't have to match the model exactly. It's the way the game feels when
+you test it that should dictate their form and size.
+
+Removing monsters off-screen
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We're going to spawn monsters at regular time intervals in the game level. If
+we're not careful, their count could increase to infinity, and we don't want
+that. Each mob instance has both a memory and a processing cost, and we don't
+want to pay for it when the mob's outside the screen.
+
+Once a monster leaves the screen, we don't need it anymore, so we can delete it.
+Godot has a node that detects when objects leave the screen,
+*VisibilityNotifier*, and we're going to use it to destroy our mobs.
+
+.. note::
+
+    When you keep instancing an object in games, there's a technique you can
+    use to avoid the cost of creating and destroying instances all the time
+    called pooling. It consists of pre-creating an array of objects and reusing
+    them over and over.
+
+    When working with GDScript, you don't need to worry about this. The main
+    reason to use pools is to avoid freezes with garbage-collected languages
+    like C# or Lua. GDScript uses a different technique to manage memory,
+    reference counting, which doesn't have that caveat. You can learn more
+    about that here :ref:`doc_gdscript_basics_memory_management`.
+
+Select the *Mob* node and add a *VisibilityNotifier* as a child of it. Another
+box, pink this time, appears. When this box completely leaves the screen, the
+node will emit a signal.
+
+|image5|
+
+Resize it using the orange dots until it covers the entire 3D model.
+
+|image6|
+
+Coding the mob's movement
+-------------------------
+
+Let's implement the monster's motion. We're going to do this in two steps.
+First, we'll write a script on the *Mob* that defines a function to initialize
+the monster. We'll then code the randomized spawn mechanism in the *Main* scene
+and call the function from there.
+
+Attach a script to the *Mob*.
+
+|image7|
+
+Here's the movement code to start with. We define two properties, "min_speed "
+and "max_speed ", to define a random speed range. We then define and initialize
+the "velocity ".
+
+::
+
+   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)
+
+Similarly to the player, we move the mob every frame by calling
+``KinematicBody``\ ’s ``move_and_slide()`` method. This time, we don't update
+the "velocity "every frame: we want the monster to move at a constant speed
+and leave the screen, even if it were to hit an obstacle.
+
+We need to define another function to calculate the start velocity. This
+function will turn the monster towards the player and randomize both its angle
+of motion and its velocity.
+
+The function will take a "start_position ", the mob's spawn position, and the
+"player_position "as its arguments.
+
+We first position the mob at "start_position ". Then, we turn it towards the
+player using the "look_at() "method and randomize the angle by rotating a
+random amount around the Y axis. Below, ``rand_range()`` outputs a random value
+between ``-PI / 4`` radians and ``PI / 4`` radians.
+
+::
+
+   # We will call this function from the Main scene.
+   func initialize(start_position, player_position):
+       translation = start_position
+       # We turn the mob so it looks at the player.
+       look_at(player_position, Vector3.UP)
+       # And rotate it randomly so it doesn't move exactly toward the player.
+       rotate_y(rand_range(-PI / 4, PI / 4))
+
+We then calculate a random speed using "rand_range() "once again and we use it
+to calculate the velocity.
+
+We start by creating a 3D vector pointing forward, multiply it by our
+"random_speed ", and finally rotate it using the "Vector3 "class's
+``rotated()`` method.
+
+::
+
+       # We calculate a random speed.
+       var random_speed = rand_range(min_speed, max_speed)
+       # We calculate a forward velocity that represents the speed.
+       velocity = Vector3.FORWARD * random_speed
+       # We then rotate the vector based on the mob's Y rotation to move in the direction it's looking.
+       velocity = velocity.rotated(Vector3.UP, rotation.y)
+
+Leaving the screen
+------------------
+
+We still have to destroy the mobs when they leave the screen. To do so, we'll
+connect our *VisibilityNotifier* node’s ``screen_exited`` signal to the *Mob*.
+
+Head back to the 3D viewport by clicking on the *3D* label at the top of the
+editor. You can also press F2.
+
+|image8|
+
+Select the *VisibilityNotifier* node and on the right side of the interface,
+navigate to the *Node* dock. Double-click the *screen_exited()* signal.
+
+|image9|
+
+Connect the signal to the *Mob*.
+
+|image10|
+
+This will take you back to the script editor and add a new function for you,
+``_on_VisibilityNotifier_screen_exited()``. From it, call the ``queue_free()``
+method. This will destroy the mob instance when the *VisibilityNotifier* \'s box
+leaves the screen.
+
+::
+
+   func _on_VisibilityNotifier_screen_exited():
+       queue_free()
+
+Our monster is ready to enter the game! In the next part, you will spawn
+monsters in the game level.
+
+Here is the complete "Mob.gd "script for reference.
+
+::
+
+   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)
+
+
+   func _on_VisibilityNotifier_screen_exited():
+       queue_free()
+
+.. |image0| image:: img/04.mob_scene/01.initial_three_nodes.png
+.. |image1| image:: img/04.mob_scene/02.add_child_node.png
+.. |image2| image:: img/04.mob_scene/03.scene_with_collision_shape.png
+.. |image3| image:: img/04.mob_scene/04.create_box_shape.png
+.. |image4| image:: img/04.mob_scene/05.box_final_size.png
+.. |image5| image:: img/04.mob_scene/06.visibility_notifier.png
+.. |image6| image:: img/04.mob_scene/07.visibility_notifier_bbox_resized.png
+.. |image7| image:: img/04.mob_scene/08.mob_attach_script.png
+.. |image8| image:: img/04.mob_scene/09.switch_to_3d_workspace.png
+.. |image9| image:: img/04.mob_scene/10.node_dock.png
+.. |image10| image:: img/04.mob_scene/11.connect_signal.png

BIN
getting_started/first_3d_game/img/04.mob_scene/01.initial_three_nodes.png


BIN
getting_started/first_3d_game/img/04.mob_scene/02.add_child_node.png


BIN
getting_started/first_3d_game/img/04.mob_scene/03.scene_with_collision_shape.png


BIN
getting_started/first_3d_game/img/04.mob_scene/04.create_box_shape.png


BIN
getting_started/first_3d_game/img/04.mob_scene/05.box_final_size.png


BIN
getting_started/first_3d_game/img/04.mob_scene/06.visibility_notifier.png


BIN
getting_started/first_3d_game/img/04.mob_scene/07.visibility_notifier_bbox_resized.png


BIN
getting_started/first_3d_game/img/04.mob_scene/08.mob_attach_script.png


BIN
getting_started/first_3d_game/img/04.mob_scene/09.switch_to_3d_workspace.png


BIN
getting_started/first_3d_game/img/04.mob_scene/10.node_dock.png


BIN
getting_started/first_3d_game/img/04.mob_scene/11.connect_signal.png


+ 1 - 0
getting_started/first_3d_game/index.rst

@@ -57,5 +57,6 @@ Contents
    01.game_setup
    02.player_input
    03.player_movement_code
+   04.mob_scene
 
 .. |image0| image:: img/squash-the-creeps-final.gif