Examples/ShrimpleWalker.cs

Component for a player-like character controller example. It reads input, sets desired velocity on a ShrimpleCharacterController, handles jumping and duck/run speeds, updates a SkinnedModelRenderer animation parameters, and creates a child Camera GameObject.

Native Interop
using Sandbox.Citizen;

namespace ShrimpleCharacterController;

[Hide]
public sealed class ShrimpleWalker : Component
{
    [RequireComponent]
    public ShrimpleCharacterController Controller { get; set; }
    [Property]
    public SkinnedModelRenderer Renderer { get; set; }
    public GameObject Camera { get; set; }

    [Property]
    [Range(50f, 200f)]
    public float WalkSpeed { get; set; } = 100f;

    [Property]
    [Range(100f, 500f)]
    public float RunSpeed { get; set; } = 300f;

    [Property]
    [Range(25f, 100f)]
    public float DuckSpeed { get; set; } = 50f;

    [Property]
    [Range(200f, 500f)]
    public float JumpStrength { get; set; } = 350f;

    public Angles EyeAngles { get; set; }

    protected override void OnStart()
    {
        base.OnStart();

        Renderer = Components.Get<SkinnedModelRenderer>(FindMode.EverythingInSelfAndDescendants);
        Camera = new GameObject(true, "Camera");
        Camera.SetParent(GameObject);
        var cameraComponent = Camera.Components.Create<CameraComponent>();
        cameraComponent.ZFar = 32768f;
    }

    protected override void OnFixedUpdate()
    {
        base.OnFixedUpdate();

        var wishDirection = Input.AnalogMove.Normal * Rotation.FromYaw(EyeAngles.yaw) * GameObject.WorldRotation;
        var isDucking = Input.Down("Duck");
        var isRunning = Input.Down("Run");
        var wishSpeed = isDucking ? DuckSpeed :
            isRunning ? RunSpeed : WalkSpeed;

        Controller.WishVelocity = wishDirection * wishSpeed;

        if (Input.Pressed("Jump") && Controller.IsOnGround)
        {
            Controller.Punch(-Controller.AppliedGravity.Normal * JumpStrength);
            Renderer?.Set("b_jump", true);
        }

        if (!Renderer.IsValid()) return;

        var oldX = Renderer.GetFloat("move_x");
        var oldY = Renderer.GetFloat("move_y");

        var velocity = Controller.Velocity;
        var runScale = 1f / Renderer.Transform.World.UniformScale;
        var moveX = Vector3.Dot(velocity, Renderer.WorldRotation.Forward) * runScale;
        var moveY = Vector3.Dot(velocity, Renderer.WorldRotation.Right) * runScale;

        var newX = MathX.Lerp(oldX, moveX, Time.Delta * 10f);
        var newY = MathX.Lerp(oldY, moveY, Time.Delta * 10f);

        Renderer.Set("move_x", newX);
        Renderer.Set("move_y", newY);

        Renderer.Set("b_grounded", Controller.IsOnGround);
        Renderer.Set("duck", isDucking ? 1f : 0f);
    }

    protected override void OnUpdate()
    {
        base.OnUpdate();

        EyeAngles += Input.AnalogLook;
        EyeAngles = EyeAngles.WithPitch(MathX.Clamp(EyeAngles.pitch, -10f, 40f));
        Renderer.WorldRotation = Rotation.Slerp(Renderer.WorldRotation, Rotation.FromYaw(EyeAngles.yaw), Time.Delta * 15f);

        var cameraOffset = Vector3.Up * 70f + Vector3.Backward * 220f;
        Camera.LocalRotation = EyeAngles.ToRotation();
        Camera.LocalPosition = cameraOffset * Camera.LocalRotation;
    }
}