diff --git a/tutorials/3d/fps_tutorial/part_one.rst b/tutorials/3d/fps_tutorial/part_one.rst index 56ca7314b..38a318ed6 100644 --- a/tutorials/3d/fps_tutorial/part_one.rst +++ b/tutorials/3d/fps_tutorial/part_one.rst @@ -138,7 +138,8 @@ Attach a new script to the ``Player`` node and call it ``Player.gd``. Let's program our player by adding the ability to move around, look around with the mouse, and jump. Add the following code to ``Player.gd``: -:: +.. tabs:: + .. code-tab:: gdscript GDScript extends KinematicBody @@ -240,6 +241,135 @@ Add the following code to ``Player.gd``: camera_rot.x = clamp(camera_rot.x, -70, 70) rotation_helper.rotation_degrees = camera_rot + .. code-tab:: csharp + + using Godot; + using System; + + public class Player : KinematicBody + { + [Export] + public float Gravity = -24.8f; + [Export] + public float MaxSpeed = 20.0f; + [Export] + public float JumpSpeed = 18.0f; + [Export] + public float Accel = 4.5f; + [Export] + public float Deaccel = 16.0f; + [Export] + public float MaxSlopeAngle = 40.0f; + [Export] + public float MouseSensitivity = 0.05f; + + private Vector3 _vel = new Vector3(); + private Vector3 _dir = new Vector3(); + + private Camera _camera; + private Spatial _rotationHelper; + + // Called when the node enters the scene tree for the first time. + public override void _Ready() + { + _camera = GetNode("Rotation_Helper/Camera"); + _rotationHelper = GetNode("Rotation_Helper"); + + Input.SetMouseMode(Input.MouseMode.Captured); + } + + public override void _PhysicsProcess(float delta) + { + ProcessInput(delta); + ProcessMovement(delta); + } + + private void ProcessInput(float delta) + { + // ------------------------------------------------------------------- + // Walking + _dir = new Vector3(); + Transform camXform = _camera.GetGlobalTransform(); + + Vector2 inputMovementVector = new Vector2(); + + if (Input.IsActionPressed("movement_forward")) + inputMovementVector.y += 1; + if (Input.IsActionPressed("movement_backward")) + inputMovementVector.y -= 1; + if (Input.IsActionPressed("movement_left")) + inputMovementVector.x -= 1; + if (Input.IsActionPressed("movement_right")) + inputMovementVector.x += 1; + + inputMovementVector = inputMovementVector.Normalized(); + + _dir += -camXform.basis.z.Normalized() * inputMovementVector.y; + _dir += camXform.basis.x.Normalized() * inputMovementVector.x; + // ------------------------------------------------------------------- + + // ------------------------------------------------------------------- + // Jumping + if (IsOnFloor()) + { + if (Input.IsActionJustPressed("movement_jump")) + _vel.y = JumpSpeed; + } + // ------------------------------------------------------------------- + + // ------------------------------------------------------------------- + // Capturing/Freeing the cursor + if (Input.IsActionJustPressed("ui_cancel")) + { + if (Input.GetMouseMode() == Input.MouseMode.Visible) + Input.SetMouseMode(Input.MouseMode.Captured); + else + Input.SetMouseMode(Input.MouseMode.Visible); + } + // ------------------------------------------------------------------- + } + + private void ProcessMovement(float delta) + { + _dir.y = 0; + _dir = _dir.Normalized(); + + _vel.y += delta * Gravity; + + Vector3 hvel = _vel; + hvel.y = 0; + + Vector3 target = _dir; + + target *= MaxSpeed; + + float accel; + if (_dir.Dot(hvel) > 0) + accel = Accel; + else + accel = Deaccel; + + hvel = hvel.LinearInterpolate(target, accel * delta); + _vel.x = hvel.x; + _vel.z = hvel.z; + _vel = MoveAndSlide(_vel, new Vector3(0, 1, 0), false, 4, Mathf.Deg2Rad(MaxSlopeAngle)); + } + + public override void _Input(InputEvent @event) + { + if (@event is InputEventMouseMotion && Input.GetMouseMode() == Input.MouseMode.Captured) + { + InputEventMouseMotion mouseEvent = @event as InputEventMouseMotion; + _rotationHelper.RotateX(Mathf.Deg2Rad(mouseEvent.Relative.y * MouseSensitivity)); + RotateY(Mathf.Deg2Rad(-mouseEvent.Relative.x * MouseSensitivity)); + + Vector3 cameraRot = _rotationHelper.RotationDegrees; + cameraRot.x = Mathf.Clamp(cameraRot.x, -70, 70); + _rotationHelper.RotationDegrees = cameraRot; + } + } + } + This is a lot of code, so let's break it down function by function: .. tip:: While copy and pasting code is ill advised, as you can learn a lot from manually typing the code in, you can @@ -339,7 +469,8 @@ In Godot, the origin is at position ``(0, 0, 0)`` with a rotation of ``(0, 0, 0) If you want to move using the world space directional vectors, you'd do something like this: -:: +.. tabs:: + .. code-tab:: gdscript GDScript if Input.is_action_pressed("movement_forward"): node.translate(Vector3(0, 0, 1)) @@ -349,6 +480,17 @@ If you want to move using the world space directional vectors, you'd do somethin node.translate(Vector3(1, 0, 0)) if Input.is_action_pressed("movement_right"): node.translate(Vector3(-1, 0, 0)) + + .. code-tab:: csharp + + if (Input.IsActionPressed("movement_forward")) + node.Translate(new Vector3(0, 0, 1)); + if (Input.IsActionPressed("movement_backward")) + node.Translate(new Vector3(0, 0, -1)); + if (Input.IsActionPressed("movement_left")) + node.Translate(new Vector3(1, 0, 0)); + if (Input.IsActionPressed("movement_right")) + node.Translate(new Vector3(-1, 0, 0)); .. note:: Notice how we do not need to do any calculations to get world space directional vectors. We can define a few :ref:`Vector3 ` variables and input the values pointing in each direction. @@ -387,7 +529,8 @@ Each of those vectors point towards each of the local space vectors coming from To use the :ref:`Spatial ` node's local directional vectors, we use this code: -:: +.. tabs:: + .. code-tab:: gdscript GDScript if Input.is_action_pressed("movement_forward"): node.translate(node.global_transform.basis.z.normalized()) @@ -397,6 +540,17 @@ To use the :ref:`Spatial ` node's local directional vectors, we u node.translate(node.global_transform.basis.x.normalized()) if Input.is_action_pressed("movement_right"): node.translate(-node.global_transform.basis.x.normalized()) + + .. code-tab:: csharp + + if (Input.IsActionPressed("movement_forward")) + node.Translate(node.GlobalTransform.basis.z.Normalized()); + if (Input.IsActionPressed("movement_backward")) + node.Translate(-node.GlobalTransform.basis.z.Normalized()); + if (Input.IsActionPressed("movement_left")) + node.Translate(node.GlobalTransform.basis.x.Normalized()); + if (Input.IsActionPressed("movement_right")) + node.Translate(-node.GlobalTransform.basis.x.Normalized()); Here is what local space looks like in 2D: @@ -539,7 +693,8 @@ so let's do that! First we need a few more class variables in our player script: -:: +.. tabs:: + .. code-tab:: gdscript GDScript const MAX_SPRINT_SPEED = 30 const SPRINT_ACCEL = 18 @@ -547,6 +702,16 @@ First we need a few more class variables in our player script: var flashlight + .. code-tab:: csharp + + [Export] + public float MaxSprintSpeed = 30.0f; + [Export] + public float SprintAccel = 18.0f; + private bool _isSprinting = false; + + private SpotLight _flashlight; + All the sprinting variables work exactly the same as the non sprinting variables with similar names. @@ -555,17 +720,23 @@ we will be using to hold the player's flash light node. Now we need to add a few lines of code, starting in ``_ready``. Add the following to ``_ready``: -:: +.. tabs:: + .. code-tab:: gdscript GDScript flashlight = $Rotation_Helper/Flashlight + .. code-tab:: csharp + + _flashlight = GetNode("Rotation_Helper/Flashlight"); + This gets the ``Flashlight`` node and assigns it to the ``flashlight`` variable. _________ Now we need to change some of the code in ``process_input``. Add the following somewhere in ``process_input``: -:: +.. tabs:: + .. code-tab:: gdscript GDScript # ---------------------------------- # Sprinting @@ -584,6 +755,26 @@ Now we need to change some of the code in ``process_input``. Add the following s flashlight.show() # ---------------------------------- + .. code-tab:: csharp + + // ------------------------------------------------------------------- + // Sprinting + if (Input.IsActionPressed("movement_sprint")) + _isSprinting = true; + else + _isSprinting = false; + // ------------------------------------------------------------------- + + // ------------------------------------------------------------------- + // Turning the flashlight on/off + if (Input.IsActionJustPressed("flashlight")) + { + if (_flashlight.IsVisibleInTree()) + _flashlight.Hide(); + else + _flashlight.Show(); + } + Let's go over the additions: We set ``is_sprinting`` to true when the player is holding down the ``movement_sprint`` action, and false @@ -597,25 +788,40 @@ _________ Now we need to change a couple things in ``process_movement``. First, replace ``target *= MAX_SPEED`` with the following: -:: +.. tabs:: + .. code-tab:: gdscript GDScript if is_sprinting: target *= MAX_SPRINT_SPEED else: target *= MAX_SPEED + .. code-tab:: csharp + + if (_isSprinting) + target *= MaxSprintSpeed; + else + target *= MaxSpeed; + Now instead of always multiplying ``target`` by ``MAX_SPEED``, we first check to see if the player is sprinting or not. If the player is sprinting, we instead multiply ``target`` by ``MAX_SPRINT_SPEED``. Now all that's left is to change the acceleration when sprinting. Change ``accel = ACCEL`` to the following: -:: +.. tabs:: + .. code-tab:: gdscript GDScript if is_sprinting: accel = SPRINT_ACCEL else: accel = ACCEL + .. code-tab:: csharp + + if (_isSprinting) + accel = SprintAccel; + else + accel = Accel; Now, when the player is sprinting, we'll use ``SPRINT_ACCEL`` instead of ``ACCEL``, which will accelerate the player faster.