OliveRave/UnityProject/Assets/CyanEmu/Scripts/CyanEmuPlayerController.cs

1136 lines
39 KiB
C#

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<VRC_SceneDescriptor>();
gameObject.layer = LayerMask.NameToLayer("PlayerLocal");
gameObject.tag = "Player";
rigidbody_ = gameObject.AddComponent<Rigidbody>();
rigidbody_.isKinematic = true;
characterController_ = gameObject.AddComponent<CharacterController>();
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<Collider>());
playerCamera_ = new GameObject("Player Camera");
GameObject cameraHolder = new GameObject("CameraHolder");
cameraHolder.transform.SetParent(playerCamera_.transform, false);
camera_ = cameraHolder.AddComponent<Camera>();
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<AudioListener>();
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<Rigidbody>();
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<Rigidbody>();
leftArmRigidbody_.isKinematic = true;
mouseLook_ = new MouseLook();
mouseLook_.Init(transform, playerCamera_.transform);
stance_ = Stance.STANDING;
baseInput_ = transform.parent.gameObject.GetComponent<CyanEmuBaseInput>();
CreateMenu();
GameObject interactHelper = new GameObject("InteractHelper");
interactHelper.transform.SetParent(transform.parent, false);
interactHelper_ = interactHelper.AddComponent<CyanEmuInteractHelper>();
Func<bool> shouldCheckForInteracts = () => { return currentPickup_ == null && !menu_.activeInHierarchy && !isDead_; };
interactHelper_.Initialize(playerCamera_.transform, playerCamera_.transform, shouldCheckForInteracts);
reticleTexture_ = Resources.Load<Texture2D>("Images/Reticle");
cameraProxyObject_ = new GameObject("CameraDamageProxy").transform;
cameraProxyObject_.SetParent(CyanEmuMain.GetProxyObjectTransform(), false);
UpdateCameraProxyPosition();
// experimental!
//interactHelper_.highlightManager = playerCamera_.AddComponent<VRCP_HighlightManager>();
}
private void Start()
{
Camera refCamera = null;
if (descriptor_.ReferenceCamera != null)
{
refCamera = descriptor_.ReferenceCamera.GetComponent<Camera>();
}
if (refCamera == null)
{
refCamera = Camera.main;
}
if (refCamera == null)
{
GameObject mainCamera = GameObject.FindGameObjectWithTag("MainCamera");
if (mainCamera != null)
{
refCamera = mainCamera.GetComponent<Camera>();
}
}
CopyCameraValues(refCamera, camera_);
if (CyanEmuCombatSystemHelper.instance != null)
{
CyanEmuCombatSystemHelper.instance.CreateVisualDamage();
}
// Go through all ui shapes to update canvas cameras?
foreach (var ui in FindObjectsOfType<VRC_UiShape>())
{
Canvas canvas = ui.GetComponent<Canvas>();
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<PostProcessLayer>();
if (refPostProcessLayer != null)
{
#if UNITY_ANDROID
instance.LogWarning("Post processing is not supported on Android");
#else
PostProcessLayer postProcessLayer = camera.gameObject.AddComponent<PostProcessLayer>();
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>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.worldCamera = camera_;
canvas.planeDistance = camera_.nearClipPlane + 0.1f;
canvas.sortingOrder = 1000;
menu_.AddComponent<GraphicRaycaster>();
GameObject respawnButton = new GameObject("RespawnButton");
respawnButton.layer = menuLayer;
respawnButton.transform.SetParent(menu_.transform, false);
respawnButton.transform.localPosition = new Vector3(60, 0, 0);
respawnButton.AddComponent<Image>();
UIButton button = respawnButton.AddComponent<UIButton>();
button.onClick.AddListener(Respawn);
GameObject respawnText = new GameObject("RespawnText");
respawnText.layer = menuLayer;
respawnText.transform.SetParent(respawnButton.transform, false);
Text text = respawnText.AddComponent<Text>();
respawnText.GetComponent<RectTransform>().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<Image>();
button = exitMenuButton.AddComponent<UIButton>();
button.onClick.AddListener(CloseMenu);
GameObject exitMenuText = new GameObject("ExitMenuText");
exitMenuText.layer = menuLayer;
exitMenuText.transform.SetParent(exitMenuButton.transform, false);
text = exitMenuText.AddComponent<Text>();
exitMenuText.GetComponent<RectTransform>().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<Image>();
button = settingsButton.AddComponent<UIButton>();
// 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<RectTransform>();
rect.sizeDelta = new Vector2(100, 50);
GameObject settingsText = new GameObject("SettingsText");
settingsText.layer = menuLayer;
settingsText.transform.SetParent(settingsButton.transform, false);
text = settingsText.AddComponent<Text>();
settingsText.GetComponent<RectTransform>().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>();
fixedJoint.connectedBody = pickup.GetRigidbody();
}
public void DropObject(CyanEmuPickupHelper pickup)
{
if (currentPickup_ == pickup)
{
currentPickup_ = null;
FixedJoint fixedJoint = rightArmPosition_.GetComponent<FixedJoint>();
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<VRC_Pickup>();
}
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<VRC_Trigger>();
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;
}
}
}