This commit is contained in:
Oscar
2025-05-27 00:59:12 +03:00
parent 670b0ade24
commit 37173c8b39
14 changed files with 730 additions and 184 deletions

View File

@@ -0,0 +1,18 @@
public sealed partial class Dedugan
{
private void RotateCamera()
{
if (RagdollController.Enabled)
{
var offset = RagdollController.WorldRotation.Up * 20f - Camera.WorldRotation.Forward * 200f;
Camera.WorldPosition = Vector3.Lerp(Camera.WorldPosition, RagdollController.WorldPosition + offset, Time.Delta * 5f);
Camera.LocalRotation = Rotation.Lerp(Camera.LocalRotation, EyeAngles.ToRotation(), Time.Delta * 2f);
}
else
{
Camera.LocalRotation = EyeAngles.ToRotation();
var offset = CameraPivot.LocalPosition + CameraPivot.LocalRotation.Backward * (CamOffsetX + EyeAngles.pitch * .5f);
Camera.LocalPosition = offset * Camera.LocalRotation;
}
}
}

View File

@@ -0,0 +1,28 @@
public sealed partial class Dedugan
{
private void DrawDebugGizmos()
{
Gizmo.Transform = global::Transform.Zero;
Gizmo.Draw.LineThickness = 2f;
Gizmo.Draw.IgnoreDepth = true;
Gizmo.Draw.Color = Color.Blue;
Gizmo.Draw.Arrow(WorldPosition, WorldPosition + (_up * 1200f));
Gizmo.Draw.Color = Color.Red;
Gizmo.Draw.Arrow(WorldPosition, WorldPosition + (_forward * 1200f));
Gizmo.Draw.Color = Color.Green;
Gizmo.Draw.Arrow(WorldPosition, WorldPosition + (_right * 1200f));
Gizmo.Draw.Color = Color.Black;
Gizmo.Draw.Arrow(WorldPosition, WorldPosition + (-_up * 100f));
Gizmo.Draw.Color = Color.Magenta;
Gizmo.Draw.Arrow(WorldPosition, WorldPosition + _wishDirection * 10f);
var textStartPos = new Vector2(10f);
Gizmo.Draw.ScreenText($"IsOnGround: {Controller.IsOnGround}", textStartPos);
Gizmo.Draw.ScreenText($"IsSlipping: {Controller.IsSlipping}", textStartPos.WithY(30f));
Gizmo.Draw.ScreenText($"WishVelocity: {Controller.WishVelocity}", textStartPos.WithY(50f));
Gizmo.Draw.ScreenText($"Test: {Vector3.Dot(_right, _up)}", textStartPos.WithY(70f));
Gizmo.Draw.ScreenBiasedHalfCircle(WorldPosition, 3f);
}
}

View File

@@ -0,0 +1,302 @@
public sealed partial class Dedugan
{
public Component Pressed { get; set; }
public bool EnablePressing { get; set; } = true;
public Component Hovered { get; set; }
[Sync( SyncFlags.Interpolate )]
public Vector3 TracedHitPos { get; set; }
[Sync]
public bool CameraTraceIsHit { get; set; }
public Vector3 TracedHitNormal { get; set; }
private GameObject interactionPanel;
private static GameObject interactionPanelPrefab;
private TimeSince holdTimer;
private bool isHolding;
private bool triggered;
private const string InteractionPrefabPath = "prefabs/InteractionPanel.prefab";
public void UpdateLookAt()
{
if ( EnablePressing )
{
if ( Pressed.IsValid() )
{
UpdatePressed();
}
else
{
UpdateHovered();
}
}
}
private void UpdatePressed()
{
bool flag = Input.Pressed( "Use" );
if ( flag && Pressed.Components.TryGet<IPressable>( out var pressable ) )
{
if ( pressable.RequiresHold )
{
if ( !isHolding )
{
holdTimer = 0;
isHolding = true;
triggered = false;
}
if ( triggered ) return;
var progress = holdTimer / pressable.HoldTime;
ShowInteractionUI( TracedHitPos, pressable.DisplayText, true, progress );
if ( holdTimer > pressable.HoldTime )
{
triggered = true;
pressable.Pressing( new IPressable.Event
{
Ray = new Ray( Camera.WorldPosition, EyeAngles.ToRotation().Forward ),
Source = this
} );
}
}
else
{
flag = pressable.Pressing( new IPressable.Event
{
Ray = new Ray( Camera.WorldPosition, EyeAngles.ToRotation().Forward ),
Source = this
} );
}
}
if ( GetDistanceFromGameObject( Pressed.GameObject, Camera.WorldPosition ) > InteractDistance )
{
flag = false;
}
if ( !flag )
{
StopPressing();
}
}
private void UpdateHovered()
{
SwitchHovered( TryGetLookedAt() );
if ( Hovered is IPressable pressable )
{
pressable.Look( new IPressable.Event
{
Ray = new Ray( Camera.WorldPosition, EyeAngles.ToRotation().Forward ),
Source = this
} );
ShowInteractionUI( TracedHitPos, pressable.DisplayText );
}
else
{
ClearInteractionUI();
}
if ( Input.Pressed( "use" ) )
{
StartPressing( Hovered );
}
}
public void StartPressing( Component obj )
{
StopPressing();
if ( !obj.IsValid() )
{
ISceneEvent<PlayerController.IEvents>.PostToGameObject( GameObject, x => x.FailPressing() );
return;
}
var component = obj.Components.Get<IPressable>( FindMode.EnabledInSelfAndDescendants );
if ( component != null )
{
if ( !component.CanPress( new IPressable.Event
{
Ray = new Ray( Camera.WorldPosition, EyeAngles.ToRotation().Forward ),
Source = this
} ) )
{
ISceneEvent<PlayerController.IEvents>.PostToGameObject( GameObject, x => x.FailPressing() );
return;
}
component.Press( new IPressable.Event
{
Ray = new Ray( Camera.WorldPosition, EyeAngles.ToRotation().Forward ),
Source = this
} );
}
Pressed = obj;
if ( Pressed.IsValid() )
{
ISceneEvent<PlayerController.IEvents>.PostToGameObject( GameObject, x => x.StartPressing( Pressed ) );
}
}
private Component TryGetLookedAt()
{
for ( float num = 0f; num <= 4f; num += 2f )
{
var from = Scene.Camera.WorldPosition + Scene.Camera.WorldRotation.Forward;
var to = from + Scene.Camera.WorldRotation.Forward * (InteractDistance - num);
var eyeTrace = Scene.Trace.Ray( from, to ).IgnoreGameObjectHierarchy( GameObject ).Radius( num ).Run();
TracedHitPos = eyeTrace.Hit ? eyeTrace.HitPosition : eyeTrace.EndPosition;
CameraTraceIsHit = eyeTrace.Hit;
TracedHitNormal = eyeTrace.Normal;
if ( !eyeTrace.Hit || !eyeTrace.GameObject.IsValid() )
continue;
Component foundComponent = null;
ISceneEvent<PlayerController.IEvents>.PostToGameObject( GameObject, x =>
{
foundComponent = x.GetUsableComponent( eyeTrace.GameObject ) ?? foundComponent;
} );
if ( foundComponent.IsValid() )
return foundComponent;
foreach ( var component in eyeTrace.GameObject.Components.GetAll<IPressable>() )
{
if ( component.CanPress( new IPressable.Event
{
Ray = new Ray( Camera.WorldPosition, EyeAngles.ToRotation().Forward ),
Source = this
} ) )
{
return component as Component;
}
}
}
return null;
}
public void StopPressing()
{
if ( Pressed.IsValid() )
{
ISceneEvent<PlayerController.IEvents>.PostToGameObject( GameObject, x => x.StopPressing( Pressed ) );
if ( Pressed is IPressable pressable )
{
pressable.Release( new IPressable.Event
{
Ray = new Ray( Camera.WorldPosition, EyeAngles.ToRotation().Forward ),
Source = this
} );
}
Pressed = null;
}
isHolding = false;
triggered = false;
ClearInteractionUI();
}
private void SwitchHovered( Component obj )
{
var e = new IPressable.Event
{
Ray = new Ray( Camera.WorldPosition, EyeAngles.ToRotation().Forward ),
Source = this
};
if ( Hovered == obj )
{
if ( Hovered is IPressable pressable )
{
pressable.Look( e );
}
return;
}
if ( Hovered is IPressable pressable2 )
{
pressable2.Blur( e );
Hovered = null;
}
Hovered = obj;
if ( Hovered is IPressable pressable3 )
{
pressable3.Hover( e );
pressable3.Look( e );
}
}
private float GetDistanceFromGameObject( GameObject obj, Vector3 point )
{
var a = obj.WorldPosition;
var b = Camera.WorldPosition;
float minDist = Vector3.DistanceBetween( a, b );
foreach ( var collider in Pressed.GetComponentsInChildren<Collider>() )
{
var closest = collider.FindClosestPoint( Camera.WorldPosition );
var dist = Vector3.DistanceBetween( closest, Camera.WorldPosition );
if ( dist < minDist )
{
minDist = dist;
}
}
return minDist;
}
private void ShowInteractionUI( Vector3 position, string text, bool showProgress = false, float progress = 0f )
{
if ( interactionPanelPrefab == null )
{
interactionPanelPrefab = GameObject.GetPrefab( InteractionPrefabPath );
if ( interactionPanelPrefab == null ) return;
}
if ( !interactionPanel.IsValid() )
{
interactionPanel = interactionPanelPrefab.Clone( GameObject.Scene );
}
interactionPanel.Transform.Position = position;
interactionPanel.Transform.Rotation = Rotation.LookAt( Camera.WorldPosition - position );
var panel = interactionPanel.GetComponent<PanelComponent>()?.GetPanel();
if ( panel is not null )
{
panel.SetProperty( "InteractionString", text );
panel.SetProperty( "IsHoldInteraction", showProgress );
panel.SetProperty( "ProgressionHold", progress );
}
}
private void ClearInteractionUI()
{
if ( interactionPanel.IsValid() )
{
interactionPanel.Destroy();
interactionPanel = null;
}
}
}

View File

@@ -0,0 +1,55 @@
public sealed partial class Dedugan
{
private void UpdateMovement()
{
_directionToAxis = OverrideGravity == Vector3.Zero
? Vector3.VectorPlaneProject(WorldPosition, Vector3.Right).Normal
: OverrideGravity;
_up = -_directionToAxis;
_forward = Vector3.Right;
_right = Vector3.Cross(_up, _forward).Normal;
Controller.Up = _up;
Controller.VectorGravity = -_up * 850f;
if (Network.IsOwner)
{
LookAtSurfaceNormal(_up, _forward);
_wishDirection = Input.AnalogMove.Normal * Rotation.FromYaw(EyeAngles.yaw) * WorldRotation;
var isDucking = Input.Down("Duck");
var isRunning = Input.Down("Run");
var wishSpeed = isDucking ? DuckSpeed : isRunning ? RunSpeed : WalkSpeed;
var ragdollMul = RagdollController.Enabled ? 0f : 1f;
Controller.WishVelocity = _wishDirection * wishSpeed * ragdollMul;
if (Input.Pressed("Jump") && Controller.IsOnGround)
{
Controller.Punch(-Controller.AppliedGravity.Normal * JumpStrength);
AnimationHelper?.TriggerJump();
}
IsDucking = Input.Down("Duck") ? 1f : 0f;
}
if (!RagdollController.Enabled)
Controller.Move();
else
Controller.Velocity = 0;
if (!AnimationHelper.IsValid()) return;
AnimationHelper.DuckLevel = IsDucking;
AnimationHelper.WithWishVelocity(Controller.WishVelocity);
AnimationHelper.WithVelocity(Controller.Velocity);
AnimationHelper.IsGrounded = Controller.IsOnGround;
}
private void LookAtSurfaceNormal(Vector3 up, Vector3 moveDirection)
{
var newRotation = Rotation.LookAt(moveDirection, up);
WorldRotation = Rotation.Lerp(WorldRotation, newRotation, Time.Delta * 10f);
}
}

View File

@@ -3,169 +3,56 @@ using Sandbox;
using Sandbox.Citizen;
using ShrimpleCharacterController;
public sealed class Dedugan : Component
public sealed partial class Dedugan : Component
{
[RequireComponent] public ShrimpleCharacterController.ShrimpleCharacterController Controller { get; set; }
[RequireComponent] public CitizenAnimationHelper AnimationHelper { get; set; }
public SkinnedModelRenderer Renderer { get; set; }
public GameObject Camera { get; set; }
[Property] public GameObject CameraPivot { get; set; }
[Property][Range(1f, 200f, 1f)] public float CamOffsetX { get; set; }
[Property] public GameObject CameraPivot { get; set; }
[Property] [Range(50f, 1200f, 10f)] public float WalkSpeed { get; set; } = 100f;
[Property] [Range(100f, 1500f, 20f)] public float RunSpeed { get; set; } = 300f;
[Property] [Range(25f, 1100f, 5f)] public float DuckSpeed { get; set; } = 50f;
[Property] [Range(200f, 1500f, 20f)] public float JumpStrength { get; set; } = 350f;
[Property][Range(50f, 1200f, 10f)] public float WalkSpeed { get; set; } = 100f;
[Property][Range(100f, 1500f, 20f)] public float RunSpeed { get; set; } = 300f;
[Property][Range(25f, 1100f, 5f)] public float DuckSpeed { get; set; } = 50f;
[Property][Range(200f, 1500f, 20f)] public float JumpStrength { get; set; } = 350f;
[Property][Range(10f, 500f, 10f)] public float InteractDistance { get; set; } = 350f;
[Sync] public Angles NetworkedEyeAngles { get; set; } // для передачи углов другим клиентам
private RagdollController RagdollController { get; set; }
[Sync] public Angles NetworkedEyeAngles { get; set; }
public Angles EyeAngles { get; set; }
[Sync] private float IsDucking { get; set; } = 0f;
private RagdollController RagdollController { get; set; }
public Vector3 OverrideGravity { get; set; } = Vector3.Zero;
private Vector3 _directionToAxis = Vector3.Up;
private Vector3 _up = Vector3.Up;
private Vector3 _forward = Vector3.Forward;
private Vector3 _right = Vector3.Right;
[Sync] private float IsDucking { get; set; } = 0f;
private Vector3 wishDirection;
private Vector3 _wishDirection;
protected override void OnStart()
{
base.OnStart();
RagdollController = Components.Get<RagdollController>();
Renderer = Components.Get<SkinnedModelRenderer>(FindMode.EverythingInSelfAndDescendants);
if (!Network.IsOwner) return;
// var cameraComponent = GameObject.GetComponentInParent<CameraComponent>(true, true) ;//new GameObject(true, "Camera");
var cameraComponent = Scene.Camera ;//new GameObject(true, "Camera");
var cameraComponent = Scene.Camera;
Camera = cameraComponent.GameObject;
Camera.SetParent(GameObject);
// var cameraComponent = Camera.Components.Create<CameraComponent>();
cameraComponent.ZFar = 32768f;
cameraComponent.FieldOfView = 100f;
}
protected override void DrawGizmos()
{
base.DrawGizmos();
Gizmo.Transform = global::Transform.Zero;
Gizmo.Draw.LineThickness = 2f;
Gizmo.Draw.IgnoreDepth = true;
Gizmo.Draw.Color = Color.Blue;
Gizmo.Draw.Arrow(WorldPosition, WorldPosition + (_up * 1200f));
Gizmo.Draw.Color = Color.Red;
Gizmo.Draw.Arrow(WorldPosition, WorldPosition + (_forward * 1200f));
Gizmo.Draw.Color = Color.Green;
Gizmo.Draw.Arrow(WorldPosition, WorldPosition + (_right * 1200f));
Gizmo.Draw.Color = Color.Black;
Gizmo.Draw.Arrow(WorldPosition, WorldPosition + (-_up * 100f));
Gizmo.Draw.Color = Color.Magenta;
Gizmo.Draw.Arrow(WorldPosition, WorldPosition + wishDirection * 10f);
var textStartPos = new Vector2(10f);
Gizmo.Draw.ScreenText($"IsOnGround: {Controller.IsOnGround}", textStartPos);
Gizmo.Draw.ScreenText($"IsSlipping: {Controller.IsSlipping}", textStartPos.WithY(30f));
Gizmo.Draw.ScreenText($"WishVelocity: {Controller.WishVelocity}", textStartPos.WithY(50f));
Gizmo.Draw.ScreenText($"Test: {Vector3.Dot(_right, _up)}", textStartPos.WithY(70f));
Gizmo.Draw.ScreenBiasedHalfCircle(WorldPosition, 3f);
}
protected override void OnFixedUpdate()
{
base.OnFixedUpdate();
if ( OverrideGravity == Vector3.Zero )
{
_directionToAxis = Vector3.VectorPlaneProject(WorldPosition, Vector3.Right).Normal;
}
else
{
_directionToAxis = OverrideGravity;
}
_up = -_directionToAxis;
_forward = Vector3.Right;
_right = Vector3.Cross(_up, _forward).Normal;
Controller.Up = _up;
Controller.VectorGravity = -_up * 850f;
if (Network.IsOwner)
{
LookAtSurfaceNormal(_up, _forward);
wishDirection = Input.AnalogMove.Normal * Rotation.FromYaw(EyeAngles.yaw) * WorldRotation;
var isDucking = Input.Down("Duck");
var isRunning = Input.Down("Run");
var wishSpeed = isDucking ? DuckSpeed :
isRunning ? RunSpeed : WalkSpeed;
var ragdollMul = RagdollController.Enabled ? 0f : 1f;
Controller.WishVelocity = wishDirection * wishSpeed * ragdollMul;
if (Input.Pressed("Jump") && Controller.IsOnGround)
{
Controller.Punch(-Controller.AppliedGravity.Normal * JumpStrength);
AnimationHelper?.TriggerJump();
}
if (!AnimationHelper.IsValid()) return;
IsDucking = Input.Down("Duck") ? 1f : 0f;
}
if ( !RagdollController.Enabled )
{
Controller.Move();
}
else
{
Controller.Velocity = 0;
}
if (!AnimationHelper.IsValid()) return;
AnimationHelper.DuckLevel = IsDucking;
AnimationHelper.WithWishVelocity(Controller.WishVelocity);
AnimationHelper.WithVelocity(Controller.Velocity);
AnimationHelper.IsGrounded = Controller.IsOnGround;
}
private void LookAtSurfaceNormal(Vector3 up, Vector3 moveDirection)
{
var newRotation = Rotation.LookAt(moveDirection, up);
WorldRotation = Rotation.Lerp(WorldRotation, newRotation, Time.Delta * 10f);
}
protected override void OnUpdate()
{
base.OnUpdate();
if (Network.IsOwner)
{
EyeAngles += Input.AnalogLook;
EyeAngles = EyeAngles.WithPitch(MathX.Clamp(EyeAngles.pitch, -89f, 89f));
NetworkedEyeAngles = EyeAngles;
RotateCamera();
var targetRotation = Rotation.LookAt(Rotation.FromYaw(EyeAngles.yaw).Forward, -_directionToAxis);
var currentForward = Renderer.LocalRotation.Forward;
@@ -173,20 +60,13 @@ public sealed class Dedugan : Component
if (angleDiff > 15f && Controller.Velocity.Length > 10f)
{
NetworkedEyeAngles = EyeAngles;
Renderer.LocalRotation = Rotation.Slerp(Renderer.LocalRotation, Rotation.FromYaw(EyeAngles.yaw), Time.Delta * 3f);
}
if ( Input.Pressed( "Use" ) )
{
var tr = Scene.Trace
.Ray( Camera.WorldPosition, Camera.WorldPosition + Camera.WorldRotation.Forward * 500f ).IgnoreGameObjectHierarchy(GameObject).Run();
if ( tr.Hit)
{
tr.GameObject.GetComponent<IInteractable>()?.OnUse();
}
}
RotateCamera();
UpdateLookAt();
// UpdatePressed();
}
else
{
@@ -195,52 +75,13 @@ public sealed class Dedugan : Component
}
}
void RotateCamera()
protected override void OnFixedUpdate()
{
if ( RagdollController.Enabled )
{
var cameraOffset = RagdollController.WorldRotation.Up * 20f - Camera.WorldRotation.Forward * 200f;//RagdollController.LocalPosition + RagdollController.LocalRotation.Backward * (CamOffsetX + EyeAngles.pitch * .5f);
Camera.WorldPosition = Vector3.Lerp( Camera.WorldPosition, RagdollController.WorldPosition + cameraOffset, Time.Delta * 5f);
Camera.LocalRotation = Rotation.Lerp(Camera.LocalRotation, EyeAngles.ToRotation(), Time.Delta * 2f);
}
else
{
Camera.LocalRotation = EyeAngles.ToRotation();
var cameraOffset = CameraPivot.LocalPosition + CameraPivot.LocalRotation.Backward * (CamOffsetX + EyeAngles.pitch * .5f);
Camera.LocalPosition = cameraOffset * Camera.LocalRotation;
}
UpdateMovement();
}
// void RotateCamera()
// {
// // 1. Задание локального вращения камеры
// Rotation camRot = EyeAngles.ToRotation();
// Camera.LocalRotation = camRot;
//
// // 2. Позиция Pivot'а в мире (нужно для трейса)
// var pivotWorldPos = CameraPivot.LocalPosition;
//
// // 3. Смещение плеча (локально → в мир)
// var shoulderOffsetWorld = Vector3.Zero;
//
// // 4. Желаемая мировая позиция камеры
// var desiredWorldPos = pivotWorldPos - camRot.Forward * 10f + shoulderOffsetWorld;
//
// // 5. Трейс от Pivot до желаемой позиции камеры
// var tr = Scene.Trace
// .Ray(pivotWorldPos, desiredWorldPos)
// .Radius(4f)
// .IgnoreGameObjectHierarchy(GameObject)
// .Run();
//
// // 6. Получаем локальную позицию относительно CameraPivot
// var finalWorldCamPos = tr.EndPosition;
// var finalLocalCamPos = CameraPivot.Transform.WorldToLocal.Transform(finalWorldCamPos);
//
// // 7. Применяем к камере
// Camera.LocalPosition = finalLocalCamPos;
// Camera.LocalRotation = camRot;
// }
protected override void DrawGizmos()
{
DrawDebugGizmos();
}
}

View File

@@ -0,0 +1,45 @@
namespace Sandbox;
[Icon( "skip_next" )]
public sealed class MusicPlayerNextButton : Component, Component.IPressable
{
[Property] public string Label { get; set; } = "Next track";
public bool Press( Component.IPressable.Event e )
{
Log.Info( $"Press от {e.Source}" );
return true;
}
public void Hover( Component.IPressable.Event e )
{
Log.Info( $"Hover от {e.Source}" );
}
public void Look( Component.IPressable.Event e )
{
Log.Info( "Look..." );
}
public void Blur( Component.IPressable.Event e )
{
Log.Info( "Blur — игрок отвёл взгляд" );
}
public void Release( Component.IPressable.Event e )
{
Log.Info( "Release — игрок отпустил кнопку" );
}
public bool Pressing( Component.IPressable.Event e )
{
// возвращаем true, чтобы удержание продолжалось
return true;
}
public bool CanPress( Component.IPressable.Event e )
{
// например, можно сделать: return !isCoolingDown
return true;
}
}

View File

@@ -0,0 +1,26 @@
@using System.Threading.Tasks
@inherits PanelComponent
<root class="interaction-panel">
<div class="label">@InteractionString</div>
@if (IsHoldInteraction)
{
<div class="progress-bar">
<div class="progress-fill" style="width: @(ProgressionHold * 100)%"></div>
</div>
}
</root>
@code {
[Property] public string InteractionString { get; set; } = "Interact";
[Property] public bool IsHoldInteraction { get; set; } = false;
[Property] public float ProgressionHold { get; set; } = 0f;
public async Task TriggerInteractAnimation()
{
AddClass("interacted");
await Task.Delay(300);
RemoveClass("interacted");
}
}

View File

@@ -0,0 +1,13 @@
namespace Sandbox.UI;
public class HoverInfoPanelBase : WorldPanel
{
[Property] public string Label { get; set; } = "Описание";
[Property] public bool Visible { get; set; } = false;
public HoverInfoPanelBase(SceneWorld world) : base(world)
{
PanelBounds = new Rect(-200, -100, 400, 200);
WorldScale = 0.05f;
}
}

View File

@@ -0,0 +1,24 @@
.interaction-panel {
padding: 8px;
background-color: rgba(0,0,0,0.7);
border-radius: 6px;
color: white;
font-size: 16px;
.label {
margin-bottom: 4px;
}
.progress-bar {
width: 100px;
height: 8px;
background-color: #333;
border-radius: 4px;
.progress-fill {
height: 100%;
background-color: limegreen;
border-radius: 4px;
}
}
&.interacted {
background-color: rgba(0, 128, 255, 0.6);
}
}