diff --git a/unity-client/Assets/Scripts/AgentVisual.cs b/unity-client/Assets/Scripts/AgentVisual.cs index 8c63d40..b8b14a6 100644 --- a/unity-client/Assets/Scripts/AgentVisual.cs +++ b/unity-client/Assets/Scripts/AgentVisual.cs @@ -82,6 +82,7 @@ namespace TheIsland.Visual private Vector3 _targetPosition; private bool _isMoving; private float _moveSpeed = 3f; + private Vector3 _lastPosition; // UI Smoothing (Phase 19) private float _currentHpPercent; @@ -109,6 +110,7 @@ namespace TheIsland.Visual if (_animator == null) _animator = gameObject.AddComponent(); CreateVisuals(); + _lastPosition = transform.position; } private void Update() @@ -152,17 +154,54 @@ namespace TheIsland.Visual _spriteRenderer.sortingOrder = Mathf.RoundToInt(-transform.position.z * 100); } - // Phase 19-B/D: Use AgentAnimator + // Phase 19-B/D/E: Use AgentAnimator if (_animator != null) { - float currentSpeed = _isMoving ? _moveSpeed : 0; - _animator.SetMovement(currentSpeed, _moveSpeed); + // Calculate world velocity based on position change + Vector3 currentVelocity = (transform.position - _lastPosition) / (Time.deltaTime > 0 ? Time.deltaTime : 0.001f); + _animator.SetMovement(currentVelocity, _moveSpeed); + _lastPosition = transform.position; + } + + // Phase 19-E: Social Orientation (Interaction Facing) + if (!_isMoving) + { + FaceInteractionTarget(); } // Phase 19: Smooth UI Bar Transitions UpdateSmoothBars(); } + private void FaceInteractionTarget() + { + // If the agent is talking or near others, turn to face them + float socialRange = 2.5f; + AgentVisual nearestAgent = null; + float minDist = socialRange; + + var allAgents = FindObjectsByType(FindObjectsSortMode.None); + foreach (var other in allAgents) + { + if (other == this || !other.IsAlive) continue; + float d = Vector3.Distance(transform.position, other.transform.position); + if (d < minDist) + { + minDist = d; + nearestAgent = other; + } + } + + if (nearestAgent != null && _spriteRenderer != null) + { + float dx = nearestAgent.transform.position.x - transform.position.x; + if (Mathf.Abs(dx) > 0.1f) + { + _spriteRenderer.flipX = dx < 0; + } + } + } + private Vector3 CalculateRepulsion() { Vector3 force = Vector3.zero; diff --git a/unity-client/Assets/Scripts/Visual/AgentAnimator.cs b/unity-client/Assets/Scripts/Visual/AgentAnimator.cs index a422177..f4833b6 100644 --- a/unity-client/Assets/Scripts/Visual/AgentAnimator.cs +++ b/unity-client/Assets/Scripts/Visual/AgentAnimator.cs @@ -6,15 +6,17 @@ namespace TheIsland /// /// Procedural 2D animator for agents. /// Handles idle breathing, movement bopping, and action-based squash/stretch. + /// Phase 19-E: Added banking turns and anticipation/overshoot. /// public class AgentAnimator : MonoBehaviour { [Header("Animation Settings")] public float idleSpeed = 2f; - public float idleAmount = 0.05f; + public float idleAmount = 0.04f; public float moveBopSpeed = 12f; - public float moveBopAmount = 0.12f; - public float moveTiltAmount = 8f; + public float moveBopAmount = 0.1f; + public float moveTiltAmount = 10f; + public float bankingAmount = 15f; private SpriteRenderer _spriteRenderer; private Vector3 _originalScale; @@ -22,12 +24,13 @@ namespace TheIsland private Quaternion _targetLocalRot; private Vector3 _targetScale; + private Vector3 _currentVelocity; private float _velocityPercentage; // 0 to 1 private bool _isMoving; + private float _transitionTimer; private void Awake() { - // Find in children if not on this object _spriteRenderer = GetComponentInChildren(); } @@ -41,18 +44,49 @@ namespace TheIsland { _originalScale = Vector3.one; } + _targetScale = _originalScale; } + public void SetMovement(Vector3 velocity, float maxVelocity = 3f) + { + _currentVelocity = velocity; + _velocityPercentage = Mathf.Clamp01(velocity.magnitude / Mathf.Max(0.1f, maxVelocity)); + + bool nowMoving = _velocityPercentage > 0.05f; + if (nowMoving && !_isMoving) + { + // Anticipation: Squash when starting to move + TriggerAnticipation(); + } + else if (!nowMoving && _isMoving) + { + // Overshoot: Rebound when stopping + TriggerOvershoot(); + } + + _isMoving = nowMoving; + } + + // Compatibility for older code public void SetMovement(float currentVelocity, float maxVelocity) { - _velocityPercentage = Mathf.Clamp01(currentVelocity / Mathf.Max(0.1f, maxVelocity)); - _isMoving = _velocityPercentage > 0.05f; + SetMovement(new Vector3(currentVelocity, 0, 0), maxVelocity); } public void TriggerActionEffect() { StopAllCoroutines(); - StartCoroutine(ActionPulseRoutine()); + StartCoroutine(ActionPulseRoutine(0.4f, 1.3f)); + } + + private void TriggerAnticipation() + { + StartCoroutine(ActionPulseRoutine(0.15f, 0.8f)); // Squash + } + + private void TriggerOvershoot() + { + StartCoroutine(ActionPulseRoutine(0.2f, 1.15f)); // Slight stretch } private void Update() @@ -69,9 +103,11 @@ namespace TheIsland } // Smoothly apply transforms - _spriteRenderer.transform.localPosition = Vector3.Lerp(_spriteRenderer.transform.localPosition, _targetLocalPos, Time.deltaTime * 10f); - _spriteRenderer.transform.localRotation = Quaternion.Slerp(_spriteRenderer.transform.localRotation, _targetLocalRot, Time.deltaTime * 10f); - _spriteRenderer.transform.localScale = Vector3.Lerp(_spriteRenderer.transform.localScale, _targetScale, Time.deltaTime * 10f); + float lerpSpeed = 12f; + var t = _spriteRenderer.transform; + t.localPosition = Vector3.Lerp(t.localPosition, _targetLocalPos, Time.deltaTime * lerpSpeed); + t.localRotation = Quaternion.Slerp(t.localRotation, _targetLocalRot, Time.deltaTime * lerpSpeed); + t.localScale = Vector3.Lerp(t.localScale, _targetScale, Time.deltaTime * lerpSpeed); } private void AnimateIdle() @@ -90,34 +126,37 @@ namespace TheIsland float cycle = Time.time * moveBopSpeed; float bop = Mathf.Abs(Mathf.Sin(cycle)) * moveBopAmount * _velocityPercentage; - // Tilt based on the cycle to give a "walking" feel - float tilt = Mathf.Sin(cycle) * moveTiltAmount * _velocityPercentage; + // Traditional Bop Tilt + float bopTilt = Mathf.Sin(cycle) * moveTiltAmount * _velocityPercentage; + + // Phase 19-E: Banking Turn Tilt + // Lean into the direction of X velocity + float bankingTilt = -(_currentVelocity.x / 3f) * bankingAmount; _targetLocalPos = new Vector3(0, bop, 0); - _targetLocalRot = Quaternion.Euler(0, 0, tilt); + _targetLocalRot = Quaternion.Euler(0, 0, bopTilt + bankingTilt); // Squash and stretch during the bop float stretch = 1f + bop; float squash = 1f / stretch; - _targetScale = new Vector3(_originalScale.x * stretch, _originalScale.y * squash, _originalScale.z); + _targetScale = new Vector3(_originalScale.x * squash, _originalScale.y * stretch, _originalScale.z); } - private IEnumerator ActionPulseRoutine() + private IEnumerator ActionPulseRoutine(float duration, float targetScaleY) { float elapsed = 0; - float duration = 0.25f; + Vector3 peakScale = new Vector3(_originalScale.x * (2f - targetScaleY), _originalScale.y * targetScaleY, _originalScale.z); + while (elapsed < duration) { elapsed += Time.deltaTime; - float t = elapsed / duration; - // Double pulse or overshoot - float scale = 1.0f + Mathf.Sin(t * Mathf.PI) * 0.4f; + float progress = elapsed / duration; + float sin = Mathf.Sin(progress * Mathf.PI); - // Override target scale briefly - _spriteRenderer.transform.localScale = new Vector3(_originalScale.x * scale, _originalScale.y * (2f - scale), _originalScale.z); + // Temp override of targetScale for the pulse + _spriteRenderer.transform.localScale = Vector3.Lerp(_originalScale, peakScale, sin); yield return null; } - _targetScale = _originalScale; } } } \ No newline at end of file