Added Unity project files
This commit is contained in:
@ -0,0 +1,218 @@
|
||||
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.ClientSim.PlayerTracking;
|
||||
using VRC.Udon.Common;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
// Listens to Events:
|
||||
// - ClientSimMouseReleasedEvent
|
||||
// - ClientSimOnPlayerEnteredStationEvent
|
||||
// - ClientSimOnPlayerExitedStationEvent
|
||||
// Listens to Input Events:
|
||||
// - ToggleCrouch
|
||||
// - ToggleProne
|
||||
[AddComponentMenu("")]
|
||||
public class ClientSimDesktopTrackingProvider : ClientSimTrackingProviderBase
|
||||
{
|
||||
[SerializeField]
|
||||
private Transform playerXRotationBase;
|
||||
[SerializeField]
|
||||
private Transform playerYRotationBase;
|
||||
|
||||
private bool _mouseReleased = false;
|
||||
private ClientSimDesktopTrackingRotator _desktopRotator;
|
||||
private IClientSimStation _currentStation;
|
||||
|
||||
public override void Initialize(
|
||||
IClientSimEventDispatcher eventDispatcher,
|
||||
IClientSimInput input,
|
||||
ClientSimSettings settings,
|
||||
IClientSimPlayerHeightManager heightManager)
|
||||
{
|
||||
base.Initialize(eventDispatcher, input, settings, heightManager);
|
||||
|
||||
SetTrackingItemPositions();
|
||||
|
||||
_desktopRotator = new ClientSimDesktopTrackingRotator(playerXRotationBase, playerYRotationBase);
|
||||
}
|
||||
|
||||
private void SetTrackingItemPositions()
|
||||
{
|
||||
head.localPosition = new Vector3(0, STANDING_HEIGHT, .1f);
|
||||
head.localRotation = Quaternion.identity;
|
||||
|
||||
rightHand.localPosition = new Vector3(0.15f, -0.13f, 0.4f);
|
||||
rightHand.localRotation = Quaternion.Euler(-35, 0, -90);
|
||||
|
||||
leftHand.localPosition = new Vector3(-0.15f, -0.13f, 0.4f);
|
||||
leftHand.localRotation = Quaternion.Euler(-35, 0, -90);
|
||||
}
|
||||
|
||||
#region IClientSimInputEventSubscribable
|
||||
|
||||
public override void SubscribeInputEvents()
|
||||
{
|
||||
base.SubscribeInputEvents();
|
||||
|
||||
input.SubscribeToggleCrouch(ToggleCrouchInput);
|
||||
input.SubscribeToggleProne(ToggleProneInput);
|
||||
}
|
||||
|
||||
public override void UnsubscribeInputEvents()
|
||||
{
|
||||
base.UnsubscribeInputEvents();
|
||||
|
||||
input.UnsubscribeToggleCrouch(ToggleCrouchInput);
|
||||
input.UnsubscribeToggleProne(ToggleProneInput);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IClientSimInputEventSubscribable
|
||||
|
||||
public override void SubscribeEvents()
|
||||
{
|
||||
base.SubscribeEvents();
|
||||
|
||||
eventDispatcher.Subscribe<ClientSimMouseReleasedEvent>(MouseReleasedEvent);
|
||||
eventDispatcher.Subscribe<ClientSimOnPlayerEnteredStationEvent>(PlayerEnteredStation);
|
||||
eventDispatcher.Subscribe<ClientSimOnPlayerExitedStationEvent>(PlayerExitedStation);
|
||||
}
|
||||
|
||||
public override void UnsubscribeEvents()
|
||||
{
|
||||
base.UnsubscribeEvents();
|
||||
|
||||
eventDispatcher.Unsubscribe<ClientSimMouseReleasedEvent>(MouseReleasedEvent);
|
||||
eventDispatcher.Unsubscribe<ClientSimOnPlayerEnteredStationEvent>(PlayerEnteredStation);
|
||||
eventDispatcher.Unsubscribe<ClientSimOnPlayerExitedStationEvent>(PlayerExitedStation);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ClientSim Events
|
||||
|
||||
private void MouseReleasedEvent(ClientSimMouseReleasedEvent mouseReleasedEvent)
|
||||
{
|
||||
_mouseReleased = mouseReleasedEvent.isReleased;
|
||||
}
|
||||
|
||||
private void PlayerEnteredStation(ClientSimOnPlayerEnteredStationEvent stationEvent)
|
||||
{
|
||||
_currentStation = stationEvent.station;
|
||||
_desktopRotator.SetStation(_currentStation);
|
||||
|
||||
if (_currentStation.IsSeated())
|
||||
{
|
||||
SetStance(ClientSimPlayerStanceEnum.SITTING);
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayerExitedStation(ClientSimOnPlayerExitedStationEvent stationEvent)
|
||||
{
|
||||
_currentStation = null;
|
||||
_desktopRotator.SetStation(null);
|
||||
SetStance(ClientSimPlayerStanceEnum.STANDING);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ClientSim Input
|
||||
|
||||
private void ToggleCrouchInput(bool value)
|
||||
{
|
||||
// Only handle on down, and not on release.
|
||||
if (!value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetPlayerStance() == ClientSimPlayerStanceEnum.CROUCHING)
|
||||
{
|
||||
SetStance(ClientSimPlayerStanceEnum.STANDING);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetStance(ClientSimPlayerStanceEnum.CROUCHING);
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleProneInput(bool value)
|
||||
{
|
||||
// Only handle on down, and not on release.
|
||||
if (!value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetPlayerStance() == ClientSimPlayerStanceEnum.PRONE)
|
||||
{
|
||||
SetStance(ClientSimPlayerStanceEnum.STANDING);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetStance(ClientSimPlayerStanceEnum.PRONE);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// If mouse is released, do not update rotation.
|
||||
if (!_mouseReleased)
|
||||
{
|
||||
_desktopRotator.HandleRotation(input.GetLookHorizontal(), input.GetLookVertical());
|
||||
}
|
||||
}
|
||||
|
||||
private void SetStance(ClientSimPlayerStanceEnum stance)
|
||||
{
|
||||
// If in a station, ignore all non sitting stances.
|
||||
if (_currentStation != null && _currentStation.IsLockedInStation() && stance != ClientSimPlayerStanceEnum.SITTING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 cameraPosition = head.localPosition;
|
||||
switch (stance)
|
||||
{
|
||||
case ClientSimPlayerStanceEnum.PRONE:
|
||||
cameraPosition.y = PRONE_HEIGHT;
|
||||
break;
|
||||
case ClientSimPlayerStanceEnum.CROUCHING:
|
||||
cameraPosition.y = CROUCHING_HEIGHT;
|
||||
break;
|
||||
case ClientSimPlayerStanceEnum.SITTING:
|
||||
cameraPosition.y = SITTING_HEIGHT;
|
||||
break;
|
||||
case ClientSimPlayerStanceEnum.STANDING:
|
||||
cameraPosition.y = STANDING_HEIGHT;
|
||||
break;
|
||||
}
|
||||
|
||||
head.localPosition = cameraPosition;
|
||||
}
|
||||
|
||||
public override Transform GetHandRaycastTransform(HandType handType)
|
||||
{
|
||||
throw new ClientSimException("Desktop tracking does not support arm based raycasting");
|
||||
}
|
||||
|
||||
public override bool IsVR()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool SupportsPickupAutoHold()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void LookTowardsPoint(Vector3 point)
|
||||
{
|
||||
_desktopRotator.LookAtPoint(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5cdb19253bd15ee4296b716139111626
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,83 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.SDK3.ClientSim.PlayerTracking
|
||||
{
|
||||
public class ClientSimDesktopTrackingRotator
|
||||
{
|
||||
private const float MINIMUM_X_ANGLE = -80f;
|
||||
private const float MAXIMUM_X_ANGLE = 70f;
|
||||
private const float MINIMUM_Y_ANGLE = -90F;
|
||||
private const float MAXIMUM_Y_ANGLE = 90F;
|
||||
|
||||
private Quaternion _targetBaseYRot;
|
||||
private Quaternion _targetHeadXRot;
|
||||
|
||||
private readonly Transform _headXTransform;
|
||||
private readonly Transform _headYTransform;
|
||||
|
||||
private IClientSimStation _currentStation;
|
||||
|
||||
public ClientSimDesktopTrackingRotator(Transform headXTransform, Transform headYTransform)
|
||||
{
|
||||
_headXTransform = headXTransform;
|
||||
_headYTransform = headYTransform;
|
||||
|
||||
_targetBaseYRot = _headYTransform.localRotation;
|
||||
_targetHeadXRot = _headXTransform.localRotation;
|
||||
}
|
||||
|
||||
public void SetStation(IClientSimStation station)
|
||||
{
|
||||
_currentStation = station;
|
||||
_targetBaseYRot = Quaternion.identity;
|
||||
}
|
||||
|
||||
public void HandleRotation(float xDelta, float yDelta)
|
||||
{
|
||||
_targetHeadXRot *= Quaternion.Euler(-yDelta, 0f, 0f);
|
||||
_targetHeadXRot = ClampRotationAroundAxis(_targetHeadXRot, 0, MINIMUM_X_ANGLE, MAXIMUM_X_ANGLE);
|
||||
|
||||
// Player in a station, allow limited rotation on Y axis.
|
||||
if (_currentStation != null && _currentStation.IsLockedInStation())
|
||||
{
|
||||
_targetBaseYRot *= Quaternion.Euler(0f, xDelta, 0f);
|
||||
_targetBaseYRot = ClampRotationAroundAxis(_targetBaseYRot, 1, MINIMUM_Y_ANGLE, MAXIMUM_Y_ANGLE);
|
||||
}
|
||||
|
||||
_headXTransform.localRotation = _targetHeadXRot;
|
||||
_headYTransform.localRotation = _targetBaseYRot;
|
||||
}
|
||||
|
||||
// Used in tests to force rotate the player to look at an object.
|
||||
public void LookAtPoint(Vector3 point)
|
||||
{
|
||||
// Get the amount the player needs to rotate on Y
|
||||
Vector3 localizedYPoint = _headYTransform.InverseTransformPoint(point);
|
||||
localizedYPoint.y = 0;
|
||||
float yAngle = Vector3.SignedAngle(Vector3.forward, localizedYPoint, Vector3.up);
|
||||
|
||||
// Get the amount the player needs to rotate on X
|
||||
Vector3 localizedXPoint = _headXTransform.InverseTransformPoint(point);
|
||||
localizedXPoint.x = 0;
|
||||
float xAngle = Vector3.SignedAngle(Vector3.forward, localizedXPoint, Vector3.left);
|
||||
|
||||
HandleRotation(yAngle, xAngle);
|
||||
}
|
||||
|
||||
private static Quaternion ClampRotationAroundAxis(Quaternion q, int index, float minAngle, float maxAngle)
|
||||
{
|
||||
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, minAngle, maxAngle);
|
||||
|
||||
q[index] = Mathf.Tan(0.5f * Mathf.Deg2Rad * angle);
|
||||
|
||||
return q;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4416d1ef28048a94683820481afedf33
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,10 @@
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
public enum ClientSimPlayerStanceEnum
|
||||
{
|
||||
STANDING,
|
||||
CROUCHING,
|
||||
PRONE,
|
||||
SITTING
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 062e0aeaac1ea1e4b9c22f6b612b3163
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,270 @@
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using VRC.SDKBase;
|
||||
using VRC.Udon.Common;
|
||||
|
||||
namespace VRC.SDK3.ClientSim
|
||||
{
|
||||
/// <summary>
|
||||
/// System responsible for providing tracking data to the rest of ClientSim.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Currently only used for DesktopTracking, but can be extended for VR and even fake VR implementations.
|
||||
/// Sends Events:
|
||||
/// - ClientSimOnPlayerHeightUpdateEvent
|
||||
/// - ClientSimOnTrackingScaleUpdateEvent
|
||||
/// Listens to Events:
|
||||
/// - ClientSimOnPlayerHeightUpdateEvent
|
||||
/// </remarks>
|
||||
[DefaultExecutionOrder(-3000)] // Update before player raycasting
|
||||
public abstract class ClientSimTrackingProviderBase : ClientSimBehaviour, IClientSimTrackingProvider, IDisposable
|
||||
{
|
||||
private const float MINIMUM_TRACKING_SCALE = 0.1f;
|
||||
private const float MAXIMUM_TRACKING_SCALE = 50f;
|
||||
|
||||
// TODO calculate this value based on the avatar instead of hard coding it.
|
||||
public const float AVATAR_HEIGHT = 1.9f;
|
||||
|
||||
protected const float STANDING_HEIGHT = 1.75f;
|
||||
protected const float CROUCHING_HEIGHT = 1.0f;
|
||||
protected const float PRONE_HEIGHT = 0.5f;
|
||||
protected const float SITTING_HEIGHT = 1.2f;
|
||||
|
||||
[SerializeField]
|
||||
protected Transform head;
|
||||
[SerializeField]
|
||||
protected Transform leftHand;
|
||||
[SerializeField]
|
||||
protected Transform rightHand;
|
||||
[SerializeField]
|
||||
protected Transform playspace;
|
||||
[SerializeField]
|
||||
protected Transform playerAudioListener;
|
||||
[SerializeField]
|
||||
protected Camera playerCamera;
|
||||
|
||||
protected IClientSimEventDispatcher eventDispatcher;
|
||||
protected IClientSimInput input;
|
||||
protected ClientSimSettings settings;
|
||||
protected IClientSimPlayerHeightManager heightManager;
|
||||
|
||||
private float _trackingScale = 1;
|
||||
|
||||
public static float CalculateTrackingScaleFromPlayerHeight(float playerHeight)
|
||||
{
|
||||
return playerHeight / AVATAR_HEIGHT;
|
||||
}
|
||||
|
||||
public static float CalculatePlayerHeightFromTrackingScale(float trackingScale)
|
||||
{
|
||||
return trackingScale * AVATAR_HEIGHT;
|
||||
}
|
||||
|
||||
public virtual void Initialize(
|
||||
IClientSimEventDispatcher eventDispatcher,
|
||||
IClientSimInput input,
|
||||
ClientSimSettings settings,
|
||||
IClientSimPlayerHeightManager heightManager)
|
||||
{
|
||||
this.eventDispatcher = eventDispatcher;
|
||||
this.input = input;
|
||||
this.settings = settings;
|
||||
this.heightManager = heightManager;
|
||||
|
||||
SubscribeEvents();
|
||||
|
||||
// Input will be null with incorrect Unity input project settings.
|
||||
if (input != null)
|
||||
{
|
||||
SubscribeInputEvents();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
// Send event for this to ensure everything that uses the player height is properly updated.
|
||||
eventDispatcher.SendEvent(new ClientSimOnPlayerHeightUpdateEvent
|
||||
{
|
||||
playerHeight = heightManager.GetAvatarEyeHeightAsMeters()
|
||||
});
|
||||
|
||||
|
||||
// Only disable audio listeners and cameras if the player is spawned.
|
||||
if (settings.spawnPlayer)
|
||||
{
|
||||
// Destroy other audio listeners
|
||||
foreach (var listener in FindObjectsByType<AudioListener>(FindObjectsSortMode.None))
|
||||
{
|
||||
if (listener.transform == playerAudioListener)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
DestroyImmediate(listener);
|
||||
}
|
||||
|
||||
// Disable all cameras that do not render to a render texture.
|
||||
foreach (var worldCamera in FindObjectsByType<Camera>(FindObjectsSortMode.None))
|
||||
{
|
||||
if (worldCamera == playerCamera)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (worldCamera.targetTexture != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
worldCamera.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
UnsubscribeEvents();
|
||||
|
||||
// Input will be null with incorrect Unity input project settings.
|
||||
if (input != null)
|
||||
{
|
||||
UnsubscribeInputEvents();
|
||||
}
|
||||
}
|
||||
|
||||
#region IClientSimTrackingProvider
|
||||
|
||||
public virtual VRCPlayerApi.TrackingData GetTrackingData(VRCPlayerApi.TrackingDataType trackingDataType)
|
||||
{
|
||||
VRCPlayerApi.TrackingData data = new VRCPlayerApi.TrackingData();
|
||||
|
||||
switch (trackingDataType)
|
||||
{
|
||||
case VRCPlayerApi.TrackingDataType.Head:
|
||||
data.position = head.position;
|
||||
data.rotation = head.rotation;
|
||||
break;
|
||||
case VRCPlayerApi.TrackingDataType.LeftHand:
|
||||
data.position = leftHand.position;
|
||||
data.rotation = leftHand.rotation;
|
||||
break;
|
||||
case VRCPlayerApi.TrackingDataType.RightHand:
|
||||
data.position = rightHand.position;
|
||||
data.rotation = rightHand.rotation;
|
||||
break;
|
||||
case VRCPlayerApi.TrackingDataType.Origin:
|
||||
data.position = playspace.position;
|
||||
data.rotation = playspace.rotation;
|
||||
break;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public virtual Transform GetTrackingTransform(VRCPlayerApi.TrackingDataType trackingDataType)
|
||||
{
|
||||
switch (trackingDataType)
|
||||
{
|
||||
case VRCPlayerApi.TrackingDataType.Head:
|
||||
return head;
|
||||
case VRCPlayerApi.TrackingDataType.LeftHand:
|
||||
return leftHand;
|
||||
case VRCPlayerApi.TrackingDataType.RightHand:
|
||||
return rightHand;
|
||||
case VRCPlayerApi.TrackingDataType.Origin:
|
||||
return playspace;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public float GetTrackingScale()
|
||||
{
|
||||
return _trackingScale;
|
||||
}
|
||||
|
||||
public void SetTrackingScale(float scale)
|
||||
{
|
||||
scale = Mathf.Clamp(scale, MINIMUM_TRACKING_SCALE, MAXIMUM_TRACKING_SCALE);
|
||||
|
||||
_trackingScale = scale;
|
||||
playspace.localScale = scale * Vector3.one;
|
||||
|
||||
// Audio listener must always be scale 1 to ensure ONSP sounds correctly.
|
||||
playerAudioListener.localScale = 1.0f / scale * Vector3.one;
|
||||
|
||||
eventDispatcher.SendEvent(new ClientSimOnTrackingScaleUpdateEvent { trackingScale = scale });
|
||||
}
|
||||
|
||||
public ClientSimPlayerStanceEnum GetPlayerStance()
|
||||
{
|
||||
// Check heights starting from shortest
|
||||
float headHeight = head.localPosition.y;
|
||||
if (headHeight <= PRONE_HEIGHT)
|
||||
{
|
||||
return ClientSimPlayerStanceEnum.PRONE;
|
||||
}
|
||||
if (headHeight <= CROUCHING_HEIGHT)
|
||||
{
|
||||
return ClientSimPlayerStanceEnum.CROUCHING;
|
||||
}
|
||||
|
||||
return ClientSimPlayerStanceEnum.STANDING;
|
||||
}
|
||||
|
||||
public abstract Transform GetHandRaycastTransform(HandType handType);
|
||||
|
||||
public abstract bool IsVR();
|
||||
|
||||
public abstract bool SupportsPickupAutoHold();
|
||||
|
||||
public abstract void LookTowardsPoint(Vector3 point);
|
||||
|
||||
#endregion
|
||||
|
||||
#region IClientSimPlayerCameraProvider
|
||||
|
||||
public Camera GetCamera()
|
||||
{
|
||||
return playerCamera;
|
||||
}
|
||||
|
||||
public Camera GetCameraForObject(GameObject obj)
|
||||
{
|
||||
// TODO: Make this interact with camera stacking
|
||||
return playerCamera;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ClientSim Events
|
||||
|
||||
private void OnPlayerHeightUpdate(ClientSimOnPlayerHeightUpdateEvent heightEvent)
|
||||
{
|
||||
// Convert player height to tracking scale and set the new scale.
|
||||
SetTrackingScale(CalculateTrackingScaleFromPlayerHeight(heightEvent.playerHeight));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public virtual void SubscribeInputEvents() { }
|
||||
|
||||
public virtual void UnsubscribeInputEvents() { }
|
||||
|
||||
public virtual void SubscribeEvents()
|
||||
{
|
||||
eventDispatcher.Subscribe<ClientSimOnPlayerHeightUpdateEvent>(OnPlayerHeightUpdate);
|
||||
}
|
||||
|
||||
public virtual void UnsubscribeEvents()
|
||||
{
|
||||
eventDispatcher.Unsubscribe<ClientSimOnPlayerHeightUpdateEvent>(OnPlayerHeightUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3d6fc3f30fc48c247855805690cd90cc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user