- Horseshoe score text should now sync. - Score text on horseshoes will hopefully not slowly drift away any longer. - Chicken sounds now load in background.
294 lines
7.0 KiB
C#
294 lines
7.0 KiB
C#
|
|
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;
|
|
|
|
[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<Animator>();
|
|
}
|
|
|
|
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 (AnimationState != ChickenState.Eat && DistanceToPlayer <= 0.7f)
|
|
{
|
|
SetAnimationState(ChickenState.Eat);
|
|
}
|
|
else if (AnimationState == ChickenState.Eat && DistanceToPlayer >= 0.9f)
|
|
{
|
|
|
|
SetAnimationState(DistanceToPlayer <= 2.75f ? ChickenState.Walk : 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;
|
|
}
|
|
}
|