diff --git a/Assets/prefabs/player.prefab b/Assets/prefabs/player.prefab index 8f65d15..c3b9806 100644 --- a/Assets/prefabs/player.prefab +++ b/Assets/prefabs/player.prefab @@ -56,12 +56,12 @@ "WalkSpeed": 100 }, { - "__type": "Sandbox.Citizen.CitizenAnimationHelper", - "__guid": "640af9e4-02ed-4391-95ea-e45fba51fa42", - "BodyWeight": 1, - "EyesWeight": 1, + "__type": "AnimationHelper", + "__guid": "536068a6-fe5d-4f45-af74-9522ebf7e663", + "BodyWeight": 0, + "EyesWeight": 0, "HeadWeight": 1, - "LookAtEnabled": false, + "LookAtEnabled": true, "Target": { "_type": "component", "component_id": "4fd8c36d-180b-41af-8439-6b975ca1c7b3", diff --git a/Assets/scenes/minimal.scene b/Assets/scenes/minimal.scene index e27d0ae..ce6d681 100644 --- a/Assets/scenes/minimal.scene +++ b/Assets/scenes/minimal.scene @@ -6,7 +6,6 @@ "Flags": 0, "Name": "NetworkManager", "Enabled": true, - "NetworkMode": 0, "Components": [ { "__type": "Sandbox.SceneInformation", @@ -17,13 +16,13 @@ }, { "__type": "Sandbox.KPTLConnect", - "__guid": "79db0a43-3cee-44bf-86db-fa70ca72f62c", + "__guid": "895b0f93-287e-4607-808e-6ce5bdbb5c05", "PlayerPrefab": { "_type": "gameobject", "prefab": "prefabs/player.prefab" }, "SpawnPoints": [], - "StartServer": false + "StartServer": true } ] }, diff --git a/Code/AnimationHelper.cs b/Code/AnimationHelper.cs new file mode 100644 index 0000000..9745a39 --- /dev/null +++ b/Code/AnimationHelper.cs @@ -0,0 +1,429 @@ +using System; + +/// +/// Used to control the Citizen animation state. You don't have to use this to animate your citizen avatar, but our +/// aim is to put everything you need in this class, so you can easily see what variables are available. +/// +[Title( "Animation Helper" )] +[Category( "KPTL" )] +[Icon( "directions_run" )] +public sealed class AnimationHelper : Component, Component.ExecuteInEditor +{ + /// + /// The skinned model renderer that we'll apply all parameters to. + /// + [Property] public SkinnedModelRenderer Target { get; set; } + + /// + /// Where are the eyes of our character? + /// + [Property] public GameObject EyeSource { get; set; } + + /// + /// How tall are we? + /// + [Property, Range( 0.5f, 1.5f ), Title( "Avatar Height Scale" )] public float? Height { get; set; } + + /// + /// Are we looking at something? Useful for stuff like cutscenes, where you want an NPC to stare at you. + /// + [Property, ToggleGroup( "LookAtEnabled", Label = "Look At" )] + public bool LookAtEnabled { get; set; } = false; + + /// + /// Which GameObject should we be looking at? + /// + [Property, ToggleGroup( "LookAtEnabled" )] public GameObject LookAt { get; set; } + + [Property, ToggleGroup( "LookAtEnabled" ), Range( 0, 1 )] public float EyesWeight { get; set; } = 1.0f; + [Property, ToggleGroup( "LookAtEnabled" ), Range( 0, 1 )] public float HeadWeight { get; set; } = 1.0f; + [Property, ToggleGroup( "LookAtEnabled" ), Range( 0, 1 )] public float BodyWeight { get; set; } = 1.0f; + + /// + /// IK will try to place the limb where this GameObject is in the world. + /// + [Property, Group( "Inverse kinematics" ), Title( "Left Hand" )] public GameObject IkLeftHand { get; set; } + + /// + [Property, Group( "Inverse kinematics" ), Title( "Right Hand" )] public GameObject IkRightHand { get; set; } + + /// + [Property, Group( "Inverse kinematics" ), Title( "Left Foot" )] public GameObject IkLeftFoot { get; set; } + + /// + [Property, Group( "Inverse kinematics" ), Title( "Right Foot" )] public GameObject IkRightFoot { get; set; } + + [Sync] private Vector3 LookDir { get; set; } + + protected override void OnUpdate() + { + if ( !Target.IsValid() ) + return; + + AimBodyWeight = 0f; + AimEyesWeight = 0f; + AimHeadWeight = 1f; + + + if ( LookAtEnabled ) + { + if ( Network.IsOwner ) + { + LookDir = Scene.Camera.GameObject.WorldRotation.Forward; + } + WithLook( LookDir, EyesWeight, HeadWeight, BodyWeight ); + } + + if ( Height.HasValue ) + { + Target.Set( "scale_height", Height.Value ); + } + + if ( IkLeftHand.IsValid() && IkLeftHand.Active ) Target.SetIk( "hand_left", IkLeftHand.Transform.World ); + else Target.ClearIk( "hand_left" ); + + if ( IkRightHand.IsValid() && IkRightHand.Active ) Target.SetIk( "hand_right", IkRightHand.Transform.World ); + else Target.ClearIk( "hand_right" ); + + if ( IkLeftFoot.IsValid() && IkLeftFoot.Active ) Target.SetIk( "foot_left", IkLeftFoot.Transform.World ); + else Target.ClearIk( "foot_left" ); + + if ( IkRightFoot.IsValid() && IkRightFoot.Active ) Target.SetIk( "foot_right", IkRightFoot.Transform.World ); + else Target.ClearIk( "foot_right" ); + } + + public void ProceduralHitReaction( DamageInfo info, float damageScale = 1.0f, Vector3 force = default ) + { + var boneId = info.Hitbox?.Bone?.Index ?? 0; + var bone = Target.GetBoneObject( boneId ); + + var localToBone = bone.LocalPosition; + if ( localToBone == Vector3.Zero ) localToBone = Vector3.One; + + Target.Set( "hit", true ); + Target.Set( "hit_bone", boneId ); + Target.Set( "hit_offset", localToBone ); + Target.Set( "hit_direction", force.Normal ); + Target.Set( "hit_strength", (force.Length / 1000.0f) * damageScale ); + } + + /// + /// The transform of the eyes, in world space. This is worked out from EyeSource is it's set. + /// + public Transform EyeWorldTransform + { + get + { + if ( EyeSource.IsValid() ) return EyeSource.Transform.World; + + return Transform.World; + } + } + + + /// + /// Have the player look at this point in the world + /// + public void WithLook( Vector3 lookDirection, float eyesWeight = 1.0f, float headWeight = 1.0f, float bodyWeight = 1.0f ) + { + // Target.SetLookDirection( "aim_eyes", lookDirection, eyesWeight ); + Target.SetLookDirection( "aim_head", lookDirection, headWeight ); + // Target.SetLookDirection( "aim_body", lookDirection, bodyWeight ); + } + + /// + /// Have the player animate moving with a set velocity (this doesn't move them! Your character controller is responsible for that) + /// + /// + public void WithVelocity( Vector3 Velocity ) + { + var dir = Velocity; + var forward = Target.WorldRotation.Forward.Dot( dir ); + var sideward = Target.WorldRotation.Right.Dot( dir ); + + var angle = MathF.Atan2( sideward, forward ).RadianToDegree().NormalizeDegrees(); + + Target.Set( "move_direction", angle ); + Target.Set( "move_speed", Velocity.Length ); + Target.Set( "move_groundspeed", Velocity.WithZ( 0 ).Length ); + Target.Set( "move_y", sideward ); + Target.Set( "move_x", forward ); + Target.Set( "move_z", Velocity.z ); + } + + /// + /// Animates the wish for the character to move in a certain direction. For example, when in the air, your character will swing their arms in that direction. + /// + /// + public void WithWishVelocity( Vector3 Velocity ) + { + var dir = Velocity; + var forward = Target.WorldRotation.Forward.Dot( dir ); + var sideward = Target.WorldRotation.Right.Dot( dir ); + + var angle = MathF.Atan2( sideward, forward ).RadianToDegree().NormalizeDegrees(); + + Target.Set( "wish_direction", angle ); + Target.Set( "wish_speed", Velocity.Length ); + Target.Set( "wish_groundspeed", Velocity.WithZ( 0 ).Length ); + Target.Set( "wish_y", sideward ); + Target.Set( "wish_x", forward ); + Target.Set( "wish_z", Velocity.z ); + } + + /// + /// Where are we aiming? + /// + public Rotation AimAngle + { + set + { + value = Target.WorldRotation.Inverse * value; + var ang = value.Angles(); + + Target.Set( "aim_body_pitch", ang.pitch ); + Target.Set( "aim_body_yaw", ang.yaw ); + } + } + + /// + /// The weight of the aim angle, but specifically for the Citizen's eyes. + /// + public float AimEyesWeight + { + get => Target.GetFloat( "aim_eyes_weight" ); + set => Target.Set( "aim_eyes_weight", value ); + } + + /// + /// The weight of the aim angle, but specifically for the Citizen's head. + /// + public float AimHeadWeight + { + get => Target.GetFloat( "aim_head_weight" ); + set => Target.Set( "aim_head_weight", value ); + } + + + /// + /// The weight of the aim angle, but specifically for the Citizen's body. + /// + public float AimBodyWeight + { + get => Target.GetFloat( "aim_body_weight" ); + set => Target.Set( "aim_body_weight", value ); + } + + /// + /// How much the character is rotating in degrees per second, this controls feet shuffling. + /// If rotating clockwise this should be positive, if rotating counter-clockwise this should be negative. + /// + public float MoveRotationSpeed + { + get => Target.GetFloat( "move_rotationspeed" ); + set => Target.Set( "move_rotationspeed", value ); + } + + [Obsolete( "Use MoveRotationSpeed" )] + public float FootShuffle + { + get => Target.GetFloat( "move_shuffle" ); + set => Target.Set( "move_shuffle", value ); + } + + /// + /// The scale of being ducked (crouched) (0 - 1) + /// + [Sync] public float DuckLevel + { + get => Target.GetFloat( "duck" ); + set => Target.Set( "duck", value ); + } + + /// + /// How loud are we talking? + /// + public float VoiceLevel + { + get => Target.GetFloat( "voice" ); + set => Target.Set( "voice", value ); + } + + /// + /// Are we sitting down? + /// + public bool IsSitting + { + get => Target.GetBool( "b_sit" ); + set => Target.Set( "b_sit", value ); + } + + /// + /// Are we on the ground? + /// + public bool IsGrounded + { + get => Target.GetBool( "b_grounded" ); + set => Target.Set( "b_grounded", value ); + } + + /// + /// Are we swimming? + /// + public bool IsSwimming + { + get => Target.GetBool( "b_swim" ); + set => Target.Set( "b_swim", value ); + } + + /// + /// Are we climbing? + /// + public bool IsClimbing + { + get => Target.GetBool( "b_climbing" ); + set => Target.Set( "b_climbing", value ); + } + + /// + /// Are we noclipping? + /// + public bool IsNoclipping + { + get => Target.GetBool( "b_noclip" ); + set => Target.Set( "b_noclip", value ); + } + + /// + /// Is the weapon lowered? By default, this'll happen when the character hasn't been shooting for a while. + /// + public bool IsWeaponLowered + { + get => Target.GetBool( "b_weapon_lower" ); + set => Target.Set( "b_weapon_lower", value ); + } + + public enum HoldTypes + { + None, + Pistol, + Rifle, + Shotgun, + HoldItem, + Punch, + Swing, + RPG + } + + /// + /// What kind of weapon are we holding? + /// + public HoldTypes HoldType + { + get => (HoldTypes)Target.GetInt( "holdtype" ); + set => Target.Set( "holdtype", (int)value ); + } + + public enum Hand + { + Both, + Right, + Left + } + + /// + /// What's the handedness of our weapon? Left handed, right handed, or both hands? This is only supported by some holdtypes, like Pistol, HoldItem. + /// + public Hand Handedness + { + get => (Hand)Target.GetInt( "holdtype_handedness" ); + set => Target.Set( "holdtype_handedness", (int)value ); + } + + /// + /// Triggers a jump animation + /// + [Broadcast] public void TriggerJump() + { + Target.Set( "b_jump", true); + } + + /// + /// Triggers a weapon deploy animation + /// + public void TriggerDeploy() + { + Target.Set( "b_deploy", true ); + } + + public enum MoveStyles + { + Auto, + Walk, + Run + } + + /// + /// We can force the model to walk or run, or let it decide based on the speed. + /// + public MoveStyles MoveStyle + { + get => (MoveStyles)Target.GetInt( "move_style" ); + set => Target.Set( "move_style", (int)value ); + } + + public enum SpecialMoveStyle + { + None, + LedgeGrab, + Roll, + Slide + } + + /// + /// We can force the model to have a specific movement state, instead of just running around. + /// is good for shimmying across a ledge. + /// is good for a platformer game where the character is rolling around continuously. + /// is good for a shooter game or a platformer where the character is sliding. + /// + public SpecialMoveStyle SpecialMove + { + get => (SpecialMoveStyle)Target.GetInt( "special_movement_states" ); + set => Target.Set( "special_movement_states", (int)value ); + } + + //Sitting + public enum SittingStyle + { + None, + Chair, + Floor + } + + /// + /// How are we sitting down? + /// + public SittingStyle Sitting + { + get => (SittingStyle)Target.GetInt( "sit" ); + set => Target.Set( "sit", (int)value ); + } + + /// + /// How far up are we sitting down from the floor? + /// + public float SittingOffsetHeight + { + get => Target.GetFloat( "sit_offset_height" ); + set => Target.Set( "sit_offset_height", value ); + } + + /// + /// From 0-1, how much are we actually sitting down. + /// + public float SittingPose + { + get => Target.GetFloat( "sit_pose" ); + set => Target.Set( "sit_pose", value ); + } +} diff --git a/Code/Kal.cs b/Code/Kal.cs index 3e43bf4..d2ce0d8 100644 --- a/Code/Kal.cs +++ b/Code/Kal.cs @@ -4,8 +4,8 @@ using ShrimpleCharacterController; public sealed class Kal : Component { - [RequireComponent] public ShrimpleCharacterController.ShrimpleCharacterController Controller { get; set; } - [RequireComponent] public CitizenAnimationHelper AnimationHelper { get; set; } + [RequireComponent] private ShrimpleCharacterController.ShrimpleCharacterController Controller { get; set; } + [RequireComponent] private AnimationHelper AnimationHelper { get; set; } public SkinnedModelRenderer Renderer { get; set; } [Property] public GameObject Body { get; set; } public GameObject Camera { get; set; } @@ -16,18 +16,23 @@ public sealed class Kal : Component [Property] [Range(100f, 500f, 20f)] public float RunSpeed { get; set; } = 300f; [Property] [Range(25f, 100f, 5f)] public float DuckSpeed { get; set; } = 50f; [Property] [Range(200f, 500f, 20f)] public float JumpStrength { get; set; } = 350f; + [Sync] public Angles EyeAngles { get; set; } - [Sync] public bool isDucking { get; set; } = false; - [Sync] public bool isRunning { get; set; } = false; + // [Sync] public bool IsDucking { get; set; } + // [Sync] public bool IsRunning { get; set; } protected override void OnStart() { base.OnStart(); + Controller = Components.Get(); + AnimationHelper = Components.Get(); + if ( !Network.IsOwner ) return; Renderer = Components.Get(FindMode.EverythingInSelfAndDescendants); - Camera = new GameObject(true, "Camera"); + Camera = Scene.Camera.GameObject; + Camera.SetParent(GameObject); var cameraComponent = Camera.Components.Create(); cameraComponent.ZFar = 32768f; @@ -40,35 +45,31 @@ public sealed class Kal : Component if ( Network.IsOwner ) { var wishDirection = Input.AnalogMove.Normal * Rotation.FromYaw( EyeAngles.yaw ); - isDucking = Input.Down( "Duck" ); - isRunning = Input.Down( "Run" ); - var wishSpeed = isDucking ? DuckSpeed : - isRunning ? RunSpeed : WalkSpeed; + var IsDucking = Input.Down( "Duck" ); + var IsRunning = Input.Down( "Run" ); + var wishSpeed = IsDucking ? DuckSpeed : + IsRunning ? RunSpeed : WalkSpeed; Controller.WishVelocity = wishDirection * wishSpeed; - + Controller.Move(); if ( Input.Pressed( "Jump" ) && Controller.IsOnGround ) { Controller.Punch( Vector3.Up * JumpStrength * 2f ); - AnimationHelper?.TriggerJump(); + AnimationHelper.TriggerJump(); } - + if ( !AnimationHelper.IsValid() ) return; + AnimationHelper.DuckLevel = IsDucking ? 1f : 0f; } - - UpdateAnimations(); - } - - void UpdateAnimations() - { - AnimationHelper.WithWishVelocity(Controller.WishVelocity); - AnimationHelper.WithVelocity(Controller.Velocity); - AnimationHelper.IsGrounded = Controller.IsOnGround; - AnimationHelper.DuckLevel = isDucking ? 1f : 0f; + Log.Info( AnimationHelper.HeadWeight ); + + AnimationHelper.WithWishVelocity(Controller.WishVelocity); + AnimationHelper.WithVelocity(Controller.Velocity); + AnimationHelper.IsGrounded = Controller.IsOnGround; } protected override void OnUpdate()