using System; using UnityEngine; using UnityEngine.UI; using VRC.SDKBase; using System.Reflection; // Noodle bowl defines its own button class, which would break CyanEmu. // Ensuring button is properly defined with this using statement. using UIButton = UnityEngine.UI.Button; #if UDON using VRC.Udon; using VRC.Udon.Common; #endif #if UNITY_POST_PROCESSING_STACK_V2 using UnityEngine.Rendering.PostProcessing; #endif namespace VRCPrefabs.CyanEmu { [AddComponentMenu("")] public class CyanEmuPlayerController : MonoBehaviour { public static CyanEmuPlayerController instance; private enum Stance { STANDING, CROUCHING, PRONE, SITTING } public const float DEFAULT_RUN_SPEED_ = 4; public const float DEFAULT_WALK_SPEED_ = 2; private const float CROUCH_SPEED_MULTIPLYER_ = 0.35f; private const float PRONE_SPEED_MULTIPLYER_ = 0.15f; private const float STANDING_HEIGHT_ = 1.6f; private const float CROUCHING_HEIGHT_ = 1.0f; private const float PRONE_HEIGHT_ = 0.5f; private const float SITTING_HEIGHT_ = 0.88f; private const float STICK_TO_GROUND_FORCE_ = 2f; private const float RATE_OF_AIR_ACCELERATION_ = 5f; // TODO, make based on avatar armspan/settings private const float AVATAR_SCALE_ = 1.13f; private readonly KeyCode MenuKey = KeyCode.Escape; private GameObject playspace_; private GameObject playerCamera_; private GameObject rightArmPosition_; private GameObject leftArmPosition_; private Rigidbody rightArmRigidbody_; private Rigidbody leftArmRigidbody_; private GameObject menu_; private Transform cameraProxyObject_; private Rigidbody rigidbody_; private VRC_SceneDescriptor descriptor_; private CharacterController characterController_; private Camera camera_; private MouseLook mouseLook_; private CyanEmuStationHelper currentStation_; private CyanEmuPickupHelper currentPickup_; private CyanEmuBaseInput baseInput_; private CyanEmuInteractHelper interactHelper_; private CyanEmuPlayer player_; private Stance stance_; private bool isDead_; private bool isImmobile_; private bool isWalking_; private float walkSpeed_ = DEFAULT_WALK_SPEED_; private float strafeSpeed_ = DEFAULT_WALK_SPEED_; private float runSpeed_ = DEFAULT_RUN_SPEED_; private float jumpSpeed_; private bool jump_; private float gravityStrength_ = 1f; //Only used to prevent sliding without changing the input manager. private Vector2 prevInput_; private Vector2 prevInputResult_; private bool velSet; private Vector3 playerRetainedVelocity_; private CollisionFlags collisionFlags_; private bool peviouslyGrounded_; private bool legacyLocomotion_; private bool updateStancePosition_; private Texture2D reticleTexture_; // Used for determining pickup throw private Vector3 prevousHandPosition_; private Vector3 prevousHandRotation_; public static Camera GetPlayerCamera() { if (instance != null) { return instance.GetCamera(); } return Camera.main; } private void Awake() { if (instance != null) { this.LogError("Player controller instance already exists!"); DestroyImmediate(this); return; } #if VRC_SDK_VRCSDK2 legacyLocomotion_ = true; #endif instance = this; descriptor_ = FindObjectOfType(); gameObject.layer = LayerMask.NameToLayer("PlayerLocal"); gameObject.tag = "Player"; rigidbody_ = gameObject.AddComponent(); rigidbody_.isKinematic = true; characterController_ = gameObject.AddComponent(); characterController_.slopeLimit = 50; characterController_.stepOffset = .5f; characterController_.skinWidth = 0.005f; characterController_.minMoveDistance = 0; characterController_.center = new Vector3(0, 0.8f, 0); characterController_.radius = 0.2f; characterController_.height = 1.6f; GameObject capsule = GameObject.CreatePrimitive(PrimitiveType.Capsule); capsule.transform.localScale = new Vector3(0.4f, 1, 0.4f); capsule.transform.SetParent(transform, false); capsule.transform.localPosition = new Vector3(0, 1, 0); capsule.layer = LayerMask.NameToLayer("MirrorReflection"); DestroyImmediate(capsule.GetComponent()); playerCamera_ = new GameObject("Player Camera"); GameObject cameraHolder = new GameObject("CameraHolder"); cameraHolder.transform.SetParent(playerCamera_.transform, false); camera_ = cameraHolder.AddComponent(); camera_.cullingMask &= ~(1 << 18); // remove mirror reflection updateStancePosition_ = false; // TODO, make based on avatar armspan/settings cameraHolder.transform.localScale = Vector3.one * AVATAR_SCALE_; playerCamera_.AddComponent(); playerCamera_.transform.SetParent(transform, false); playerCamera_.transform.localPosition = new Vector3(0, STANDING_HEIGHT_, .1f); playerCamera_.transform.localRotation = Quaternion.identity; playspace_ = new GameObject("Playspace Center"); playspace_.transform.SetParent(transform, false); playspace_.transform.localPosition = new Vector3(-1, 0, -1); playspace_.transform.localRotation = Quaternion.Euler(0, 45, 0); rightArmPosition_ = new GameObject("Right Arm Position"); rightArmPosition_.transform.SetParent(playerCamera_.transform, false); rightArmPosition_.transform.localPosition = new Vector3(0.2f, -0.2f, 0.75f); rightArmPosition_.transform.localRotation = Quaternion.Euler(-45, 0, -90); rightArmRigidbody_ = rightArmPosition_.AddComponent(); rightArmRigidbody_.isKinematic = true; leftArmPosition_ = new GameObject("Left Arm Position"); leftArmPosition_.transform.SetParent(playerCamera_.transform, false); leftArmPosition_.transform.localPosition = new Vector3(-0.2f, -0.2f, 0.75f); leftArmPosition_.transform.localRotation = Quaternion.Euler(-45, 0, -90); leftArmRigidbody_ = leftArmPosition_.AddComponent(); leftArmRigidbody_.isKinematic = true; mouseLook_ = new MouseLook(); mouseLook_.Init(transform, playerCamera_.transform); stance_ = Stance.STANDING; baseInput_ = transform.parent.gameObject.GetComponent(); CreateMenu(); GameObject interactHelper = new GameObject("InteractHelper"); interactHelper.transform.SetParent(transform.parent, false); interactHelper_ = interactHelper.AddComponent(); Func shouldCheckForInteracts = () => { return currentPickup_ == null && !menu_.activeInHierarchy && !isDead_; }; interactHelper_.Initialize(playerCamera_.transform, playerCamera_.transform, shouldCheckForInteracts); reticleTexture_ = Resources.Load("Images/Reticle"); cameraProxyObject_ = new GameObject("CameraDamageProxy").transform; cameraProxyObject_.SetParent(CyanEmuMain.GetProxyObjectTransform(), false); UpdateCameraProxyPosition(); // experimental! //interactHelper_.highlightManager = playerCamera_.AddComponent(); } private void Start() { Camera refCamera = null; if (descriptor_.ReferenceCamera != null) { refCamera = descriptor_.ReferenceCamera.GetComponent(); } if (refCamera == null) { refCamera = Camera.main; } if (refCamera == null) { GameObject mainCamera = GameObject.FindGameObjectWithTag("MainCamera"); if (mainCamera != null) { refCamera = mainCamera.GetComponent(); } } CopyCameraValues(refCamera, camera_); if (CyanEmuCombatSystemHelper.instance != null) { CyanEmuCombatSystemHelper.instance.CreateVisualDamage(); } // Go through all ui shapes to update canvas cameras? foreach (var ui in FindObjectsOfType()) { Canvas canvas = ui.GetComponent(); if (canvas == null) continue; canvas.worldCamera = camera_; } #if VRC_SDK_VRCSDK2 CyanEmuPlayerModsHelper.ApplyRoomMods(this); #endif } public static void CopyCameraValues(Camera refCamera, Camera camera) { if (refCamera != null) { camera.farClipPlane = refCamera.farClipPlane; camera.nearClipPlane = Mathf.Clamp(refCamera.nearClipPlane, 0.01f, 0.02f); camera.clearFlags = refCamera.clearFlags; camera.backgroundColor = refCamera.backgroundColor; camera.tag = "MainCamera"; #if UNITY_POST_PROCESSING_STACK_V2 PostProcessLayer refPostProcessLayer = refCamera.GetComponent(); if (refPostProcessLayer != null) { #if UNITY_ANDROID instance.LogWarning("Post processing is not supported on Android"); #else PostProcessLayer postProcessLayer = camera.gameObject.AddComponent(); postProcessLayer.volumeLayer = refPostProcessLayer.volumeLayer; postProcessLayer.volumeTrigger = refPostProcessLayer.volumeTrigger != refPostProcessLayer.transform ? refPostProcessLayer.volumeTrigger : postProcessLayer.volumeTrigger = camera.transform; // post processing should always be enabled. // postProcessLayer.enabled = refPostProcessLayer.enabled; // Use reflection to copy over resources : https://github.com/Unity-Technologies/PostProcessing/issues/467 FieldInfo resourcesInfo = typeof(PostProcessLayer).GetField("m_Resources", BindingFlags.NonPublic | BindingFlags.Instance); PostProcessResources postProcessResources = resourcesInfo.GetValue(refPostProcessLayer) as PostProcessResources; postProcessLayer.Init(postProcessResources); #endif } #endif refCamera.gameObject.SetActive(false); } } private void CreateMenu() { const int menuLayer = 12; Font font = Font.CreateDynamicFontFromOSFont("Ariel", 20); // Create Menu menu_ = new GameObject("Menu"); menu_.layer = menuLayer; menu_.transform.parent = transform.parent; Canvas canvas = menu_.AddComponent(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; canvas.worldCamera = camera_; canvas.planeDistance = camera_.nearClipPlane + 0.1f; canvas.sortingOrder = 1000; menu_.AddComponent(); GameObject respawnButton = new GameObject("RespawnButton"); respawnButton.layer = menuLayer; respawnButton.transform.SetParent(menu_.transform, false); respawnButton.transform.localPosition = new Vector3(60, 0, 0); respawnButton.AddComponent(); UIButton button = respawnButton.AddComponent(); button.onClick.AddListener(Respawn); GameObject respawnText = new GameObject("RespawnText"); respawnText.layer = menuLayer; respawnText.transform.SetParent(respawnButton.transform, false); Text text = respawnText.AddComponent(); respawnText.GetComponent().sizeDelta = Vector2.zero; text.text = "Respawn"; text.fontSize = 20; text.color = Color.black; text.alignment = TextAnchor.MiddleCenter; text.rectTransform.anchorMin = Vector2.zero; text.rectTransform.anchorMax = Vector2.one; text.font = font; GameObject exitMenuButton = new GameObject("ExitMenuButton"); exitMenuButton.layer = menuLayer; exitMenuButton.transform.SetParent(menu_.transform, false); exitMenuButton.transform.localPosition = new Vector3(-60, 0, 0); exitMenuButton.AddComponent(); button = exitMenuButton.AddComponent(); button.onClick.AddListener(CloseMenu); GameObject exitMenuText = new GameObject("ExitMenuText"); exitMenuText.layer = menuLayer; exitMenuText.transform.SetParent(exitMenuButton.transform, false); text = exitMenuText.AddComponent(); exitMenuText.GetComponent().sizeDelta = Vector2.zero; text.text = "Close\nMenu"; text.fontSize = 20; text.color = Color.black; text.alignment = TextAnchor.MiddleCenter; text.rectTransform.anchorMin = Vector2.zero; text.rectTransform.anchorMax = Vector2.one; text.font = font; #if UNITY_EDITOR GameObject settingsButton = new GameObject("SettingsButton"); settingsButton.layer = menuLayer; settingsButton.transform.SetParent(menu_.transform, false); settingsButton.transform.localPosition = new Vector3(-60, -100, 0); settingsButton.AddComponent(); button = settingsButton.AddComponent(); // TODO handle this better Type settingsWindow = Type.GetType("VRCPrefabs.CyanEmu.CyanEmuSettingsWindow, Assembly-CSharp-Editor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); button.onClick.AddListener(() => UnityEditor.EditorWindow.GetWindow(settingsWindow, false, "CyanEmu Settings")); RectTransform rect = settingsButton.GetComponent(); rect.sizeDelta = new Vector2(100, 50); GameObject settingsText = new GameObject("SettingsText"); settingsText.layer = menuLayer; settingsText.transform.SetParent(settingsButton.transform, false); text = settingsText.AddComponent(); settingsText.GetComponent().sizeDelta = Vector2.zero; text.text = "Settings"; text.fontSize = 20; text.color = Color.black; text.alignment = TextAnchor.MiddleCenter; text.rectTransform.anchorMin = Vector2.zero; text.rectTransform.anchorMax = Vector2.one; text.font = font; #endif ToggleMenu(true); } public float GetJump() { return jumpSpeed_; } public void SetJump(float jump) { jumpSpeed_ = jump; } public float GetRunSpeed() { return runSpeed_; } public void SetRunSpeed(float runSpeed) { runSpeed_ = runSpeed; } public float GetWalkSpeed() { return walkSpeed_; } public void SetWalkSpeed(float walkSpeed) { walkSpeed_ = walkSpeed; } public float GetStrafeSpeed() { return strafeSpeed_; } public void SetStrafeSpeed(float strafeSpeed) { strafeSpeed_ = strafeSpeed; } public float GetGravityStrength() { return gravityStrength_; } public void SetGravityStrength(float gravity) { gravityStrength_ = gravity; } public Vector3 GetPosition() { return transform.position; } public Quaternion GetRotation() { return transform.rotation; } public Vector3 GetVelocity() { // TODO fix value return characterController_.velocity; } public void SetVelocity(Vector3 velocity) { playerRetainedVelocity_ = velocity; velSet = true; } public bool IsGrounded() { return characterController_.isGrounded; } public void UseLegacyLocomotion() { legacyLocomotion_ = true; } public VRCPlayerApi.TrackingData GetTrackingData(VRCPlayerApi.TrackingDataType trackingDataType) { VRCPlayerApi.TrackingData data = new VRCPlayerApi.TrackingData(); if (trackingDataType == VRCPlayerApi.TrackingDataType.Head) { data.position = playerCamera_.transform.position; data.rotation = playerCamera_.transform.rotation; } else if (trackingDataType == VRCPlayerApi.TrackingDataType.LeftHand) { data.position = leftArmPosition_.transform.position; data.rotation = leftArmPosition_.transform.rotation; } else if (trackingDataType == VRCPlayerApi.TrackingDataType.RightHand) { data.position = rightArmPosition_.transform.position; data.rotation = rightArmPosition_.transform.rotation; } return data; } public void SetPlayer(CyanEmuPlayer player) { player_ = player; } public void Respawn() { if (currentStation_ != null) { currentStation_.ExitStation(); } CloseMenu(); Teleport(CyanEmuMain.GetNextSpawnPoint(), false); CyanEmuMain.PlayerRespawned(player_.player); } public void Teleport(Transform point, bool fromPlaySpace) { Teleport(point.position, Quaternion.Euler(0, point.rotation.eulerAngles.y, 0), fromPlaySpace); } public void Teleport(Vector3 position, Quaternion floorRotation, bool fromPlaySpace) { #if UDON // Udon auto exits players from stations when they are teleported. if (currentStation_ != null) { currentStation_.ExitStation(); } #endif floorRotation = Quaternion.Euler(0, floorRotation.eulerAngles.y, 0); if (fromPlaySpace) { floorRotation = Quaternion.Inverse(playspace_.transform.localRotation) * floorRotation; position = position + floorRotation * -playspace_.transform.localPosition; } this.Log("Moving player to " + position.ToString("F3") + " and rotation " + floorRotation.eulerAngles.ToString("F3")); transform.rotation = floorRotation; transform.position = position; mouseLook_.SetRotation(floorRotation); UpdateCameraProxyPosition(); Physics.SyncTransforms(); } public void EnterStation(CyanEmuStationHelper station) { if (currentStation_ != null) { currentStation_.ExitStation(); } if (!station.IsMobile) { characterController_.enabled = false; Teleport(station.EnterLocation, false); mouseLook_.SetBaseRotation(station.EnterLocation); mouseLook_.SetRotation(Quaternion.identity); } currentStation_ = station; stance_ = Stance.SITTING; updateStancePosition_ = true; } public void ExitStation(CyanEmuStationHelper station) { currentStation_ = null; characterController_.enabled = true; if (!station.IsMobile) { Teleport(station.ExitLocation, false); } mouseLook_.SetBaseRotation(null); jump_ = false; stance_ = Stance.STANDING; updateStancePosition_ = true; } public void PickupObject(CyanEmuPickupHelper pickup) { if (currentPickup_ != null) { currentPickup_.Drop(); } currentPickup_ = pickup; pickup.UpdatePosition(rightArmPosition_.transform, true); FixedJoint fixedJoint = rightArmPosition_.AddComponent(); fixedJoint.connectedBody = pickup.GetRigidbody(); } public void DropObject(CyanEmuPickupHelper pickup) { if (currentPickup_ == pickup) { currentPickup_ = null; FixedJoint fixedJoint = rightArmPosition_.GetComponent(); if (fixedJoint) { Destroy(fixedJoint); } Rigidbody rigidbody = pickup.GetRigidbody(); rigidbody.velocity = (rightArmPosition_.transform.position - prevousHandPosition_) * (0.5f / Time.deltaTime); rigidbody.angularVelocity = (rightArmPosition_.transform.rotation.eulerAngles - prevousHandRotation_); } } public void SitPosition(Transform seat) { transform.position = seat.position; UpdateCameraProxyPosition(); } public Camera GetCamera() { return camera_; } public float GetCameraScale() { return camera_.transform.lossyScale.x; } public Transform GetCameraProxyTransform() { return cameraProxyObject_; } public Transform GetArmTransform() { return rightArmPosition_.transform; } public VRC_Pickup GetHeldPickup(VRC_Pickup.PickupHand hand) { if (hand == VRC_Pickup.PickupHand.Right) { return currentPickup_?.GetComponent(); } return null; } public void PlayerDied() { if (currentPickup_ != null) { currentPickup_.Drop(); } if (currentStation_ != null) { currentStation_.ExitStation(); } isDead_ = true; } public void PlayerRevived() { isDead_ = false; } public void Immobilize(bool immobilize) { isImmobile_ = immobilize; } private void Update() { RotateView(); if (!jump_ && characterController_.isGrounded && jumpSpeed_ > 0) { jump_ = Input.GetButtonDown("Jump"); } if (currentPickup_ != null) { currentPickup_.UpdatePosition(rightArmPosition_.transform); currentPickup_.UpdateUse(); } UpdateStance(); UpdateMenu(); UpdateCameraProxyPosition(); prevousHandPosition_ = rightArmPosition_.transform.position; prevousHandRotation_ = rightArmPosition_.transform.rotation.eulerAngles; } private void FixedUpdate() { Physics.SyncTransforms(); Vector2 speed; Vector2 input; Vector2 prevInput = prevInputResult_; GetInput(out speed, out input); #if UDON HandleUdonInput(); #endif if (currentStation_ != null && !currentStation_.CanPlayerMoveWhileSeated(input.magnitude)) { return; } if (menu_.activeInHierarchy || isDead_) { input = Vector2.zero; jump_ = false; } // Immobile does not affect Jump if (isImmobile_) { input = Vector2.zero; } // always move along the camera forward as it is the direction that it being aimed at Vector3 desiredMove = transform.forward * input.y * speed.x + transform.right * input.x * speed.y; desiredMove.y = 0; float gravityContribution = (gravityStrength_ * Physics.gravity * Time.fixedDeltaTime).y; if (!velSet) { if (characterController_.isGrounded) { playerRetainedVelocity_ = Vector3.zero; playerRetainedVelocity_.y = -STICK_TO_GROUND_FORCE_; if (jump_) { if (!legacyLocomotion_) { playerRetainedVelocity_ = desiredMove; } playerRetainedVelocity_.y = jumpSpeed_; desiredMove = Vector3.zero; jump_ = false; } } else { // Slowly add velocity from movement inputs if (!legacyLocomotion_) { Vector3 localVelocity = transform.InverseTransformVector(characterController_.velocity); localVelocity.x = Mathf.Clamp(localVelocity.x, -speed.y, speed.y); localVelocity.z = Mathf.Clamp(localVelocity.z, -speed.x, speed.x); Vector3 maxAc = new Vector3(speed.y - localVelocity.x, 0, speed.x - localVelocity.z); Vector3 minAc = new Vector3(-speed.y - localVelocity.x, 0, -speed.x - localVelocity.z); Vector3 inputAcceleration = new Vector3(input.x * speed.y, 0, input.y * speed.x) * Time.fixedDeltaTime * RATE_OF_AIR_ACCELERATION_; inputAcceleration.x = Mathf.Clamp(inputAcceleration.x, minAc.x, maxAc.x); inputAcceleration.z = Mathf.Clamp(inputAcceleration.z, minAc.z, maxAc.z); inputAcceleration = transform.TransformVector(inputAcceleration); playerRetainedVelocity_ += inputAcceleration; desiredMove = Vector3.zero; } // Legacy stutter stepping else if ((input.sqrMagnitude < 1e-3 ^ prevInput.sqrMagnitude < 1e-3)) { playerRetainedVelocity_ = Vector3.zero; } playerRetainedVelocity_.y += gravityContribution; } } else // Dumb behavior that hopefully needs to be removed { characterController_.Move(new Vector3(desiredMove.x * 0.05f, desiredMove.y * 0.05f + gravityContribution, desiredMove.z * 0.05f) * Time.fixedDeltaTime); desiredMove = Vector3.zero; } desiredMove += playerRetainedVelocity_; peviouslyGrounded_ = characterController_.isGrounded; collisionFlags_ = characterController_.Move(desiredMove * Time.fixedDeltaTime); if (!menu_.activeInHierarchy) { mouseLook_.UpdateCursorLock(); } velSet = false; UpdateCameraProxyPosition(); } // TODO do this better... // Refactor all input out of the player controller and simply have it listen to these events #if UDON private readonly (KeyCode, string, HandType)[] keyToEvent_ = { (KeyCode.Space, UdonManager.UDON_INPUT_JUMP, HandType.LEFT), (KeyCode.Mouse0, UdonManager.UDON_INPUT_USE, HandType.RIGHT), (KeyCode.Mouse0, UdonManager.UDON_INPUT_GRAB, HandType.RIGHT), (KeyCode.Mouse1, UdonManager.UDON_INPUT_DROP, HandType.RIGHT), }; private Vector2 prevMouseInput_ = Vector2.zero; private Vector2 prevMoveInput_ = Vector2.zero; private void HandleUdonInput() { if (menu_.activeInHierarchy) { return; } foreach (var (keyCode, eventName, handType) in keyToEvent_) { HandleInputForKey(keyCode, eventName, handType); } Vector2 mouseInput = new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y")); // Clamp value from -1 to 1 mouseInput = Vector2.Max(Vector2.Min(mouseInput, Vector2.one), -Vector2.one); if (baseInput_.isMenuOpen) { mouseInput = Vector2.zero; } if (Mathf.Abs(mouseInput.x - prevMouseInput_.x) > 0.001f) { var args = new UdonInputEventArgs(mouseInput.x, HandType.RIGHT); UdonManager.Instance.RunInputAction(UdonManager.UDON_LOOK_HORIZONTAL, args); } if (Mathf.Abs(mouseInput.y - prevMouseInput_.y) > 0.001f) { var args = new UdonInputEventArgs(mouseInput.y, HandType.RIGHT); UdonManager.Instance.RunInputAction(UdonManager.UDON_LOOK_VERTICAL, args); } prevMouseInput_ = mouseInput; // TODO refactor all this out into its own input manager that the player controller listens to. // Handle sending movement input to Udon. if (Mathf.Abs(prevMoveInput_.x - prevInputResult_.x) > 1e-3) { var args = new UdonInputEventArgs(prevInputResult_.x, HandType.RIGHT); UdonManager.Instance.RunInputAction(UdonManager.UDON_MOVE_HORIZONTAL, args); } if (Mathf.Abs(prevMoveInput_.y - prevInputResult_.y) > 1e-3) { var args = new UdonInputEventArgs(prevInputResult_.y, HandType.RIGHT); UdonManager.Instance.RunInputAction(UdonManager.UDON_MOVE_VERTICAL, args); } prevMoveInput_ = prevInputResult_; } private void HandleInputForKey(KeyCode key, string eventName, HandType handType) { if (Input.GetKeyDown(key)) { var args = new UdonInputEventArgs(true, handType); UdonManager.Instance.RunInputAction(eventName, args); } if (Input.GetKeyUp(key)) { var args = new UdonInputEventArgs(false, handType); UdonManager.Instance.RunInputAction(eventName, args); } } #endif private void LateUpdate() { currentStation_?.UpdatePlayerPosition(this); } private void UpdateCameraProxyPosition() { cameraProxyObject_.position = playerCamera_.transform.position; cameraProxyObject_.rotation = playerCamera_.transform.rotation; cameraProxyObject_.localScale = playerCamera_.transform.lossyScale; } private void UpdateMenu() { if (Input.GetKeyDown(MenuKey)) { ToggleMenu(!menu_.activeInHierarchy); } } private void CloseMenu() { ToggleMenu(false); } private void ToggleMenu(bool isOpen) { mouseLook_.SetCursorLocked(!isOpen); menu_.SetActive(isOpen); baseInput_.isMenuOpen = isOpen; } private void UpdateStance() { if (Input.GetKeyDown(CyanEmuSettings.Instance.crouchKey) && currentStation_ == null) { updateStancePosition_ = true; if (stance_ == Stance.CROUCHING) { stance_ = Stance.STANDING; } else { stance_ = Stance.CROUCHING; } } if (Input.GetKeyDown(CyanEmuSettings.Instance.proneKey) && currentStation_ == null) { updateStancePosition_ = true; if (stance_ == Stance.PRONE) { stance_ = Stance.STANDING; } else { stance_ = Stance.PRONE; } } if (updateStancePosition_) { Vector3 cameraPosition = playerCamera_.transform.localPosition; cameraPosition.y = (stance_ == Stance.STANDING ? STANDING_HEIGHT_ : stance_ == Stance.CROUCHING ? CROUCHING_HEIGHT_ : stance_ == Stance.PRONE ? PRONE_HEIGHT_ : SITTING_HEIGHT_); playerCamera_.transform.localPosition = cameraPosition; } updateStancePosition_ = false; } private void GetInput(out Vector2 speed, out Vector2 input) { float horizontal = Input.GetAxis("Horizontal"); float vertical = Input.GetAxis("Vertical"); Vector2 curInput = new Vector2(horizontal, vertical); // Prevent sliding without changing the input manager >_> // TODO clean this up and just modify input manager... horizontal = GetExpectedMovement(horizontal, prevInput_.x, prevInputResult_.x); vertical = GetExpectedMovement(vertical, prevInput_.y, prevInputResult_.y); isWalking_ = !Input.GetKey(CyanEmuSettings.Instance.runKey); speed = new Vector2(isWalking_? walkSpeed_ : runSpeed_, strafeSpeed_); input = new Vector2(horizontal, vertical); prevInput_ = curInput; prevInputResult_ = input; if (stance_ == Stance.CROUCHING) { speed *= CROUCH_SPEED_MULTIPLYER_; } else if (stance_ == Stance.PRONE) { speed *= PRONE_SPEED_MULTIPLYER_; } if (input.sqrMagnitude > 1) { input.Normalize(); } } // Get the direction of change. If moving towards 1 or -1, return 1 or -1. If moving towards 0, return 0. private float GetExpectedMovement(float input, float previous, float previousDecision) { if (input != 0 && input == previous) { return previousDecision; } if (input < 0 && input < previous) { return -1; } if (input > 0 && input > previous) { return 1; } return 0; } private void RotateView() { mouseLook_.LookRotation(transform, playerCamera_.transform, currentStation_ != null && !currentStation_.IsMobile); } private void OnControllerColliderHit(ControllerColliderHit hit) { #if VRC_SDK_VRCSDK2 VRC_Trigger trig = hit.collider.GetComponent(); if (trig != null) { trig.ExecuteTriggerType(VRC_Trigger.TriggerType.OnAvatarHit); } #endif #if UDON // TODO fake implementing OnPlayerColliderEnter/Stay/Exit for Udon with this method. #endif Rigidbody body = hit.collider.attachedRigidbody; //dont move the rigidbody if the character is on top of it if (collisionFlags_ == CollisionFlags.Below) { return; } if (body == null || body.isKinematic) { return; } body.AddForceAtPosition(characterController_.velocity * 0.1f, hit.point, ForceMode.Impulse); } private void OnGUI() { // TODO if VR also return if (!CyanEmuSettings.Instance.showDesktopReticle) { return; } Vector2 center = CyanEmuBaseInput.GetScreenCenter(); Vector2 size = new Vector2(reticleTexture_.width, reticleTexture_.height); Rect position = new Rect(center - size * 0.5f, size); GUI.DrawTexture(position, reticleTexture_); } } // Modified from standard assets public class MouseLook { public float XSensitivity = 2f; public float YSensitivity = 2f; public float MinimumAngle = -90F; public float MaximumAngle = 90F; public bool lockCursor = true; private Quaternion m_CharacterTargetRot; private Quaternion m_CameraTargetRot; private bool m_cursorIsLocked = false; private Transform m_BaseRotation; public void Init(Transform character, Transform camera) { SetBaseRotation(null); m_CharacterTargetRot = character.localRotation; m_CameraTargetRot = camera.localRotation; } public void SetRotation(Quaternion newRotation) { m_CharacterTargetRot = newRotation; } public void SetBaseRotation(Transform baseRotation) { m_BaseRotation = baseRotation; } public void LookRotation(Transform character, Transform camera, bool inStation) { float yRot = Input.GetAxis("Mouse X") * XSensitivity; float xRot = Input.GetAxis("Mouse Y") * YSensitivity; if (!m_cursorIsLocked) { yRot = 0; xRot = 0; } m_CharacterTargetRot *= Quaternion.Euler(0f, yRot, 0f); m_CameraTargetRot *= Quaternion.Euler(-xRot, 0f, 0f); m_CameraTargetRot = ClampRotationAroundAxis(m_CameraTargetRot, 0); if (inStation) { m_CharacterTargetRot = ClampRotationAroundAxis(m_CharacterTargetRot, 1); character.localRotation = m_BaseRotation.rotation * m_CharacterTargetRot; } else { character.localRotation = m_CharacterTargetRot; } camera.localRotation = m_CameraTargetRot; } public void UpdateCursorLock() { if (lockCursor) { InternalLockUpdate(); } } public void SetCursorLocked(bool isLocked) { m_cursorIsLocked = isLocked; InternalLockUpdate(); } private void InternalLockUpdate() { if (m_cursorIsLocked) { Cursor.lockState = CursorLockMode.Locked; Cursor.visible = false; } else if (!m_cursorIsLocked) { Cursor.lockState = CursorLockMode.None; Cursor.visible = true; } } Quaternion ClampRotationAroundAxis(Quaternion q, int index) { q.x /= q.w; q.y /= q.w; q.z /= q.w; q.w = 1.0f; float angle = 2.0f * Mathf.Rad2Deg * Mathf.Atan(q[index]); angle = Mathf.Clamp(angle, MinimumAngle, MaximumAngle); q[index] = Mathf.Tan(0.5f * Mathf.Deg2Rad * angle); return q; } } }