using UnityEngine; namespace HotUpdate.Gameplay { /// /// 营地伙伴闲逛控制器:在指定区域内随机漫步、呼吸待机,并在探险家靠近时好奇转身打招呼。 /// public sealed class CampPetWanderer : MonoBehaviour { [Header("Wander Settings")] public Vector3 wanderCenter; public float wanderRadius = 2.8f; public float moveSpeed = 1.6f; public float turnSpeed = 480f; public float stopDistance = 0.35f; [Header("Timing")] public float minIdleTime = 2.5f; public float maxIdleTime = 6.0f; [Header("Social")] public float greetingDistance = 4.8f; [Header("Model Facing Convention")] [Tooltip("既有伙伴动物模型正面为局部 -Z(2026-10-24 之前旧资产规范),开启后使正面朝向移动方向")] public bool modelFacesMinusZ = true; Animator _animator; Transform _playerTransform; Vector3 _targetPosition; bool _isMoving; float _idleTimer; int _animParamMotion; public bool IsMoving => _isMoving; public Vector3 TargetPosition => _targetPosition; public Animator PetAnimator => _animator; void Awake() { EnsureInit(); } public void EnsureInit() { if (_animator == null) _animator = GetComponentInChildren(); if (_animParamMotion == 0) _animParamMotion = Animator.StringToHash("Motion"); if (wanderCenter == Vector3.zero) wanderCenter = transform.position; _targetPosition = transform.position; if (_idleTimer <= 0f) _idleTimer = Random.Range(minIdleTime * 0.5f, maxIdleTime); } void Start() { var explorer = GameObject.Find("Explorer"); if (explorer != null) _playerTransform = explorer.transform; } /// /// 指挥伙伴移动到指定目标点。 /// public void MoveTo(Vector3 target) { EnsureInit(); _targetPosition = target; _isMoving = true; if (_animator != null) _animator.SetInteger(_animParamMotion, 1); } /// /// 停步并进入待机状态。 /// public void Stop() { EnsureInit(); _isMoving = false; _targetPosition = transform.position; _idleTimer = Random.Range(minIdleTime, maxIdleTime); if (_animator != null) _animator.SetInteger(_animParamMotion, 0); } void Update() { float dt = Time.deltaTime; // 探险家靠近时好奇注视 if (_playerTransform != null) { Vector3 toPlayer = _playerTransform.position - transform.position; toPlayer.y = 0; if (toPlayer.sqrMagnitude < greetingDistance * greetingDistance) { if (_isMoving) Stop(); if (toPlayer.sqrMagnitude > 0.001f) { Vector3 lookDir = modelFacesMinusZ ? -toPlayer.normalized : toPlayer.normalized; Quaternion facePlayer = Quaternion.LookRotation(lookDir, Vector3.up); transform.rotation = Quaternion.RotateTowards(transform.rotation, facePlayer, turnSpeed * dt); } return; } } if (_isMoving) { Vector3 toTarget = _targetPosition - transform.position; toTarget.y = 0; float dist = toTarget.magnitude; if (dist <= stopDistance) { Stop(); return; } // 平滑转向并前进(结合模型正向规范) if (toTarget.sqrMagnitude > 0.0001f) { Vector3 lookDir = modelFacesMinusZ ? -toTarget.normalized : toTarget.normalized; Quaternion rot = Quaternion.LookRotation(lookDir, Vector3.up); transform.rotation = Quaternion.RotateTowards(transform.rotation, rot, turnSpeed * dt); } float step = Mathf.Min(moveSpeed * dt, dist); transform.position += toTarget.normalized * step; } else { _idleTimer -= dt; if (_idleTimer <= 0f) { PickNextDestination(); } } } void PickNextDestination() { Vector2 rand2D = Random.insideUnitCircle * wanderRadius; Vector3 dest = wanderCenter + new Vector3(rand2D.x, 0, rand2D.y); // 避开喷泉中心 Vector2 fountainCenter = new Vector2(0f, 0.8f); if (Vector2.Distance(new Vector2(dest.x, dest.z), fountainCenter) < 2.3f) { _idleTimer = 1.0f; return; } MoveTo(dest); } } }