using UdonSharp; using UnityEngine; using VRC.SDKBase; using VRC.Udon.Common; public enum ChickenState { Idle, Walk, Run, Eat, Held, Airborne } public class Chimken : UdonSharpBehaviour { [SerializeField] private AudioSource _AudioSource; [SerializeField] private TimerSounds _IdleSoundsTimer; [SerializeField] private TimerSounds _HeldSoundsTimer; [SerializeField] private TimerSounds _AirborneSoundsTimer; [Space] [SerializeField] private string _FollowUser = ""; [Space] [SerializeField] private Transform _GroundRayStart; [SerializeField] private Transform _GroundRayEnd; [SerializeField] private Transform _WallRayStart; [SerializeField] private Transform _WallRayEnd; [Space] [SerializeField, Range(0.001f, 2.0f)] private float _WalkSpeed = 0.35f; [SerializeField, Range(0.001f, 2.0f)] private float _RunSpeed = 1.4f; [SerializeField, Range(0.001f, 0.25f)] private float _TurnLerpSpeed = 0.15f; [SerializeField, Range(0.001f, 5.0f)] private float _StartFollowPlayerThreshold = 1.1f; [SerializeField, Range(0.001f, 5.0f)] private float _StartFollowPlayerWalkThreshold = 2.25f; [SerializeField, Range(0.001f, 5.0f)] private float _StartFollowPlayerRunThreshold = 3.75f; [SerializeField, Range(0.001f, 5.0f)] private float _StopFollowPlayerThreshold = 0.7f; [UdonSynced, FieldChangeCallback(nameof(AnimationState))] private ChickenState _AnimationState = 0; [UdonSynced] private bool _IsBeingHeld = false; [UdonSynced] private float _MovementDirectionDegrees = 0.0f; private VRCPlayerApi _PlayerToFollow = null; private bool _Moving = false; private float _MovementTimer = 0.0f; private Animator _Animator = null; private float _WallColliderTimer = 0.0f; void Start() { _Animator = GetComponent(); } void FixedUpdate() { bool Airborne = true; if (Networking.IsOwner(gameObject)) { RaycastHit HitInfo; // Ground collision detection if (_IsBeingHeld) { SetAnimationState(ChickenState.Held); } else if (Physics.Raycast(_GroundRayStart.position, _GroundRayStart.forward, out HitInfo, _GroundRayEnd.localPosition.z)) { transform.position = HitInfo.point; Airborne = false; } else { SetAnimationState(ChickenState.Airborne); } // Wall collision detection _WallColliderTimer -= Time.fixedDeltaTime; if (_WallColliderTimer <= 0.0f && _Moving) { if (Physics.Raycast(_WallRayStart.position, _WallRayStart.forward, out HitInfo, _WallRayEnd.localPosition.z)) { float OldMovementDirection = _MovementDirectionDegrees; Vector3 ReflectedAngle = Vector3.Reflect(_WallRayStart.forward, HitInfo.normal); _MovementDirectionDegrees = Vector3.SignedAngle(Vector3.forward, ReflectedAngle, Vector3.up); } _WallColliderTimer = 0.25f; } } Vector3 PlayerPosition; Vector3 PlayerPositionLateral; GetFollowPlayerPositions(out PlayerPosition, out PlayerPositionLateral); Vector3 ChickenPositionLateral = new Vector3(transform.position.x, 0.0f, transform.position.z); if (!Airborne) { if (_PlayerToFollow != null) { float DistanceToPlayer = Vector3.Distance(ChickenPositionLateral, PlayerPositionLateral); if (DistanceToPlayer <= _StopFollowPlayerThreshold) { SetAnimationState(ChickenState.Eat); } else if (DistanceToPlayer >= _StartFollowPlayerThreshold) { if (DistanceToPlayer <= _StartFollowPlayerWalkThreshold) { SetAnimationState(ChickenState.Walk); } else if (DistanceToPlayer >= _StartFollowPlayerRunThreshold) { SetAnimationState(ChickenState.Run); } } } else { _MovementTimer -= Time.deltaTime; if (_MovementTimer <= 0.0f && Networking.IsOwner(gameObject)) { SetAnimationState((ChickenState)Random.Range(0, 4)); } } } if (_Moving) { switch (_AnimationState) { case ChickenState.Walk: transform.position += (transform.forward * Time.fixedDeltaTime * _WalkSpeed); break; case ChickenState.Run: transform.position += (transform.forward * Time.fixedDeltaTime * _RunSpeed); break; case ChickenState.Airborne: transform.position += (transform.forward * Time.fixedDeltaTime * _RunSpeed); break; } if (_PlayerToFollow == null) { transform.rotation = Quaternion.Euler( transform.eulerAngles.x, Mathf.LerpAngle(transform.rotation.eulerAngles.y, _MovementDirectionDegrees, _TurnLerpSpeed), transform.eulerAngles.z); } else { float Angle = Vector3.SignedAngle(transform.forward, (PlayerPositionLateral - ChickenPositionLateral).normalized, transform.up); transform.rotation = Quaternion.Euler( transform.eulerAngles.x, Mathf.LerpAngle(transform.rotation.eulerAngles.y, transform.rotation.eulerAngles.y + Angle, _TurnLerpSpeed), transform.eulerAngles.z); } } // Reset roll and pitch to zero on every physics update so the chicken stays upright Vector3 Rotation = transform.eulerAngles; Rotation.x = 0.0f; Rotation.z = 0.0f; transform.rotation = Quaternion.Euler(Rotation); } public override void OnPlayerJoined(VRCPlayerApi Player) { if (Player.displayName == _FollowUser) { _PlayerToFollow = Player; } base.OnPlayerJoined(Player); } public override void OnPlayerLeft(VRCPlayerApi Player) { if (Player.displayName == _FollowUser) { _PlayerToFollow = null; } base.OnPlayerLeft(Player); } public override void OnPickup() { Networking.SetOwner(Networking.LocalPlayer, gameObject); SetAnimationState(ChickenState.Held); _IsBeingHeld = true; base.OnPickup(); } public override void OnDrop() { _IsBeingHeld = false; base.OnDrop(); } private void SetAnimationState(ChickenState State) { if (Networking.IsOwner(gameObject) && AnimationState != State) { AnimationState = State; _MovementDirectionDegrees = Random.Range(0.0f, 360.0f - float.MinValue); RequestSerialization(); } } private void OnStateChanged() { _Animator.SetInteger("State", (int)AnimationState); _Moving = false; switch (AnimationState) { case ChickenState.Idle: _MovementTimer = 4.0f; PlayIdleSounds(); break; case ChickenState.Walk: _MovementTimer = 4.5f; _Moving = true; PlayIdleSounds(); break; case ChickenState.Run: _MovementTimer = 1.2f; _Moving = true; PlayIdleSounds(); break; case ChickenState.Eat: _MovementTimer = 6.0f; PlayIdleSounds(); break; case ChickenState.Held: _MovementTimer = 0.0f; PlayHeldSounds(); break; case ChickenState.Airborne: _MovementTimer = 0.0f; _Moving = true; PlayAirborneSounds(); break; } } private bool GetFollowPlayerPositions(out Vector3 PlayerPosition, out Vector3 PlayerPositionLateral) { if (_PlayerToFollow != null) { PlayerPosition = _PlayerToFollow.GetPosition(); PlayerPositionLateral = new Vector3(PlayerPosition.x, 0.0f, PlayerPosition.z); return true; } PlayerPosition = Vector3.zero; PlayerPositionLateral = Vector3.zero; return false; } private void PlayIdleSounds() { _HeldSoundsTimer.StopTimer(); _AirborneSoundsTimer.StopTimer(); _IdleSoundsTimer.StartTimer(); } private void PlayHeldSounds() { _IdleSoundsTimer.StopTimer(); _AirborneSoundsTimer.StopTimer(); _HeldSoundsTimer.StartTimer(); } private void PlayAirborneSounds() { _IdleSoundsTimer.StopTimer(); _HeldSoundsTimer.StopTimer(); _AirborneSoundsTimer.StartTimer(); } private ChickenState AnimationState { set { _AnimationState = value; OnStateChanged(); } get => _AnimationState; } }