mirror of
https://github.com/stride3d/stride
synced 2026-05-24 10:19:21 +00:00
281 lines
12 KiB
C#
281 lines
12 KiB
C#
using System;
|
|
using Stride.Core;
|
|
using Stride.Core.Mathematics;
|
|
using Stride.Engine;
|
|
using Stride.Input;
|
|
|
|
namespace CSharpIntermediate
|
|
{
|
|
/// <summary>
|
|
/// A script that allows to move and rotate an entity through keyboard, mouse and touch input to provide basic camera navigation.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The entity can be moved using W, A, S, D, Q and E, arrow keys, a gamepad's left stick or dragging/scaling using multi-touch.
|
|
/// Rotation is achieved using the Numpad, the mouse while holding the right mouse button, a gamepad's right stick, or dragging using single-touch.
|
|
/// </remarks>
|
|
public class BasicCameraController : SyncScript
|
|
{
|
|
private const float MaximumPitch = MathUtil.PiOverTwo * 0.99f;
|
|
|
|
private Vector3 upVector;
|
|
private Vector3 translation;
|
|
private float yaw;
|
|
private float pitch;
|
|
|
|
public bool Gamepad { get; set; } = false;
|
|
|
|
public Vector3 KeyboardMovementSpeed { get; set; } = new Vector3(5.0f);
|
|
|
|
public Vector3 TouchMovementSpeed { get; set; } = new Vector3(0.7f, 0.7f, 0.3f);
|
|
|
|
public float SpeedFactor { get; set; } = 5.0f;
|
|
|
|
public Vector2 KeyboardRotationSpeed { get; set; } = new Vector2(3.0f);
|
|
|
|
public Vector2 MouseRotationSpeed { get; set; } = new Vector2(1.0f, 1.0f);
|
|
|
|
public Vector2 TouchRotationSpeed { get; set; } = new Vector2(1.0f, 0.7f);
|
|
|
|
public override void Start()
|
|
{
|
|
base.Start();
|
|
|
|
// Default up-direction
|
|
upVector = Vector3.UnitY;
|
|
|
|
// Configure touch input
|
|
if (!Platform.IsWindowsDesktop)
|
|
{
|
|
Input.Gestures.Add(new GestureConfigDrag());
|
|
Input.Gestures.Add(new GestureConfigComposite());
|
|
}
|
|
}
|
|
|
|
public override void Update()
|
|
{
|
|
ProcessInput();
|
|
UpdateTransform();
|
|
}
|
|
|
|
private void ProcessInput()
|
|
{
|
|
float deltaTime = (float)Game.UpdateTime.Elapsed.TotalSeconds;
|
|
translation = Vector3.Zero;
|
|
yaw = 0f;
|
|
pitch = 0f;
|
|
|
|
// Keyboard and Gamepad based movement
|
|
{
|
|
// Our base speed is: one unit per second:
|
|
// deltaTime contains the duration of the previous frame, let's say that in this update
|
|
// or frame it is equal to 1/60, that means that the previous update ran 1/60 of a second ago
|
|
// and the next will, in most cases, run in around 1/60 of a second from now. Knowing that,
|
|
// we can move 1/60 of a unit on this frame so that in around 60 frames(1 second)
|
|
// we will have travelled one whole unit in a second.
|
|
// If you don't use deltaTime your speed will be dependant on the amount of frames rendered
|
|
// on screen which often are inconsistent, meaning that if the player has performance issues,
|
|
// this entity will move around slower.
|
|
float speed = 1f * deltaTime;
|
|
|
|
Vector3 dir = Vector3.Zero;
|
|
|
|
if (Gamepad && Input.HasGamePad)
|
|
{
|
|
GamePadState padState = Input.DefaultGamePad.State;
|
|
// LeftThumb can be positive or negative on both axis (pushed to the right or to the left)
|
|
dir.Z += padState.LeftThumb.Y;
|
|
dir.X += padState.LeftThumb.X;
|
|
|
|
// Triggers are always positive, in this case using one to increase and the other to decrease
|
|
dir.Y -= padState.LeftTrigger;
|
|
dir.Y += padState.RightTrigger;
|
|
|
|
// Increase speed when pressing A, LeftShoulder or RightShoulder
|
|
// Here:does the enum flag 'Buttons' has one of the flag ('A','LeftShoulder' or 'RightShoulder') set
|
|
if ((padState.Buttons & (GamePadButton.A | GamePadButton.LeftShoulder | GamePadButton.RightShoulder)) != 0)
|
|
{
|
|
speed *= SpeedFactor;
|
|
}
|
|
}
|
|
|
|
if (Input.HasKeyboard)
|
|
{
|
|
// Move with keyboard
|
|
// Forward/Backward
|
|
if (Input.IsKeyDown(Keys.W) || Input.IsKeyDown(Keys.Up))
|
|
{
|
|
dir.Z += 1;
|
|
}
|
|
if (Input.IsKeyDown(Keys.S) || Input.IsKeyDown(Keys.Down))
|
|
{
|
|
dir.Z -= 1;
|
|
}
|
|
|
|
// Left/Right
|
|
if (Input.IsKeyDown(Keys.A) || Input.IsKeyDown(Keys.Left))
|
|
{
|
|
dir.X -= 1;
|
|
}
|
|
if (Input.IsKeyDown(Keys.D) || Input.IsKeyDown(Keys.Right))
|
|
{
|
|
dir.X += 1;
|
|
}
|
|
|
|
// Down/Up
|
|
if (Input.IsKeyDown(Keys.Q))
|
|
{
|
|
dir.Y -= 1;
|
|
}
|
|
if (Input.IsKeyDown(Keys.E))
|
|
{
|
|
dir.Y += 1;
|
|
}
|
|
|
|
// Increase speed when pressing shift
|
|
if (Input.IsKeyDown(Keys.LeftShift) || Input.IsKeyDown(Keys.RightShift))
|
|
{
|
|
speed *= SpeedFactor;
|
|
}
|
|
|
|
// If the player pushes down two or more buttons, the direction and ultimately the base speed
|
|
// will be greater than one (vector(1, 1) is farther away from zero than vector(0, 1)),
|
|
// normalizing the vector ensures that whichever direction the player chooses, that direction
|
|
// will always be at most one unit in length.
|
|
// We're keeping dir as is if isn't longer than one to retain sub unit movement:
|
|
// a stick not entirely pushed forward should make the entity move slower.
|
|
if (dir.Length() > 1f)
|
|
{
|
|
dir = Vector3.Normalize(dir);
|
|
}
|
|
}
|
|
|
|
// Finally, push all of that to the translation variable which will be used within UpdateTransform()
|
|
translation += dir * KeyboardMovementSpeed * speed;
|
|
}
|
|
|
|
// Keyboard and Gamepad based Rotation
|
|
{
|
|
// See Keyboard & Gamepad translation's deltaTime usage
|
|
float speed = 1f * deltaTime;
|
|
Vector2 rotation = Vector2.Zero;
|
|
if (Gamepad && Input.HasGamePad)
|
|
{
|
|
GamePadState padState = Input.DefaultGamePad.State;
|
|
rotation.X += padState.RightThumb.Y;
|
|
rotation.Y += -padState.RightThumb.X;
|
|
}
|
|
|
|
if (Input.HasKeyboard)
|
|
{
|
|
if (Input.IsKeyDown(Keys.NumPad2))
|
|
{
|
|
rotation.X += 1;
|
|
}
|
|
if (Input.IsKeyDown(Keys.NumPad8))
|
|
{
|
|
rotation.X -= 1;
|
|
}
|
|
|
|
if (Input.IsKeyDown(Keys.NumPad4))
|
|
{
|
|
rotation.Y += 1;
|
|
}
|
|
if (Input.IsKeyDown(Keys.NumPad6))
|
|
{
|
|
rotation.Y -= 1;
|
|
}
|
|
|
|
// See Keyboard & Gamepad translation's Normalize() usage
|
|
if (rotation.Length() > 1f)
|
|
{
|
|
rotation = Vector2.Normalize(rotation);
|
|
}
|
|
}
|
|
|
|
// Modulate by speed
|
|
rotation *= KeyboardRotationSpeed * speed;
|
|
|
|
// Finally, push all of that to pitch & yaw which are going to be used within UpdateTransform()
|
|
pitch += rotation.X;
|
|
yaw += rotation.Y;
|
|
}
|
|
|
|
// Mouse movement and gestures
|
|
{
|
|
// This type of input should not use delta time at all, they already are frame-rate independent.
|
|
// Lets say that you are going to move your finger/mouse for one second over 40 units, it doesn't matter
|
|
// the amount of frames occuring within that time frame, each frame will receive the right amount of delta:
|
|
// a quarter of a second -> 10 units, half a second -> 20 units, one second -> your 40 units.
|
|
|
|
if (Input.HasMouse)
|
|
{
|
|
// Rotate with mouse
|
|
if (Input.IsMouseButtonDown(MouseButton.Right))
|
|
{
|
|
Input.LockMousePosition();
|
|
Game.IsMouseVisible = false;
|
|
|
|
yaw -= Input.MouseDelta.X * MouseRotationSpeed.X;
|
|
pitch -= Input.MouseDelta.Y * MouseRotationSpeed.Y;
|
|
}
|
|
else
|
|
{
|
|
Input.UnlockMousePosition();
|
|
Game.IsMouseVisible = true;
|
|
}
|
|
}
|
|
|
|
// Handle gestures
|
|
foreach (var gestureEvent in Input.GestureEvents)
|
|
{
|
|
switch (gestureEvent.Type)
|
|
{
|
|
// Rotate by dragging
|
|
case GestureType.Drag:
|
|
var drag = (GestureEventDrag)gestureEvent;
|
|
var dragDistance = drag.DeltaTranslation;
|
|
yaw = -dragDistance.X * TouchRotationSpeed.X;
|
|
pitch = -dragDistance.Y * TouchRotationSpeed.Y;
|
|
break;
|
|
|
|
// Move along z-axis by scaling and in xy-plane by multi-touch dragging
|
|
case GestureType.Composite:
|
|
var composite = (GestureEventComposite)gestureEvent;
|
|
translation.X = -composite.DeltaTranslation.X * TouchMovementSpeed.X;
|
|
translation.Y = -composite.DeltaTranslation.Y * TouchMovementSpeed.Y;
|
|
translation.Z = MathF.Log(composite.DeltaScale + 1) * TouchMovementSpeed.Z;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateTransform()
|
|
{
|
|
// Get the local coordinate system
|
|
var rotation = Matrix.RotationQuaternion(Entity.Transform.Rotation);
|
|
|
|
// Enforce the global up-vector by adjusting the local x-axis
|
|
var right = Vector3.Cross(rotation.Forward, upVector);
|
|
var up = Vector3.Cross(right, rotation.Forward);
|
|
|
|
// Stabilize
|
|
right.Normalize();
|
|
up.Normalize();
|
|
|
|
// Adjust pitch. Prevent it from exceeding up and down facing. Stabilize edge cases.
|
|
var currentPitch = MathUtil.PiOverTwo - MathF.Acos(Vector3.Dot(rotation.Forward, upVector));
|
|
pitch = MathUtil.Clamp(currentPitch + pitch, -MaximumPitch, MaximumPitch) - currentPitch;
|
|
|
|
Vector3 finalTranslation = translation;
|
|
finalTranslation.Z = -finalTranslation.Z;
|
|
finalTranslation = Vector3.TransformCoordinate(finalTranslation, rotation);
|
|
|
|
// Move in local coordinates
|
|
Entity.Transform.Position += finalTranslation;
|
|
|
|
// Yaw around global up-vector, pitch and roll in local space
|
|
Entity.Transform.Rotation *= Quaternion.RotationAxis(right, pitch) * Quaternion.RotationAxis(upVector, yaw);
|
|
}
|
|
}
|
|
}
|