using CameraSystem; using MMMaellon.LightSync; using System.Linq; using UdonSharp; using UnityEngine; using VRC.SDKBase; using VRC.Udon.Common; public enum PossibleFollowMethods { CloseUp, HeadAndShoulders, HipsUp, HipsDown, FullBody }; [UdonBehaviourSyncMode(BehaviourSyncMode.Manual)] public class CameraAnchor : UdonSharpBehaviour { [SerializeField] private CameraSystem_Console CameraSystemManager; [Space] [SerializeField] private float FOV = 60.0f; [SerializeField] private float NearClippingPlane = 0.3f; [SerializeField] private float FarClippingPlane = 1000.0f; [Space] [SerializeField] private Transform CameraRoot; [Tooltip("Changing the Z scale of this object will change the FOV of the attached camera.")] [SerializeField] private Transform CameraFOVScaler; [Space] [Tooltip("How the camera should follow players, if it's set to do so.")] [SerializeField] private PossibleFollowMethods FollowMethod; [UdonSynced] private int _AttachedCameraIndex = -1; [UdonSynced] private string[] _FollowedPlayerNames = new string[0]; private Camera _AttachedCamera = null; private string[] _FollowedPlayerNames_Cache = new string[0]; private VRCPlayerApi[] _FollowedPlayers = new VRCPlayerApi[0]; private float _CameraFollowSpeed = 1.0f; void Update() { if (_AttachedCamera) { if (_AttachedCamera.transform.parent != CameraRoot) { Debug.Log("[CameraAnchor] Camera has been detached from " + gameObject.name + "."); _AttachedCamera = null; _AttachedCameraIndex = -1; RequestSerialization(); } else { _AttachedCamera.fieldOfView = FOV * CameraFOVScaler.transform.localScale.z; } } if (_FollowedPlayers.Length > 0) { Vector3 CentroidSum = Vector3.zero; for (int i = 0; i < _FollowedPlayers.Length; i++) { if (_FollowedPlayers[i] == null) { continue; } Vector3[] BonePositions = _BonesFromFollowMethod(_FollowedPlayers[i]); Vector3 CentroidPointsAverage = Vector3.zero; for (int j = 0; j < BonePositions.Length; j++) { CentroidPointsAverage += BonePositions[j]; } CentroidPointsAverage /= BonePositions.Length; CentroidSum += CentroidPointsAverage; } Vector3 CentroidAverage = CentroidSum / _FollowedPlayers.Length; Vector3 LookDirection = (CentroidAverage - transform.position).normalized; CameraRoot.transform.rotation = Quaternion.LookRotation( Vector3.Lerp( CameraRoot.transform.forward, Vector3.RotateTowards(CameraRoot.transform.forward, LookDirection, 10.0f, 0.0f), _CameraFollowSpeed / 10.0f) ); } else { CameraRoot.transform.localRotation = Quaternion.identity; } } private Vector3[] _BonesFromFollowMethod(VRCPlayerApi Player) { switch(FollowMethod) { case PossibleFollowMethods.CloseUp: { Vector3[] BonesToTrack = new Vector3[3]; BonesToTrack[0] = Player.GetBonePosition(HumanBodyBones.LeftEye); BonesToTrack[1] = Player.GetBonePosition(HumanBodyBones.RightEye); BonesToTrack[2] = Player.GetBonePosition(HumanBodyBones.Neck); return BonesToTrack; } case PossibleFollowMethods.HeadAndShoulders: { Vector3[] BonesToTrack = new Vector3[4]; BonesToTrack[0] = Player.GetBonePosition(HumanBodyBones.LeftEye); BonesToTrack[1] = Player.GetBonePosition(HumanBodyBones.RightEye); BonesToTrack[2] = Player.GetBonePosition(HumanBodyBones.LeftShoulder); BonesToTrack[3] = Player.GetBonePosition(HumanBodyBones.RightShoulder); return BonesToTrack; } case PossibleFollowMethods.HipsUp: { Vector3[] BonesToTrack = new Vector3[4]; BonesToTrack[0] = Player.GetBonePosition(HumanBodyBones.LeftEye); BonesToTrack[1] = Player.GetBonePosition(HumanBodyBones.RightEye); BonesToTrack[2] = Player.GetBonePosition(HumanBodyBones.LeftUpperLeg); BonesToTrack[3] = Player.GetBonePosition(HumanBodyBones.RightUpperLeg); return BonesToTrack; } case PossibleFollowMethods.HipsDown: { Vector3[] BonesToTrack = new Vector3[4]; BonesToTrack[0] = Player.GetBonePosition(HumanBodyBones.LeftUpperLeg); BonesToTrack[1] = Player.GetBonePosition(HumanBodyBones.RightUpperLeg); BonesToTrack[2] = Player.GetBonePosition(HumanBodyBones.LeftFoot); BonesToTrack[3] = Player.GetBonePosition(HumanBodyBones.RightFoot); return BonesToTrack; } case PossibleFollowMethods.FullBody: { Vector3[] BonesToTrack = new Vector3[4]; BonesToTrack[0] = Player.GetBonePosition(HumanBodyBones.LeftEye); BonesToTrack[1] = Player.GetBonePosition(HumanBodyBones.RightEye); BonesToTrack[2] = Player.GetBonePosition(HumanBodyBones.LeftFoot); BonesToTrack[3] = Player.GetBonePosition(HumanBodyBones.RightFoot); return BonesToTrack; } default: { Vector3[] BonesToTrack = new Vector3[1]; BonesToTrack[0] = Player.GetPosition(); return BonesToTrack; } } } public override void OnDeserialization(DeserializationResult Result) { _AttachCamera_Synced(); _FollowPlayers_Synced(); base.OnDeserialization(Result); } public override void OnPlayerLeft(VRCPlayerApi LeavingPlayer) { //foreach (VRCPlayerApi FollowedPlayer in _FollowedPlayers) //{ // if (FollowedPlayer.displayName == LeavingPlayer.displayName) // { // } //} base.OnPlayerLeft(LeavingPlayer); } public void AttachCamera(Camera CameraComponent) { if (CameraSystemManager == null) { Debug.LogError("[CameraAnchor] No CameraSystemManager set for " + gameObject.name + "; can't find a camera without it"); return; } for (int i = 0; i < CameraSystemManager.camerasObjects.Length; i++) { if (CameraSystemManager.camerasObjects[i] == CameraComponent) { Debug.Log("[CameraAnchor] Attaching camera " + i + " to anchor " + gameObject.name); _AttachedCameraIndex = i; _AttachCamera_Synced(); RequestSerialization(); break; } } } public void FollowPlayers(string[] Players, float FollowSpeed = 1.0f) { _FollowedPlayerNames = Players; _CameraFollowSpeed = FollowSpeed; _FollowPlayers_Synced(); RequestSerialization(); } public void StopFollowingPlayers() { _FollowedPlayerNames = new string[0]; _FollowPlayers_Synced(); RequestSerialization(); } private void _AttachCamera_Synced() { if (_AttachedCameraIndex >= 0) { Camera NewCamera = CameraSystemManager.camerasObjects[_AttachedCameraIndex]; if (NewCamera != _AttachedCamera) { _AttachedCamera = NewCamera; _AttachedCamera.gameObject.SetActive(true); _AttachedCamera.fieldOfView = FOV; _AttachedCamera.nearClipPlane = NearClippingPlane; _AttachedCamera.farClipPlane = FarClippingPlane; _AttachedCamera.transform.parent = CameraRoot; _AttachedCamera.GetComponent().TeleportToLocalSpace(Vector3.zero, Quaternion.identity, true); } } } private void _FollowPlayers_Synced() { if (_IsPlayerListDifferent()) { _FollowedPlayers = new VRCPlayerApi[_FollowedPlayerNames.Length]; VRCPlayerApi[] AllPlayers = new VRCPlayerApi[VRCPlayerApi.GetPlayerCount()]; VRCPlayerApi.GetPlayers(AllPlayers); int NumValidPlayers = 0; for (int i = 0; i < _FollowedPlayerNames.Length; i++) { string PlayerName = _FollowedPlayerNames[i]; Debug.Log("[CameraAnchor] Trying to find player " + PlayerName + "..."); _FollowedPlayers[i] = null; foreach (VRCPlayerApi Player in AllPlayers) { if (Player.displayName == PlayerName) { Debug.Log("[CameraAnchor] Found player " + PlayerName); _FollowedPlayers[i] = Player; NumValidPlayers++; break; } } } Debug.Log("[CameraAnchor] Found " + NumValidPlayers + " players out of a requested " + _FollowedPlayerNames.Length); int ValidPlayerCounter = 0; VRCPlayerApi[] ValidPlayers = new VRCPlayerApi[NumValidPlayers]; foreach (VRCPlayerApi Player in _FollowedPlayers) { if (Player != null) { ValidPlayers[ValidPlayerCounter] = Player; ValidPlayerCounter++; } } string PlayerNamesString = string.Join(", ", _FollowedPlayerNames); Debug.Log("[CameraAnchor] Anchor " + gameObject.name + " is now following { " + PlayerNamesString + " }"); _FollowedPlayerNames_Cache = _FollowedPlayerNames; } } private bool _IsPlayerListDifferent() { if (_FollowedPlayerNames.Length != _FollowedPlayerNames_Cache.Length) { return true; } for (int i = 0; i < _FollowedPlayerNames.Length; i++) { if (_FollowedPlayerNames[i] != _FollowedPlayerNames_Cache[i]) { return true; } } return false; } #if UNITY_EDITOR private void OnDrawGizmos() { Gizmos.DrawIcon(transform.position, "CameraAnchor", true); } #endif }