新增动态水物理插件
This commit is contained in:
8
Packages/com.nwh.common/Runtime/AssetInfo.meta
Normal file
8
Packages/com.nwh.common/Runtime/AssetInfo.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ac6e1af05281f046ad68e9cd8202764
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
85
Packages/com.nwh.common/Runtime/AssetInfo/AssetInfo.cs
Normal file
85
Packages/com.nwh.common/Runtime/AssetInfo/AssetInfo.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.AssetInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// ScriptableObject containing metadata and URLs for an NWH asset.
|
||||
/// Used by the welcome window and asset information systems.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "AssetInfo", menuName = "NWH/AssetInfo", order = 0)]
|
||||
public class AssetInfo : ScriptableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Display name of the asset.
|
||||
/// </summary>
|
||||
public string assetName = "Asset";
|
||||
|
||||
/// <summary>
|
||||
/// Unity Asset Store URL for this asset.
|
||||
/// </summary>
|
||||
public string assetURL = "https://assetstore.unity.com/packages/tools/physics/nwh-vehicle-physics-2-166252";
|
||||
|
||||
/// <summary>
|
||||
/// URL to the changelog documentation page.
|
||||
/// </summary>
|
||||
public string changelogURL = "";
|
||||
|
||||
/// <summary>
|
||||
/// Discord server invite link for support and community.
|
||||
/// </summary>
|
||||
public string discordURL = "https://discord.gg/59CQGEJ";
|
||||
|
||||
/// <summary>
|
||||
/// URL to the main documentation page.
|
||||
/// </summary>
|
||||
public string documentationURL = "";
|
||||
|
||||
/// <summary>
|
||||
/// Support email contact link.
|
||||
/// </summary>
|
||||
public string emailURL = "mailto:arescec@gmail.com";
|
||||
|
||||
/// <summary>
|
||||
/// Unity Forum thread URL for this asset.
|
||||
/// </summary>
|
||||
public string forumURL = "";
|
||||
|
||||
/// <summary>
|
||||
/// URL to quick start guide documentation.
|
||||
/// </summary>
|
||||
public string quickStartURL = "";
|
||||
|
||||
/// <summary>
|
||||
/// URL to upgrade notes between versions.
|
||||
/// </summary>
|
||||
public string upgradeNotesURL = "";
|
||||
|
||||
/// <summary>
|
||||
/// Current version string of the asset.
|
||||
/// </summary>
|
||||
public string version = "1.0";
|
||||
|
||||
/// <summary>
|
||||
/// Recent updates/changes in the current version (3-5 bullet points).
|
||||
/// </summary>
|
||||
[TextArea(3, 10)]
|
||||
public string[] recentUpdates = new string[0];
|
||||
|
||||
/// <summary>
|
||||
/// NWH publisher page URL on Unity Asset Store.
|
||||
/// </summary>
|
||||
public string publisherURL = "https://assetstore.unity.com/publishers/14460";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0968c894b2cd49fbb5dacdb1b4b5f4bb
|
||||
timeCreated: 1593277002
|
||||
@@ -0,0 +1,162 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
#region
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build;
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.AssetInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class providing editor initialization utilities for NWH packages.
|
||||
/// Handles scripting defines and welcome window display on package import.
|
||||
/// </summary>
|
||||
public class CommonInitializationMethods
|
||||
{
|
||||
private static Queue<AssetInfo> _welcomeWindowQueue = new Queue<AssetInfo>();
|
||||
private static bool _queueProcessScheduled = false;
|
||||
/// <summary>
|
||||
/// Adds a scripting define symbol to the current build target if not already present.
|
||||
/// </summary>
|
||||
/// <param name="symbol">Scripting define symbol to add (e.g., "NWH_NVP2")</param>
|
||||
protected static void AddDefines(string symbol)
|
||||
{
|
||||
NamedBuildTarget namedBuildTarget = NamedBuildTarget.FromBuildTargetGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
|
||||
string currentSymbols =
|
||||
PlayerSettings.GetScriptingDefineSymbols(namedBuildTarget);
|
||||
string newSymbols = string.Join(";", new HashSet<string>(currentSymbols.Split(';')) { symbol, });
|
||||
if (currentSymbols != newSymbols)
|
||||
{
|
||||
PlayerSettings.SetScriptingDefineSymbols(namedBuildTarget, newSymbols);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Displays welcome window for specified asset on first import or version update.
|
||||
/// Uses EditorPrefs to track display state per version. If NWH_ALWAYS_SHOW_WELCOME_WINDOW
|
||||
/// is defined, always displays window regardless of EditorPrefs state.
|
||||
/// Windows are queued and displayed sequentially to support multiple packages.
|
||||
/// Only shows on editor start, not on script reload (uses SessionState).
|
||||
/// </summary>
|
||||
/// <param name="assetName">Display name of the asset (must match AssetInfo.assetName)</param>
|
||||
protected static void ShowWelcomeWindow(string assetName)
|
||||
{
|
||||
// Skip if welcome windows have already been processed this editor session (prevents script reload triggers)
|
||||
if (SessionState.GetBool("NWH_WelcomeWindows_Processed", false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GetAssetInfo(assetName, out AssetInfo assetInfo))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if NWH_ALWAYS_SHOW_WELCOME_WINDOW
|
||||
_welcomeWindowQueue.Enqueue(assetInfo);
|
||||
#else
|
||||
string key = $"{assetInfo.assetName}_{assetInfo.version}_WW"; // Welcome Window key
|
||||
if (EditorPrefs.GetBool(key, false) == false)
|
||||
{
|
||||
EditorPrefs.SetBool(key, true);
|
||||
_welcomeWindowQueue.Enqueue(assetInfo);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Schedule queue processing after all InitializeOnLoadMethod callbacks complete
|
||||
if (!_queueProcessScheduled)
|
||||
{
|
||||
_queueProcessScheduled = true;
|
||||
EditorApplication.delayCall += ProcessWelcomeWindowQueue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Processes queued welcome windows. Marks session as processed and shows first window.
|
||||
/// Called via EditorApplication.delayCall after all InitializeOnLoadMethod callbacks complete.
|
||||
/// </summary>
|
||||
private static void ProcessWelcomeWindowQueue()
|
||||
{
|
||||
// Mark as processed to prevent script reload from showing windows again
|
||||
SessionState.SetBool("NWH_WelcomeWindows_Processed", true);
|
||||
|
||||
// Show first window in queue
|
||||
ShowNextWelcomeWindow();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Shows next welcome window from queue. Called when previous window closes.
|
||||
/// </summary>
|
||||
internal static void ShowNextWelcomeWindow()
|
||||
{
|
||||
if (_welcomeWindowQueue.Count > 0)
|
||||
{
|
||||
AssetInfo assetInfo = _welcomeWindowQueue.Dequeue();
|
||||
ConstructWelcomeWindow(assetInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates and displays WelcomeMessageWindow with specified AssetInfo.
|
||||
/// Uses CreateInstance to allow multiple independent window instances.
|
||||
/// </summary>
|
||||
/// <param name="assetInfo">AssetInfo containing package metadata and URLs</param>
|
||||
private static void ConstructWelcomeWindow(AssetInfo assetInfo)
|
||||
{
|
||||
WelcomeMessageWindow window = ScriptableObject.CreateInstance<WelcomeMessageWindow>();
|
||||
window.assetInfo = assetInfo;
|
||||
window.titleContent = new GUIContent(assetInfo.assetName);
|
||||
window.onCloseCallback = ShowNextWelcomeWindow;
|
||||
window.Show();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Locates and loads AssetInfo asset by name using AssetDatabase search.
|
||||
/// Works with packages in both Assets/ and Packages/ folders.
|
||||
/// </summary>
|
||||
/// <param name="assetName">Asset name to search for</param>
|
||||
/// <param name="assetInfo">Loaded AssetInfo if found, null otherwise</param>
|
||||
/// <returns>True if AssetInfo was found and loaded successfully</returns>
|
||||
private static bool GetAssetInfo(string assetName, out AssetInfo assetInfo)
|
||||
{
|
||||
string searchFilter = $"{assetName} AssetInfo t:AssetInfo";
|
||||
string[] guids = AssetDatabase.FindAssets(searchFilter);
|
||||
|
||||
if (guids.Length == 0)
|
||||
{
|
||||
Debug.LogWarning($"Could not find AssetInfo for '{assetName}'");
|
||||
assetInfo = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
string assetPath = AssetDatabase.GUIDToAssetPath(guids[0]);
|
||||
assetInfo = AssetDatabase.LoadAssetAtPath<AssetInfo>(assetPath);
|
||||
|
||||
if (assetInfo == null)
|
||||
{
|
||||
Debug.LogWarning($"Could not load AssetInfo at path {assetPath}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d00f825a64982c45854e762515cc7b7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Packages/com.nwh.common/Runtime/Camera.meta
Normal file
8
Packages/com.nwh.common/Runtime/Camera.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09ae1b3ddcee82a4bb4716f1fc485721
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
271
Packages/com.nwh.common/Runtime/Camera/CameraChanger.cs
Normal file
271
Packages/com.nwh.common/Runtime/Camera/CameraChanger.cs
Normal file
@@ -0,0 +1,271 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using System.Collections.Generic;
|
||||
using NWH.Common.Input;
|
||||
using NWH.Common.Vehicles;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
#if UNITY_EDITOR
|
||||
using NWH.NUI;
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Cameras
|
||||
{
|
||||
/// <summary>
|
||||
/// Switches between the camera objects that are children to this object and contain camera tag,
|
||||
/// in order they appear in the hierarchy or in order they are added to the vehicle cameras list.
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(20)]
|
||||
public class CameraChanger : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// If true vehicleCameras list will be filled through cameraTag.
|
||||
/// </summary>
|
||||
[Tooltip(" If true vehicleCameras list will be filled through cameraTag.")]
|
||||
public bool autoFindCameras = true;
|
||||
|
||||
/// <summary>
|
||||
/// List of cameras that the changer will cycle through. Leave empty if you want cameras to be automatically detected.
|
||||
/// To be detected cameras need to have camera tag and be children of the object this script is attached to.
|
||||
/// </summary>
|
||||
[FormerlySerializedAs("vehicleCameras")]
|
||||
[Tooltip(
|
||||
"List of cameras that the changer will cycle through. Leave empty if you want cameras to be automatically detected." +
|
||||
" To be detected cameras need to have camera tag and be children of the object this script is attached to.")]
|
||||
public List<GameObject> cameras = new();
|
||||
|
||||
/// <summary>
|
||||
/// Index of the camera from vehicle cameras list that will be active first.
|
||||
/// </summary>
|
||||
[Tooltip(" Index of the camera from vehicle cameras list that will be active first.")]
|
||||
public int currentCameraIndex;
|
||||
|
||||
private Vehicle _vehicle;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Has to be OnEnable as to run before the VehicleController initialization.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
_vehicle = GetComponentInParent<Vehicle>();
|
||||
if (!_vehicle)
|
||||
{
|
||||
Debug.LogError("None of the parent objects of CameraChanger contain VehicleController.");
|
||||
}
|
||||
|
||||
_vehicle.onEnable.AddListener(EnableCurrentDisableOthers);
|
||||
_vehicle.onDisable.AddListener(DisableAllCameras);
|
||||
_vehicle.onMultiplayerStatusChanged.AddListener(OnMultiplayerInstanceTypeChanged);
|
||||
|
||||
if (!_vehicle)
|
||||
{
|
||||
Debug.Log("None of the parents of camera changer contain VehicleController component. " +
|
||||
"Make sure that the camera changer is amongst the children of VehicleController object.");
|
||||
}
|
||||
|
||||
if (autoFindCameras)
|
||||
{
|
||||
cameras = new List<GameObject>();
|
||||
foreach (Camera cam in GetComponentsInChildren<Camera>(true))
|
||||
{
|
||||
cameras.Add(cam.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
if (cameras.Count == 0)
|
||||
{
|
||||
Debug.LogWarning("No cameras could be found by CameraChanger. Either add cameras manually or " +
|
||||
"add them as children to the game object this script is attached to.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_vehicle.enabled && !_vehicle.MultiplayerIsRemote && InputProvider.Instances.Count > 0)
|
||||
{
|
||||
bool changeCamera = InputProvider.CombinedInput<SceneInputProviderBase>(i => i.ChangeCamera());
|
||||
|
||||
if (changeCamera)
|
||||
{
|
||||
NextCamera();
|
||||
CheckIfInside();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnMultiplayerInstanceTypeChanged(bool isRemote)
|
||||
{
|
||||
if (isRemote)
|
||||
{
|
||||
DisableAllCameras();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void EnableCurrentDisableOthers()
|
||||
{
|
||||
if (_vehicle.MultiplayerIsRemote)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int cameraCount = cameras.Count;
|
||||
for (int i = 0; i < cameraCount; i++)
|
||||
{
|
||||
if (cameras[i] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i == currentCameraIndex)
|
||||
{
|
||||
cameras[i].SetActive(true);
|
||||
AudioListener al = cameras[i].GetComponent<AudioListener>();
|
||||
if (al != null)
|
||||
{
|
||||
al.enabled = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cameras[i].SetActive(false);
|
||||
AudioListener al = cameras[i].GetComponent<AudioListener>();
|
||||
if (al != null)
|
||||
{
|
||||
al.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void DisableAllCameras()
|
||||
{
|
||||
int cameraCount = cameras.Count;
|
||||
for (int i = 0; i < cameraCount; i++)
|
||||
{
|
||||
cameras[i].SetActive(false);
|
||||
AudioListener al = cameras[i].GetComponent<AudioListener>();
|
||||
if (al != null)
|
||||
{
|
||||
al.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Activates the next camera in the list, cycling back to the first camera when reaching the end.
|
||||
/// Automatically disables all other cameras and their AudioListeners.
|
||||
/// </summary>
|
||||
public void NextCamera()
|
||||
{
|
||||
if (cameras.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
currentCameraIndex++;
|
||||
if (currentCameraIndex >= cameras.Count)
|
||||
{
|
||||
currentCameraIndex = 0;
|
||||
}
|
||||
|
||||
EnableCurrentDisableOthers();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Activates the previous camera in the list, cycling back to the last camera when reaching the beginning.
|
||||
/// Automatically disables all other cameras and their AudioListeners.
|
||||
/// </summary>
|
||||
public void PreviousCamera()
|
||||
{
|
||||
if (cameras.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
currentCameraIndex--;
|
||||
if (currentCameraIndex < 0)
|
||||
{
|
||||
currentCameraIndex = cameras.Count - 1;
|
||||
}
|
||||
|
||||
EnableCurrentDisableOthers();
|
||||
}
|
||||
|
||||
|
||||
private void CheckIfInside()
|
||||
{
|
||||
if (cameras.Count == 0 || cameras[currentCameraIndex] == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CameraInsideVehicle civ = cameras[currentCameraIndex]?.GetComponent<CameraInsideVehicle>();
|
||||
if (civ != null)
|
||||
{
|
||||
_vehicle.CameraInsideVehicle = civ.isInsideVehicle;
|
||||
}
|
||||
else
|
||||
{
|
||||
_vehicle.CameraInsideVehicle = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
namespace NWH.Common.Cameras
|
||||
{
|
||||
[CustomEditor(typeof(CameraChanger))]
|
||||
[CanEditMultipleObjects]
|
||||
public class CameraChangerEditor : NUIEditor
|
||||
{
|
||||
public override bool OnInspectorNUI()
|
||||
{
|
||||
if (!base.OnInspectorNUI())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
drawer.Field("currentCameraIndex");
|
||||
if (drawer.Field("autoFindCameras").boolValue)
|
||||
{
|
||||
drawer.Info(
|
||||
"When using autoFindCameras make sure that all the cameras are direct children of the object this script is attached to.");
|
||||
}
|
||||
else
|
||||
{
|
||||
drawer.ReorderableList("cameras");
|
||||
}
|
||||
|
||||
drawer.EndEditor(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public override bool UseDefaultMargins()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
12
Packages/com.nwh.common/Runtime/Camera/CameraChanger.cs.meta
Normal file
12
Packages/com.nwh.common/Runtime/Camera/CameraChanger.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ebbf52df36e2d984489f1a6c789c9e95
|
||||
timeCreated: 1510062979
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,80 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using NWH.Common.Vehicles;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using NWH.NUI;
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Cameras
|
||||
{
|
||||
/// <summary>
|
||||
/// Empty component that should be attached to the cameras that are inside the vehicle if interior sound change is to
|
||||
/// be used.
|
||||
/// </summary>
|
||||
public class CameraInsideVehicle : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Is the camera inside vehicle?
|
||||
/// </summary>
|
||||
[Tooltip(" Is the camera inside vehicle?")]
|
||||
public bool isInsideVehicle = true;
|
||||
|
||||
private Vehicle _vehicle;
|
||||
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_vehicle = GetComponentInParent<Vehicle>();
|
||||
Debug.Assert(_vehicle != null,
|
||||
"CameraInsideVehicle needs to be attached to an object containing a Vehicle script.");
|
||||
}
|
||||
|
||||
|
||||
private void Update()
|
||||
{
|
||||
_vehicle.CameraInsideVehicle = isInsideVehicle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
namespace NWH.Common.Cameras
|
||||
{
|
||||
[CustomEditor(typeof(CameraInsideVehicle))]
|
||||
[CanEditMultipleObjects]
|
||||
public class CameraInsideVehicleEditor : NUIEditor
|
||||
{
|
||||
public override bool OnInspectorNUI()
|
||||
{
|
||||
if (!base.OnInspectorNUI())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
drawer.Field("isInsideVehicle");
|
||||
|
||||
drawer.EndEditor(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public override bool UseDefaultMargins()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b660c5d2e66c601438dc90ebb20d7357
|
||||
timeCreated: 1516720286
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
657
Packages/com.nwh.common/Runtime/Camera/CameraMouseDrag.cs
Normal file
657
Packages/com.nwh.common/Runtime/Camera/CameraMouseDrag.cs
Normal file
@@ -0,0 +1,657 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using NWH.Common.Input;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.Serialization;
|
||||
#if UNITY_EDITOR
|
||||
using NWH.NUI;
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Cameras
|
||||
{
|
||||
/// <summary>
|
||||
/// Camera that can be dragged with the mouse. Supports Forza-style dynamics.
|
||||
/// </summary>
|
||||
public class CameraMouseDrag : VehicleCamera
|
||||
{
|
||||
public enum POVType
|
||||
{
|
||||
FirstPerson,
|
||||
ThirdPerson,
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// POV & Basic Settings
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
[Tooltip("Camera POV type. First person camera will invert controls.\r\nZoom is not available in 1st person.")]
|
||||
public POVType povType = POVType.ThirdPerson;
|
||||
|
||||
[Tooltip("Can the camera be rotated by the user?")]
|
||||
public bool allowRotation = true;
|
||||
|
||||
[Tooltip("Can the camera be panned by the user?")]
|
||||
public bool allowPanning = true;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Distance & Position
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
[Range(0, 100f)]
|
||||
[Tooltip("Base distance from target at which camera will be positioned.")]
|
||||
public float distance = 6f;
|
||||
|
||||
[Range(0, 100f)]
|
||||
[Tooltip("Minimum distance that will be reached when zooming in.")]
|
||||
public float minDistance = 3.0f;
|
||||
|
||||
[Range(0, 100f)]
|
||||
[Tooltip("Maximum distance that will be reached when zooming out.")]
|
||||
public float maxDistance = 13.0f;
|
||||
|
||||
[Range(0, 15)]
|
||||
[Tooltip("Sensitivity of the middle mouse button / wheel.")]
|
||||
public float zoomSensitivity = 1f;
|
||||
|
||||
[Tooltip("Look position offset from the target center.")]
|
||||
public Vector3 targetPositionOffset = Vector3.zero;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Rotation
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
[FormerlySerializedAs("followTargetsRotation")]
|
||||
[Tooltip("If true the camera will rotate with the vehicle along the X and Y axis.")]
|
||||
public bool followTargetPitchAndYaw = true;
|
||||
|
||||
[Tooltip("If true the camera will rotate with the vehicle along the Z axis.")]
|
||||
public bool followTargetRoll;
|
||||
|
||||
[Tooltip("Sensitivity of rotation input.")]
|
||||
public Vector2 rotationSensitivity = new(3f, 3f);
|
||||
|
||||
[Range(-90, 90)]
|
||||
[Tooltip("Maximum vertical angle the camera can achieve.")]
|
||||
public float verticalMaxAngle = 80.0f;
|
||||
|
||||
[Range(-90, 90)]
|
||||
[Tooltip("Minimum vertical angle the camera can achieve.")]
|
||||
public float verticalMinAngle = -20.0f;
|
||||
|
||||
[Tooltip("Initial rotation around the X axis (up/down)")]
|
||||
public float initXRotation = 10f;
|
||||
|
||||
[Tooltip("Initial rotation around the Y axis (left/right)")]
|
||||
public float initYRotation;
|
||||
|
||||
[Range(0, 1)]
|
||||
[Tooltip("Smoothing of the camera rotation.")]
|
||||
public float rotationSmoothing = 0.08f;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Panning
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
[Tooltip("Sensitivity of panning input.")]
|
||||
public Vector2 panningSensitivity = new(0.06f, 0.06f);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Auto-Centering (Third-Person Only)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
[Tooltip("Camera gradually returns behind vehicle after manual rotation stops.")]
|
||||
public bool useAutoCenter = true;
|
||||
|
||||
[Range(0f, 5f)]
|
||||
[Tooltip("Seconds of no rotation input before auto-centering starts.")]
|
||||
public float autoCenterDelay = 1.5f;
|
||||
|
||||
[Range(0.5f, 10f)]
|
||||
[Tooltip("Speed at which camera returns to center. Higher = faster.")]
|
||||
public float autoCenterSpeed = 2f;
|
||||
|
||||
[Range(0f, 10f)]
|
||||
[Tooltip("Minimum vehicle speed (m/s) required for auto-centering. Prevents centering when stationary.")]
|
||||
public float autoCenterMinSpeed = 2f;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Speed-Based FOV (Third-Person Only)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
[Tooltip("Dynamically adjust FOV based on vehicle speed.")]
|
||||
public bool useSpeedFOV = true;
|
||||
|
||||
[Range(30f, 90f)]
|
||||
[Tooltip("Field of view when stationary.")]
|
||||
public float baseFOV = 60f;
|
||||
|
||||
[Range(30f, 120f)]
|
||||
[Tooltip("Maximum field of view at high speed.")]
|
||||
public float maxFOV = 75f;
|
||||
|
||||
[Range(10f, 200f)]
|
||||
[Tooltip("Speed (m/s) at which maximum FOV is reached.")]
|
||||
public float fovSpeedRange = 50f;
|
||||
|
||||
[Range(0f, 1f)]
|
||||
[Tooltip("Smoothing applied to FOV transitions.")]
|
||||
public float fovSmoothing = 0.3f;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Speed-Based Distance (Third-Person Only)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
[Tooltip("Camera pulls back at higher speeds.")]
|
||||
public bool useSpeedDistance = true;
|
||||
|
||||
[Range(0f, 0.2f)]
|
||||
[Tooltip("Extra distance added per m/s of speed.")]
|
||||
public float speedDistanceMultiplier = 0.05f;
|
||||
|
||||
[Range(0f, 10f)]
|
||||
[Tooltip("Maximum additional distance from speed.")]
|
||||
public float maxSpeedDistance = 3f;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Speed-Based Height (Third-Person Only)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
[Tooltip("Camera rises at higher speeds.")]
|
||||
public bool useSpeedHeight = true;
|
||||
|
||||
[Range(0f, 0.1f)]
|
||||
[Tooltip("Extra height added per m/s of speed.")]
|
||||
public float speedHeightMultiplier = 0.02f;
|
||||
|
||||
[Range(0f, 5f)]
|
||||
[Tooltip("Maximum additional height from speed.")]
|
||||
public float maxSpeedHeight = 1f;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Look-Ahead (Third-Person Only)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
[Tooltip("Camera anticipates turns by rotating into the direction of steering.")]
|
||||
public bool useLookAhead = true;
|
||||
|
||||
[Range(0f, 30f)]
|
||||
[Tooltip("Maximum degrees of yaw offset when turning.")]
|
||||
public float lookAheadIntensity = 15f;
|
||||
|
||||
[Range(0f, 1f)]
|
||||
[Tooltip("Smoothing applied to look-ahead. Higher = slower response.")]
|
||||
public float lookAheadSmoothing = 0.2f;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Camera Shake
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
[Tooltip("Should camera movement on acceleration be used?")]
|
||||
public bool useShake = true;
|
||||
|
||||
[Range(0f, 1f)]
|
||||
[Tooltip("Maximum head movement from the initial position.")]
|
||||
public float shakeMaxOffset = 0.2f;
|
||||
|
||||
[Range(0f, 1f)]
|
||||
[Tooltip("How much will the head move around for the given g-force.")]
|
||||
public float shakeIntensity = 0.125f;
|
||||
|
||||
[Range(0f, 1f)]
|
||||
[Tooltip("Smoothing of the head movement.")]
|
||||
public float shakeSmoothing = 0.3f;
|
||||
|
||||
[Tooltip("Movement intensity per axis. Set to 0 to disable movement on that axis or negative to reverse it.")]
|
||||
public Vector3 shakeAxisIntensity = new(1f, 0.5f, 1f);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Private Fields
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
// Core
|
||||
private Vector3 _initialPosition;
|
||||
private bool _isFirstFrame;
|
||||
private Rigidbody _rigidbody;
|
||||
private Camera _camera;
|
||||
|
||||
// Rotation
|
||||
private Vector2 _rot;
|
||||
private Vector3 _lookDir;
|
||||
private Vector3 _lookDirVel;
|
||||
private Vector3 _newLookDir;
|
||||
private Vector3 _lookAtPosition;
|
||||
private Vector3 _pan;
|
||||
|
||||
// Velocity tracking
|
||||
private Vector3 _rbLocalVelocity;
|
||||
private Vector3 _rbPrevLocalVelocity;
|
||||
private Vector3 _rbLocalAcceleration;
|
||||
private float _rbSpeed;
|
||||
|
||||
// Shake
|
||||
private Vector3 _acceleration;
|
||||
private Vector3 _accelerationChangeVelocity;
|
||||
private Vector3 _localAcceleration;
|
||||
private Vector3 _newPositionOffset;
|
||||
private Vector3 _offsetChangeVelocity;
|
||||
private Vector3 _positionOffset;
|
||||
private Vector3 _prevAcceleration;
|
||||
|
||||
// Auto-center
|
||||
private float _timeSinceLastRotationInput;
|
||||
|
||||
// Speed dynamics
|
||||
private float _currentFOV;
|
||||
private float _fovVelocity;
|
||||
private float _speedDistanceOffset;
|
||||
private float _speedDistanceVelocity;
|
||||
private float _speedHeightOffset;
|
||||
private float _speedHeightVelocity;
|
||||
|
||||
// Look-ahead
|
||||
private float _lookAheadOffset;
|
||||
private float _lookAheadVelocity;
|
||||
|
||||
private bool PointerOverUI
|
||||
{
|
||||
get
|
||||
{
|
||||
return EventSystem.current != null &&
|
||||
EventSystem.current.IsPointerOverGameObject();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_initialPosition = transform.localPosition;
|
||||
_rigidbody = target?.GetComponent<Rigidbody>();
|
||||
_camera = GetComponent<Camera>();
|
||||
|
||||
distance = Mathf.Clamp(distance, minDistance, maxDistance);
|
||||
|
||||
_rot.x = initXRotation;
|
||||
_rot.y = initYRotation;
|
||||
_isFirstFrame = true;
|
||||
|
||||
// Initialize FOV
|
||||
_currentFOV = baseFOV;
|
||||
if (_camera != null)
|
||||
{
|
||||
_camera.fieldOfView = baseFOV;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (_rigidbody == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_rbPrevLocalVelocity = _rbLocalVelocity;
|
||||
_rbLocalVelocity = transform.InverseTransformDirection(_rigidbody.linearVelocity);
|
||||
if (Time.fixedDeltaTime > 0f)
|
||||
{
|
||||
_rbLocalAcceleration = (_rbLocalVelocity - _rbPrevLocalVelocity) / Time.fixedDeltaTime;
|
||||
}
|
||||
_rbSpeed = _rbLocalVelocity.z < 0 ? -_rbLocalVelocity.z : _rbLocalVelocity.z;
|
||||
}
|
||||
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool isThirdPerson = povType == POVType.ThirdPerson;
|
||||
bool hadRotationInput = false;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Input Phase
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
bool pointerOverUI = PointerOverUI;
|
||||
if (!pointerOverUI)
|
||||
{
|
||||
Vector2 rotationInput = InputProvider.CombinedInput<SceneInputProviderBase>(i => i.CameraRotation());
|
||||
Vector2 panningInput = InputProvider.CombinedInput<SceneInputProviderBase>(i => i.CameraPanning());
|
||||
float zoomInput = InputProvider.CombinedInput<SceneInputProviderBase>(i => i.CameraZoom());
|
||||
bool rotationModifier =
|
||||
InputProvider.CombinedInput<SceneInputProviderBase>(i => i.CameraRotationModifier());
|
||||
bool panningModifier = InputProvider.CombinedInput<SceneInputProviderBase>(i => i.CameraPanningModifier());
|
||||
|
||||
if (allowRotation && rotationModifier)
|
||||
{
|
||||
float rotMagnitude = rotationInput.sqrMagnitude;
|
||||
if (rotMagnitude > 0.001f)
|
||||
{
|
||||
_rot.y += rotationInput.x * rotationSensitivity.x;
|
||||
_rot.x -= rotationInput.y * rotationSensitivity.y;
|
||||
hadRotationInput = true;
|
||||
_timeSinceLastRotationInput = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
if (allowPanning && panningModifier)
|
||||
{
|
||||
float pX = panningInput.x * panningSensitivity.x;
|
||||
float pY = panningInput.y * panningSensitivity.y;
|
||||
_pan -= target.InverseTransformDirection(transform.right * pX);
|
||||
_pan -= target.InverseTransformDirection(transform.up * pY);
|
||||
}
|
||||
|
||||
_rot.x = ClampAngle(_rot.x, verticalMinAngle, verticalMaxAngle);
|
||||
|
||||
if (isThirdPerson && (zoomInput > 0.0001f || zoomInput < -0.0001f))
|
||||
{
|
||||
distance -= zoomInput * zoomSensitivity;
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Auto-Center Phase (Third-Person Only)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
if (isThirdPerson && useAutoCenter && !hadRotationInput)
|
||||
{
|
||||
_timeSinceLastRotationInput += Time.deltaTime;
|
||||
|
||||
if (_timeSinceLastRotationInput > autoCenterDelay && _rbSpeed > autoCenterMinSpeed)
|
||||
{
|
||||
float centerLerp = autoCenterSpeed * Time.deltaTime;
|
||||
_rot.x = Mathf.Lerp(_rot.x, initXRotation, centerLerp);
|
||||
_rot.y = Mathf.Lerp(_rot.y, initYRotation, centerLerp);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Look-Ahead Phase (Third-Person Only)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
float effectiveLookAheadOffset = 0f;
|
||||
if (isThirdPerson && useLookAhead && _rigidbody != null)
|
||||
{
|
||||
// Use angular velocity for turn anticipation
|
||||
float angularVelY = _rigidbody.angularVelocity.y;
|
||||
// Also factor in lateral velocity for drifts
|
||||
float lateralVel = Vector3.Dot(_rigidbody.linearVelocity, target.right);
|
||||
float turnFactor = angularVelY * 2f + lateralVel * 0.1f;
|
||||
|
||||
float targetLookAhead = Mathf.Clamp(turnFactor * lookAheadIntensity, -lookAheadIntensity, lookAheadIntensity);
|
||||
_lookAheadOffset = Mathf.SmoothDamp(_lookAheadOffset, targetLookAhead, ref _lookAheadVelocity, lookAheadSmoothing);
|
||||
effectiveLookAheadOffset = _lookAheadOffset;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Speed Dynamics Phase (Third-Person Only)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
float effectiveDistance = distance;
|
||||
Vector3 effectiveTargetOffset = targetPositionOffset;
|
||||
|
||||
if (isThirdPerson)
|
||||
{
|
||||
// Speed-based FOV
|
||||
if (useSpeedFOV && _camera != null)
|
||||
{
|
||||
float speedNormalized = Mathf.Clamp01(_rbSpeed / fovSpeedRange);
|
||||
float targetFOV = Mathf.Lerp(baseFOV, maxFOV, speedNormalized);
|
||||
_currentFOV = Mathf.SmoothDamp(_currentFOV, targetFOV, ref _fovVelocity, fovSmoothing);
|
||||
_camera.fieldOfView = _currentFOV;
|
||||
}
|
||||
|
||||
// Speed-based distance
|
||||
if (useSpeedDistance)
|
||||
{
|
||||
float targetSpeedDistance = Mathf.Min(_rbSpeed * speedDistanceMultiplier, maxSpeedDistance);
|
||||
_speedDistanceOffset = Mathf.SmoothDamp(_speedDistanceOffset, targetSpeedDistance, ref _speedDistanceVelocity, 0.2f);
|
||||
effectiveDistance = distance + _speedDistanceOffset;
|
||||
}
|
||||
|
||||
// Speed-based height
|
||||
if (useSpeedHeight)
|
||||
{
|
||||
float targetSpeedHeight = Mathf.Min(_rbSpeed * speedHeightMultiplier, maxSpeedHeight);
|
||||
_speedHeightOffset = Mathf.SmoothDamp(_speedHeightOffset, targetSpeedHeight, ref _speedHeightVelocity, 0.2f);
|
||||
effectiveTargetOffset.y += _speedHeightOffset;
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Position/Rotation Calculation
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
Vector3 forwardVector = followTargetPitchAndYaw ? target.forward : Vector3.forward;
|
||||
Vector3 rightVector = followTargetPitchAndYaw ? target.right : Vector3.right;
|
||||
Vector3 upVector = followTargetPitchAndYaw ? target.up : Vector3.up;
|
||||
|
||||
_lookAtPosition = target.position +
|
||||
target.TransformDirection(effectiveTargetOffset + _pan);
|
||||
|
||||
// Apply look-ahead to yaw
|
||||
float effectiveYaw = _rot.y + effectiveLookAheadOffset;
|
||||
|
||||
_newLookDir = Quaternion.AngleAxis(_rot.x, rightVector) * forwardVector;
|
||||
_newLookDir = Quaternion.AngleAxis(effectiveYaw, upVector) * _newLookDir;
|
||||
|
||||
_lookDir = _isFirstFrame
|
||||
? _newLookDir
|
||||
: Vector3.SmoothDamp(_lookDir, _newLookDir, ref _lookDirVel, rotationSmoothing);
|
||||
_lookDir = Vector3.Normalize(_lookDir);
|
||||
|
||||
if (isThirdPerson)
|
||||
{
|
||||
effectiveDistance = Mathf.Clamp(effectiveDistance, minDistance, maxDistance + maxSpeedDistance);
|
||||
|
||||
Vector3 targetPosition = _lookAtPosition - _lookDir * effectiveDistance;
|
||||
transform.position = targetPosition;
|
||||
transform.forward = _lookDir;
|
||||
|
||||
// Check for ground
|
||||
if (Physics.Raycast(transform.position, -Vector3.up, out RaycastHit hit, 0.5f))
|
||||
{
|
||||
transform.position = hit.point + Vector3.up * 0.5f;
|
||||
}
|
||||
|
||||
transform.rotation =
|
||||
Quaternion.LookRotation(_lookDir, followTargetRoll ? target.up : Vector3.up);
|
||||
}
|
||||
else
|
||||
{
|
||||
transform.localPosition = _initialPosition + _pan;
|
||||
transform.rotation =
|
||||
Quaternion.LookRotation(_lookDir, followTargetRoll ? target.up : Vector3.up);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Camera Shake Phase
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
if (useShake)
|
||||
{
|
||||
_prevAcceleration = _acceleration;
|
||||
_acceleration = _rbLocalAcceleration;
|
||||
_localAcceleration = Vector3.zero;
|
||||
if (target != null)
|
||||
{
|
||||
_localAcceleration = target.TransformDirection(_acceleration);
|
||||
}
|
||||
|
||||
if (!_isFirstFrame)
|
||||
{
|
||||
_newPositionOffset = Vector3.SmoothDamp(_prevAcceleration, _localAcceleration,
|
||||
ref _accelerationChangeVelocity,
|
||||
shakeSmoothing) / 100f * shakeIntensity;
|
||||
_newPositionOffset = Vector3.Scale(_newPositionOffset, shakeAxisIntensity);
|
||||
_positionOffset = Vector3.SmoothDamp(_positionOffset, _newPositionOffset, ref _offsetChangeVelocity,
|
||||
shakeSmoothing);
|
||||
_positionOffset = Vector3.ClampMagnitude(_positionOffset, shakeMaxOffset);
|
||||
transform.position -= target.TransformDirection(_positionOffset) *
|
||||
Mathf.Clamp01(_rbSpeed * 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
_isFirstFrame = false;
|
||||
}
|
||||
|
||||
|
||||
public void OnDrawGizmosSelected()
|
||||
{
|
||||
Gizmos.DrawWireSphere(_lookAtPosition, 0.1f);
|
||||
Gizmos.DrawRay(_lookAtPosition, _lookDir);
|
||||
}
|
||||
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_isFirstFrame = true;
|
||||
}
|
||||
|
||||
|
||||
public float ClampAngle(float angle, float min, float max)
|
||||
{
|
||||
angle = Mathf.Repeat(angle + 180f, 360f) - 180f;
|
||||
return Mathf.Clamp(angle, min, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
namespace NWH.Common.Cameras
|
||||
{
|
||||
[CustomEditor(typeof(CameraMouseDrag))]
|
||||
[CanEditMultipleObjects]
|
||||
public class CameraMouseDragEditor : NUIEditor
|
||||
{
|
||||
public override bool OnInspectorNUI()
|
||||
{
|
||||
if (!base.OnInspectorNUI())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CameraMouseDrag cam = (CameraMouseDrag)target;
|
||||
bool isThirdPerson = cam.povType == CameraMouseDrag.POVType.ThirdPerson;
|
||||
|
||||
drawer.Field("target");
|
||||
|
||||
drawer.BeginSubsection("POV");
|
||||
drawer.Field("povType");
|
||||
drawer.EndSubsection();
|
||||
|
||||
if (isThirdPerson)
|
||||
{
|
||||
drawer.BeginSubsection("Distance & Position");
|
||||
drawer.Field("distance");
|
||||
drawer.Field("minDistance");
|
||||
drawer.Field("maxDistance");
|
||||
drawer.Field("zoomSensitivity");
|
||||
drawer.Field("targetPositionOffset");
|
||||
drawer.EndSubsection();
|
||||
}
|
||||
|
||||
drawer.BeginSubsection("Rotation");
|
||||
drawer.Field("allowRotation");
|
||||
drawer.Field("followTargetPitchAndYaw");
|
||||
drawer.Field("followTargetRoll");
|
||||
drawer.Field("rotationSensitivity");
|
||||
drawer.Field("verticalMaxAngle");
|
||||
drawer.Field("verticalMinAngle");
|
||||
drawer.Field("initXRotation");
|
||||
drawer.Field("initYRotation");
|
||||
drawer.Field("rotationSmoothing");
|
||||
drawer.EndSubsection();
|
||||
|
||||
drawer.BeginSubsection("Panning");
|
||||
if (drawer.Field("allowPanning").boolValue)
|
||||
{
|
||||
drawer.Field("panningSensitivity");
|
||||
}
|
||||
drawer.EndSubsection();
|
||||
|
||||
// Third-person only features
|
||||
if (isThirdPerson)
|
||||
{
|
||||
drawer.BeginSubsection("Auto-Centering");
|
||||
drawer.Info("Camera returns behind vehicle after manual rotation stops.");
|
||||
if (drawer.Field("useAutoCenter").boolValue)
|
||||
{
|
||||
drawer.Field("autoCenterDelay");
|
||||
drawer.Field("autoCenterSpeed");
|
||||
drawer.Field("autoCenterMinSpeed");
|
||||
}
|
||||
drawer.EndSubsection();
|
||||
|
||||
drawer.BeginSubsection("Speed-Based FOV");
|
||||
drawer.Info("FOV increases at high speeds for sense of velocity.");
|
||||
if (drawer.Field("useSpeedFOV").boolValue)
|
||||
{
|
||||
drawer.Field("baseFOV");
|
||||
drawer.Field("maxFOV");
|
||||
drawer.Field("fovSpeedRange");
|
||||
drawer.Field("fovSmoothing");
|
||||
}
|
||||
drawer.EndSubsection();
|
||||
|
||||
drawer.BeginSubsection("Speed-Based Distance");
|
||||
drawer.Info("Camera pulls back at higher speeds.");
|
||||
if (drawer.Field("useSpeedDistance").boolValue)
|
||||
{
|
||||
drawer.Field("speedDistanceMultiplier");
|
||||
drawer.Field("maxSpeedDistance");
|
||||
}
|
||||
drawer.EndSubsection();
|
||||
|
||||
drawer.BeginSubsection("Speed-Based Height");
|
||||
drawer.Info("Camera rises at higher speeds.");
|
||||
if (drawer.Field("useSpeedHeight").boolValue)
|
||||
{
|
||||
drawer.Field("speedHeightMultiplier");
|
||||
drawer.Field("maxSpeedHeight");
|
||||
}
|
||||
drawer.EndSubsection();
|
||||
|
||||
drawer.BeginSubsection("Look-Ahead");
|
||||
drawer.Info("Camera anticipates turns by looking into corners.");
|
||||
if (drawer.Field("useLookAhead").boolValue)
|
||||
{
|
||||
drawer.Field("lookAheadIntensity");
|
||||
drawer.Field("lookAheadSmoothing");
|
||||
}
|
||||
drawer.EndSubsection();
|
||||
}
|
||||
|
||||
drawer.BeginSubsection("Camera Shake");
|
||||
drawer.Info("Movement introduced as a result of acceleration.");
|
||||
if (drawer.Field("useShake").boolValue)
|
||||
{
|
||||
drawer.Field("shakeMaxOffset");
|
||||
drawer.Field("shakeIntensity");
|
||||
drawer.Field("shakeSmoothing");
|
||||
drawer.Field("shakeAxisIntensity");
|
||||
}
|
||||
drawer.EndSubsection();
|
||||
|
||||
drawer.EndEditor(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public override bool UseDefaultMargins()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aed8c99d9552ef84689c55f411f2f0a8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
74
Packages/com.nwh.common/Runtime/Camera/VehicleCamera.cs
Normal file
74
Packages/com.nwh.common/Runtime/Camera/VehicleCamera.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using NWH.NUI;
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Cameras
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for vehicle camera implementations with automatic target detection.
|
||||
/// </summary>
|
||||
/// <seealso cref="CameraChanger"/>
|
||||
/// <seealso cref="CameraInsideVehicle"/>
|
||||
/// <seealso cref="CameraMouseDrag"/>
|
||||
public class VehicleCamera : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Transform to track. Auto-detects parent Rigidbody if not assigned.
|
||||
/// </summary>
|
||||
[Tooltip(
|
||||
"Transform that this script is targeting. Can be left empty if head movement is not being used.")]
|
||||
public Transform target;
|
||||
|
||||
|
||||
public virtual void Awake()
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
target = GetComponentInParent<Rigidbody>()?.transform;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
namespace NWH.Common.Cameras
|
||||
{
|
||||
[CustomEditor(typeof(VehicleCamera))]
|
||||
[CanEditMultipleObjects]
|
||||
public class VehicleCameraEditor : NUIEditor
|
||||
{
|
||||
public override bool OnInspectorNUI()
|
||||
{
|
||||
if (!base.OnInspectorNUI())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
drawer.EndEditor(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public override bool UseDefaultMargins()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
11
Packages/com.nwh.common/Runtime/Camera/VehicleCamera.cs.meta
Normal file
11
Packages/com.nwh.common/Runtime/Camera/VehicleCamera.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96c571c670f315d47a72efdef0393001
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
3
Packages/com.nwh.common/Runtime/CoM.meta
Normal file
3
Packages/com.nwh.common/Runtime/CoM.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a01c2715f1cc472ebdd1fecad54410f3
|
||||
timeCreated: 1609878006
|
||||
47
Packages/com.nwh.common/Runtime/CoM/IMassAffector.cs
Normal file
47
Packages/com.nwh.common/Runtime/CoM/IMassAffector.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.CoM
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for objects that contribute mass and affect vehicle center of mass calculations.
|
||||
/// Implemented by fuel tanks, cargo systems, and other variable mass components.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Mass affectors allow dynamic vehicle physics by contributing their mass and position
|
||||
/// to the overall center of mass calculation. As fuel depletes or cargo loads change,
|
||||
/// the vehicle's handling characteristics update automatically.
|
||||
/// </remarks>
|
||||
public interface IMassAffector
|
||||
{
|
||||
/// <summary>
|
||||
/// Current mass of this affector in kilograms.
|
||||
/// Should return variable values for fuel tanks, cargo, etc.
|
||||
/// </summary>
|
||||
/// <returns>Mass in kg</returns>
|
||||
float GetMass();
|
||||
|
||||
/// <summary>
|
||||
/// World position of this affector's center of mass.
|
||||
/// Used for weighted center of mass calculations.
|
||||
/// </summary>
|
||||
Vector3 GetWorldCenterOfMass();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns transform of the mass affector.
|
||||
/// </summary>
|
||||
Transform GetTransform();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a3d7462a39d468281892f29f24f478e
|
||||
timeCreated: 1609878311
|
||||
88
Packages/com.nwh.common/Runtime/CoM/MassAffector.cs
Normal file
88
Packages/com.nwh.common/Runtime/CoM/MassAffector.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using NWH.NUI;
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.CoM
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple mass affector implementation that contributes a fixed mass at its transform position
|
||||
/// to the vehicle's center of mass calculations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use this component for static mass contributions like passengers, cargo, or equipment.
|
||||
/// For dynamic masses like fuel tanks, create a custom IMassAffector implementation that
|
||||
/// returns varying mass values.
|
||||
/// </remarks>
|
||||
public class MassAffector : MonoBehaviour, IMassAffector
|
||||
{
|
||||
/// <summary>
|
||||
/// Mass contribution of this affector in kilograms.
|
||||
/// </summary>
|
||||
public float mass = 100.0f;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the mass of this affector.
|
||||
/// </summary>
|
||||
/// <returns>Mass in kilograms.</returns>
|
||||
public float GetMass()
|
||||
{
|
||||
return mass;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the transform of this mass affector.
|
||||
/// </summary>
|
||||
public Transform GetTransform()
|
||||
{
|
||||
return transform;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the world position of this mass affector's center of mass.
|
||||
/// </summary>
|
||||
public Vector3 GetWorldCenterOfMass()
|
||||
{
|
||||
return transform.position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
namespace NWH.Common.CoM
|
||||
{
|
||||
[CustomEditor(typeof(MassAffector))]
|
||||
[CanEditMultipleObjects]
|
||||
public class MassAffectorEditor : NUIEditor
|
||||
{
|
||||
public override bool OnInspectorNUI()
|
||||
{
|
||||
if (!base.OnInspectorNUI())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
drawer.Field("mass", true, "kg");
|
||||
|
||||
drawer.EndEditor(this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Packages/com.nwh.common/Runtime/CoM/MassAffector.cs.meta
Normal file
11
Packages/com.nwh.common/Runtime/CoM/MassAffector.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2027acb4e3713ee4abb61754e4f34f2a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 0f529537840cdb34e9b744ffe2fec59b, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
615
Packages/com.nwh.common/Runtime/CoM/VariableCenterOfMass.cs
Normal file
615
Packages/com.nwh.common/Runtime/CoM/VariableCenterOfMass.cs
Normal file
@@ -0,0 +1,615 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using System;
|
||||
using NWH.Common.Utility;
|
||||
using NWH.Common.Vehicles;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
#if UNITY_EDITOR
|
||||
using NWH.NUI;
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.CoM
|
||||
{
|
||||
/// <summary>
|
||||
/// Dynamic center of mass and inertia calculation system that updates Rigidbody properties
|
||||
/// based on attached mass affectors like fuel tanks, cargo loads, and passengers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// VariableCenterOfMass enables realistic vehicle physics behavior by automatically adjusting
|
||||
/// center of mass and inertia tensor as vehicle loading changes. This affects handling characteristics,
|
||||
/// stability, and acceleration response without requiring complex rigidbody hierarchies.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The system calculates total mass, weighted center of mass position, and inertia contributions
|
||||
/// from all IMassAffector components. Changes in fuel level, cargo loading, or passenger weight
|
||||
/// immediately affect vehicle dynamics, creating realistic weight distribution effects.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Critical for vehicle realism: Front-heavy vehicles understeer more, rear-heavy vehicles
|
||||
/// may oversteer, and high center of mass increases rollover tendency. The system updates
|
||||
/// these characteristics dynamically based on actual mass distribution.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="IMassAffector"/>
|
||||
/// <seealso cref="MassAffector"/>
|
||||
/// <seealso cref="NWH.Common.Vehicles.Vehicle"/>
|
||||
[DisallowMultipleComponent]
|
||||
[DefaultExecutionOrder(-1000)]
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
public class VariableCenterOfMass : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Objects attached or part of the vehicle affecting its center of mass and inertia.
|
||||
/// </summary>
|
||||
[NonSerialized]
|
||||
public IMassAffector[] affectors;
|
||||
|
||||
/// <summary>
|
||||
/// Base mass of the object, without IMassAffectors.
|
||||
/// </summary>
|
||||
[Tooltip("Base mass of the object, without IMassAffectors.")]
|
||||
public float baseMass = 1400f;
|
||||
|
||||
/// <summary>
|
||||
/// Center of mass of the object. Auto calculated. To adjust center of mass use centerOfMassOffset.
|
||||
/// </summary>
|
||||
[Tooltip(
|
||||
"Center of mass of the rigidbody. Needs to be readjusted when new colliders are added.")]
|
||||
public Vector3 centerOfMass = Vector3.zero;
|
||||
|
||||
/// <summary>
|
||||
/// Combined center of mass, including the Rigidbody and any IMassAffectors.
|
||||
/// </summary>
|
||||
public Vector3 combinedCenterOfMass = Vector3.zero;
|
||||
|
||||
/// <summary>
|
||||
/// Total inertia tensor. Includes Rigidbody and IMassAffectors.
|
||||
/// </summary>
|
||||
public Vector3 combinedInertiaTensor;
|
||||
|
||||
/// <summary>
|
||||
/// Total mass of the object with masses of IMassAffectors counted in.
|
||||
/// </summary>
|
||||
[Tooltip("Total mass of the object with masses of IMassAffectors counted in.")]
|
||||
public float combinedMass = 1400f;
|
||||
|
||||
/// <summary>
|
||||
/// Object dimensions in [m]. X - width, Y - height, Z - length.
|
||||
/// It is important to set the correct dimensions or otherwise inertia might be calculated incorrectly.
|
||||
/// </summary>
|
||||
[Tooltip(
|
||||
"Object dimensions in [m]. X - width, Y - height, Z - length.\r\nIt is important to set the correct dimensions or otherwise inertia might be calculated incorrectly.")]
|
||||
public Vector3 dimensions = new(1.8f, 1.6f, 4.6f);
|
||||
|
||||
/// <summary>
|
||||
/// Vector by which the inertia tensor of the rigidbody will be scaled on Start().
|
||||
/// Due to the uniform density of the rigidbodies, versus the very non-uniform density of a vehicle, inertia can feel
|
||||
/// off.
|
||||
/// Use this to adjust inertia tensor values.
|
||||
/// </summary>
|
||||
[Tooltip(
|
||||
" Vector by which the inertia tensor of the rigidbody will be scaled on Start().\r\n Due to the unform density of the rigidbodies, versus the very non-uniform density of a vehicle, inertia can feel\r\n off.\r\n Use this to adjust inertia tensor values.")]
|
||||
public Vector3 inertiaTensor = new(1000f, 1000f, 1000f);
|
||||
|
||||
/// <summary>
|
||||
/// When enabled the Unity-calculated center of mass will be used.
|
||||
/// </summary>
|
||||
[Tooltip(
|
||||
"When enabled the Unity-calculated center of mass will be used.")]
|
||||
public bool useDefaultCenterOfMass = true;
|
||||
|
||||
/// <summary>
|
||||
/// When true inertia settings will be ignored and default Rigidbody inertia tensor will be used.
|
||||
/// </summary>
|
||||
[Tooltip("When true inertia settings will be ignored and default Rigidbody inertia tensor will be used.")]
|
||||
public bool useDefaultInertia = true;
|
||||
|
||||
/// <summary>
|
||||
/// Should the default Rigidbody mass be used?
|
||||
/// </summary>
|
||||
public bool useDefaultMass = true;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the script will search for any IMassAffectors attached as a child (recursively)
|
||||
/// of this script and use them when calculating mass, center of mass and inertia tensor.
|
||||
/// </summary>
|
||||
public bool useMassAffectors;
|
||||
|
||||
/// <summary>
|
||||
/// When true, properties will be recalculated in the next FixedUpdate.
|
||||
/// Call MarkDirty() when mass affectors change to trigger update.
|
||||
/// </summary>
|
||||
[Tooltip("When true, properties will be recalculated in the next FixedUpdate. Automatically managed.")]
|
||||
public bool isDirty = true;
|
||||
|
||||
private Rigidbody _rigidbody;
|
||||
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
_rigidbody = GetComponent<Rigidbody>();
|
||||
|
||||
if (useDefaultMass)
|
||||
{
|
||||
baseMass = _rigidbody.mass;
|
||||
}
|
||||
|
||||
if (useDefaultInertia)
|
||||
{
|
||||
inertiaTensor = _rigidbody.inertiaTensor;
|
||||
}
|
||||
|
||||
if (useDefaultCenterOfMass)
|
||||
{
|
||||
centerOfMass = _rigidbody.centerOfMass;
|
||||
}
|
||||
|
||||
affectors = GetMassAffectors();
|
||||
UpdateAllProperties();
|
||||
}
|
||||
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (isDirty)
|
||||
{
|
||||
UpdateAllProperties();
|
||||
isDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Mark properties as needing recalculation.
|
||||
/// Call this when mass affectors change (fuel consumption, cargo loading, etc.).
|
||||
/// </summary>
|
||||
public void MarkDirty()
|
||||
{
|
||||
isDirty = true;
|
||||
}
|
||||
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
Initialize();
|
||||
UpdateAllProperties();
|
||||
}
|
||||
|
||||
// CoM
|
||||
Gizmos.color = Color.yellow;
|
||||
Vector3 worldCoM = transform.TransformPoint(centerOfMass);
|
||||
Gizmos.DrawSphere(worldCoM, 0.03f);
|
||||
Handles.Label(worldCoM, "CoM");
|
||||
|
||||
// Mass Affectors
|
||||
Gizmos.color = Color.cyan;
|
||||
|
||||
if (affectors == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < affectors.Length; i++)
|
||||
{
|
||||
if (affectors[i] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Gizmos.DrawSphere(affectors[i].GetTransform().position, 0.05f);
|
||||
}
|
||||
|
||||
// Dimensions
|
||||
if (!useDefaultInertia)
|
||||
{
|
||||
Transform t = transform;
|
||||
Vector3 fwdOffset = t.forward * dimensions.z * 0.5f;
|
||||
Vector3 rightOffset = t.right * dimensions.x * 0.5f;
|
||||
Vector3 upOffset = t.up * dimensions.y * 0.5f;
|
||||
|
||||
Gizmos.color = Color.blue;
|
||||
Gizmos.DrawLine(worldCoM + fwdOffset, worldCoM - fwdOffset);
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawLine(worldCoM + rightOffset, worldCoM - rightOffset);
|
||||
Gizmos.color = Color.green;
|
||||
Gizmos.DrawLine(worldCoM + upOffset, worldCoM - upOffset);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
_rigidbody = GetComponent<Rigidbody>();
|
||||
affectors = GetMassAffectors();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates all Rigidbody properties (mass, center of mass, and inertia) based on current settings and affectors.
|
||||
/// Called automatically when isDirty flag is set.
|
||||
/// </summary>
|
||||
public void UpdateAllProperties()
|
||||
{
|
||||
if (!useDefaultMass)
|
||||
{
|
||||
UpdateMass();
|
||||
}
|
||||
|
||||
if (!useDefaultCenterOfMass)
|
||||
{
|
||||
UpdateCoM();
|
||||
}
|
||||
|
||||
if (!useDefaultInertia)
|
||||
{
|
||||
UpdateInertia();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates and applies the total mass to the Rigidbody.
|
||||
/// Includes mass from affectors if useMassAffectors is enabled.
|
||||
/// </summary>
|
||||
public void UpdateMass()
|
||||
{
|
||||
if (useMassAffectors)
|
||||
{
|
||||
combinedMass = CalculateMass();
|
||||
}
|
||||
else
|
||||
{
|
||||
combinedMass = baseMass;
|
||||
}
|
||||
|
||||
_rigidbody.mass = combinedMass;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates and applies the CoM to the Rigidbody.
|
||||
/// </summary>
|
||||
public void UpdateCoM()
|
||||
{
|
||||
if (useMassAffectors)
|
||||
{
|
||||
combinedCenterOfMass = centerOfMass + CalculateRelativeCenterOfMassOffset();
|
||||
}
|
||||
else
|
||||
{
|
||||
combinedCenterOfMass = centerOfMass;
|
||||
}
|
||||
|
||||
_rigidbody.centerOfMass = combinedCenterOfMass;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates and applies the inertia tensor to the Rigidbody.
|
||||
/// </summary>
|
||||
public void UpdateInertia(bool applyUnchanged = false)
|
||||
{
|
||||
if (useMassAffectors)
|
||||
{
|
||||
combinedInertiaTensor = inertiaTensor + CalculateInertiaTensorOffset(dimensions);
|
||||
}
|
||||
else
|
||||
{
|
||||
combinedInertiaTensor = inertiaTensor;
|
||||
}
|
||||
|
||||
// Inertia tensor of constrained rigidbody will be 0 which causes errors when trying to set.
|
||||
if (combinedInertiaTensor.x > 0 && combinedInertiaTensor.y > 0 && combinedInertiaTensor.z > 0)
|
||||
{
|
||||
_rigidbody.inertiaTensor = combinedInertiaTensor;
|
||||
_rigidbody.inertiaTensorRotation = Quaternion.identity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Updates list of IMassAffectors attached to this object.
|
||||
/// Call after IMassAffector has been added or removed from the object.
|
||||
/// </summary>
|
||||
public IMassAffector[] GetMassAffectors()
|
||||
{
|
||||
return GetComponentsInChildren<IMassAffector>(true);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the mass of the Rigidbody and attached mass affectors.
|
||||
/// </summary>
|
||||
public float CalculateMass()
|
||||
{
|
||||
float massSum = baseMass;
|
||||
|
||||
if (affectors == null)
|
||||
{
|
||||
return massSum;
|
||||
}
|
||||
|
||||
foreach (IMassAffector affector in affectors)
|
||||
{
|
||||
if (affector == null || affector.GetTransform() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (affector.GetTransform().gameObject.activeInHierarchy)
|
||||
{
|
||||
massSum += affector.GetMass();
|
||||
}
|
||||
}
|
||||
|
||||
return massSum;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the center of mass of the Rigidbody and attached mass affectors.
|
||||
/// </summary>
|
||||
public Vector3 CalculateRelativeCenterOfMassOffset()
|
||||
{
|
||||
Vector3 offset = Vector3.zero;
|
||||
|
||||
if (useMassAffectors && affectors != null)
|
||||
{
|
||||
float massSum = CalculateMass();
|
||||
|
||||
for (int i = 0; i < affectors.Length; i++)
|
||||
{
|
||||
if (affectors[i] == null || affectors[i].GetTransform() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
offset += transform.InverseTransformPoint(affectors[i].GetWorldCenterOfMass()) *
|
||||
(affectors[i].GetMass() / massSum);
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the inertia tensor of the Rigidbody and attached mass affectors.
|
||||
/// </summary>
|
||||
public Vector3 CalculateInertiaTensorOffset(Vector3 dimensions)
|
||||
{
|
||||
Vector3 affectorInertiaSum = Vector3.zero;
|
||||
|
||||
if (affectors == null)
|
||||
{
|
||||
return affectorInertiaSum;
|
||||
}
|
||||
|
||||
for (int i = 0; i < affectors.Length; i++)
|
||||
{
|
||||
IMassAffector affector = affectors[i];
|
||||
if (affector == null || affector.GetTransform() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (affector.GetTransform().gameObject.activeInHierarchy)
|
||||
{
|
||||
float mass = affector.GetMass();
|
||||
Vector3 affectorLocalPos = transform.InverseTransformPoint(affector.GetTransform().position);
|
||||
float x = Vector3.ProjectOnPlane(affectorLocalPos, Vector3.right).magnitude * mass;
|
||||
float y = Vector3.ProjectOnPlane(affectorLocalPos, Vector3.up).magnitude * mass;
|
||||
float z = Vector3.ProjectOnPlane(affectorLocalPos, Vector3.forward).magnitude * mass;
|
||||
affectorInertiaSum.x += x * x;
|
||||
affectorInertiaSum.y += y * y;
|
||||
affectorInertiaSum.z += z * z;
|
||||
}
|
||||
}
|
||||
|
||||
return affectorInertiaSum;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates inertia tensor for a cuboid with given dimensions and mass.
|
||||
/// Uses parallel axis theorem for rectangular prism approximation.
|
||||
/// </summary>
|
||||
/// <param name="dimensions">Object dimensions in meters (width, height, length)</param>
|
||||
/// <param name="mass">Total mass in kilograms</param>
|
||||
/// <returns>Inertia tensor components (Ix, Iy, Iz) in kg⋅m²</returns>
|
||||
public static Vector3 CalculateInertia(Vector3 dimensions, float mass)
|
||||
{
|
||||
float c = 1f / 12f * mass;
|
||||
float Ix = c * (dimensions.y * dimensions.y + dimensions.z * dimensions.z);
|
||||
float Iy = c * (dimensions.x * dimensions.x + dimensions.z * dimensions.z);
|
||||
float Iz = c * (dimensions.y * dimensions.y + dimensions.x * dimensions.x);
|
||||
|
||||
return new Vector3(Ix, Iy, Iz);
|
||||
}
|
||||
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
_rigidbody = GetComponent<Rigidbody>();
|
||||
Bounds bounds = gameObject.FindBoundsIncludeChildren();
|
||||
dimensions = new Vector3(bounds.extents.x * 2f, bounds.extents.y * 2f, bounds.extents.z * 2f);
|
||||
Debug.Log($"Detected dimensions of {name} as {dimensions} [m]. If incorrect, adjust manually.");
|
||||
if (dimensions.x < Vehicle.SMALL_NUMBER)
|
||||
{
|
||||
dimensions.x = Vehicle.SMALL_NUMBER;
|
||||
}
|
||||
|
||||
if (dimensions.y < Vehicle.SMALL_NUMBER)
|
||||
{
|
||||
dimensions.y = Vehicle.SMALL_NUMBER;
|
||||
}
|
||||
|
||||
if (dimensions.z < Vehicle.SMALL_NUMBER)
|
||||
{
|
||||
dimensions.z = Vehicle.SMALL_NUMBER;
|
||||
}
|
||||
|
||||
centerOfMass = _rigidbody.centerOfMass;
|
||||
baseMass = _rigidbody.mass;
|
||||
combinedMass = baseMass;
|
||||
inertiaTensor = _rigidbody.inertiaTensor;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the combined center of mass position in world space coordinates.
|
||||
/// </summary>
|
||||
/// <returns>World space position of the center of mass</returns>
|
||||
public Vector3 GetWorldCenterOfMass()
|
||||
{
|
||||
return transform.TransformPoint(combinedCenterOfMass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
namespace NWH.Common.CoM
|
||||
{
|
||||
[CustomEditor(typeof(VariableCenterOfMass))]
|
||||
public class VariableCenterOfMassEditor : NUIEditor
|
||||
{
|
||||
public override bool OnInspectorNUI()
|
||||
{
|
||||
if (!base.OnInspectorNUI())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
VariableCenterOfMass vcom = (VariableCenterOfMass)target;
|
||||
if (vcom == null)
|
||||
{
|
||||
drawer.EndEditor();
|
||||
return false;
|
||||
}
|
||||
|
||||
Rigidbody parentRigidbody = vcom.gameObject.GetComponentInParent<Rigidbody>(true);
|
||||
if (parentRigidbody == null)
|
||||
{
|
||||
drawer.EndEditor();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
foreach (Object o in targets)
|
||||
{
|
||||
VariableCenterOfMass t = (VariableCenterOfMass)o;
|
||||
t.affectors = t.GetMassAffectors();
|
||||
t.UpdateAllProperties();
|
||||
}
|
||||
}
|
||||
|
||||
drawer.BeginSubsection("Mass Affectors");
|
||||
if (drawer.Field("useMassAffectors").boolValue)
|
||||
{
|
||||
if (vcom.affectors != null)
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
vcom.affectors = vcom.GetMassAffectors();
|
||||
}
|
||||
|
||||
for (int i = 0; i < vcom.affectors.Length; i++)
|
||||
{
|
||||
IMassAffector affector = vcom.affectors[i];
|
||||
if (affector == null || affector.GetTransform() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string positionStr = i == 0 ? "(this)" : $"Position = {affector.GetTransform().localPosition}";
|
||||
drawer.Label(
|
||||
$"{affector.GetTransform().name} | Mass = {affector.GetMass()} | {positionStr}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawer.EndSubsection();
|
||||
|
||||
// MASS
|
||||
drawer.BeginSubsection("Mass");
|
||||
if (!drawer.Field("useDefaultMass").boolValue)
|
||||
{
|
||||
float newMass = drawer.Field("baseMass", true, "kg").floatValue;
|
||||
parentRigidbody.mass = newMass;
|
||||
|
||||
if (vcom.useMassAffectors)
|
||||
{
|
||||
drawer.Field("combinedMass", false, "kg");
|
||||
}
|
||||
}
|
||||
|
||||
drawer.EndSubsection();
|
||||
|
||||
// CENTER OF MASS
|
||||
drawer.BeginSubsection("Center Of Mass");
|
||||
if (!drawer.Field("useDefaultCenterOfMass").boolValue)
|
||||
{
|
||||
drawer.Field("centerOfMass");
|
||||
|
||||
if (vcom.useMassAffectors)
|
||||
{
|
||||
drawer.Field("combinedCenterOfMass", false);
|
||||
}
|
||||
}
|
||||
|
||||
drawer.EndSubsection();
|
||||
|
||||
// INERTIA
|
||||
drawer.BeginSubsection("Inertia");
|
||||
if (!drawer.Field("useDefaultInertia").boolValue)
|
||||
{
|
||||
drawer.Field("inertiaTensor", true, "kg m2");
|
||||
if (vcom.useMassAffectors)
|
||||
{
|
||||
drawer.Field("combinedInertiaTensor", false, "kg m2");
|
||||
}
|
||||
|
||||
drawer.BeginSubsection("Calculate Inertia From Dimensions");
|
||||
{
|
||||
drawer.Field("dimensions", true, "m");
|
||||
if (drawer.Button("Calculate"))
|
||||
{
|
||||
vcom.inertiaTensor =
|
||||
VariableCenterOfMass.CalculateInertia(vcom.dimensions, parentRigidbody.mass);
|
||||
EditorUtility.SetDirty(vcom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawer.EndSubsection();
|
||||
|
||||
drawer.EndEditor(this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 818177d2061f4558b66bee63b4cc78f6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 6fde4d0f34470624fbed94f695585e63, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Packages/com.nwh.common/Runtime/Demo.meta
Normal file
8
Packages/com.nwh.common/Runtime/Demo.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 60fc4fb477a32b64bad8e6a902e1c871
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,60 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Demo
|
||||
{
|
||||
/// <summary>
|
||||
/// Displays the name of the currently active main camera in a Text component.
|
||||
/// Updates every 0.1 seconds.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Text))]
|
||||
public class DemoCameraNameDisplay : MonoBehaviour
|
||||
{
|
||||
private Text cameraText;
|
||||
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
cameraText = GetComponent<Text>();
|
||||
StartCoroutine(CameraNameCoroutine());
|
||||
}
|
||||
|
||||
|
||||
private IEnumerator CameraNameCoroutine()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Camera cameraMain = Camera.main;
|
||||
if (cameraMain != null)
|
||||
{
|
||||
cameraText.text = cameraMain.name;
|
||||
}
|
||||
else
|
||||
{
|
||||
cameraText.text = "[no main camera]";
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
StopAllCoroutines();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3de98c3d51e70904c871f0fe1e8f2ec3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
57
Packages/com.nwh.common/Runtime/Demo/DemoDtSetter.cs
Normal file
57
Packages/com.nwh.common/Runtime/Demo/DemoDtSetter.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Demo
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets Time.fixedDeltaTime to a specific value for demo scenes.
|
||||
/// Default is 0.008333s (120Hz) for optimal physics performance.
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(-500)]
|
||||
public class DemoDtSetter : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Target physics update rate in seconds. Default 0.008333s equals 120Hz.
|
||||
/// </summary>
|
||||
public float fixedDeltaTime = 0.008333f; // 120Hz default.
|
||||
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (fixedDeltaTime <= 0.0001f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Time.fixedDeltaTime <= 0.0001f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EditorPrefs.GetBool("DemoDtSetter Warning"))
|
||||
{
|
||||
Debug.Log(
|
||||
$"[Show Once] DemoDtSetter: Setting Time.fixedDeltaTime to {fixedDeltaTime} ({1f / fixedDeltaTime} Hz) " +
|
||||
$"from the current {Time.fixedDeltaTime} ({1f / Time.fixedDeltaTime} Hz). " +
|
||||
"Remove the script from the __SceneManager to disable this, but note that the Sports Car damper stiffness " +
|
||||
"might need to be reduced to prevent jitter.");
|
||||
Time.fixedDeltaTime = fixedDeltaTime;
|
||||
EditorPrefs.SetBool("DemoDtSetter Warning", true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Packages/com.nwh.common/Runtime/Demo/DemoDtSetter.cs.meta
Normal file
11
Packages/com.nwh.common/Runtime/Demo/DemoDtSetter.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9870118831fb3a8498d9436757d12cb9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
50
Packages/com.nwh.common/Runtime/Demo/DemoOscillator.cs
Normal file
50
Packages/com.nwh.common/Runtime/Demo/DemoOscillator.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Demo
|
||||
{
|
||||
/// <summary>
|
||||
/// Moves a Rigidbody in a sinusoidal oscillation pattern.
|
||||
/// Useful for creating moving platforms or obstacles in demo scenes.
|
||||
/// </summary>
|
||||
public class DemoOscillator : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Speed of the oscillation in Hz. Higher values result in faster movement.
|
||||
/// </summary>
|
||||
public float speed = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum displacement from the starting position in each axis.
|
||||
/// </summary>
|
||||
public Vector3 travel;
|
||||
private Rigidbody _rb;
|
||||
|
||||
private Vector3 initPos;
|
||||
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_rb = GetComponent<Rigidbody>();
|
||||
initPos = transform.position;
|
||||
}
|
||||
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
float sinValue = Mathf.Sin(Time.time * speed);
|
||||
_rb.MovePosition(initPos + travel * sinValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Packages/com.nwh.common/Runtime/Demo/DemoOscillator.cs.meta
Normal file
11
Packages/com.nwh.common/Runtime/Demo/DemoOscillator.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 42fceba0509e2f64f86cbf8f2986e3ed
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
45
Packages/com.nwh.common/Runtime/Demo/DemoRotator.cs
Normal file
45
Packages/com.nwh.common/Runtime/Demo/DemoRotator.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Demo
|
||||
{
|
||||
/// <summary>
|
||||
/// Continuously rotates a Rigidbody at a constant rate.
|
||||
/// Useful for rotating platforms or visual elements in demo scenes.
|
||||
/// </summary>
|
||||
public class DemoRotator : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Rotation speed in degrees per second for each axis.
|
||||
/// </summary>
|
||||
public Vector3 rotation;
|
||||
private Transform _cachedTransform;
|
||||
private Rigidbody _rb;
|
||||
private Vector3 _scaledRotation;
|
||||
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_rb = GetComponent<Rigidbody>();
|
||||
_cachedTransform = transform;
|
||||
}
|
||||
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
_scaledRotation = rotation * Time.fixedDeltaTime;
|
||||
_rb.MoveRotation(_cachedTransform.rotation * Quaternion.Euler(_scaledRotation));
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Packages/com.nwh.common/Runtime/Demo/DemoRotator.cs.meta
Normal file
11
Packages/com.nwh.common/Runtime/Demo/DemoRotator.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9cd1053c43b92914a848c55fe7027672
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,61 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using System.Collections;
|
||||
using NWH.Common.Vehicles;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Demo
|
||||
{
|
||||
/// <summary>
|
||||
/// Displays the name and type of the currently active vehicle in a Text component.
|
||||
/// Updates every 0.1 seconds.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Text))]
|
||||
public class DemoVehicleNameDisplay : MonoBehaviour
|
||||
{
|
||||
private Text vehicleText;
|
||||
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
vehicleText = GetComponent<Text>();
|
||||
StartCoroutine(VehicleNameCoroutine());
|
||||
}
|
||||
|
||||
|
||||
private IEnumerator VehicleNameCoroutine()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Vehicle vehicle = Vehicle.ActiveVehicle;
|
||||
if (vehicle != null)
|
||||
{
|
||||
vehicleText.text = $"{vehicle.name} [{vehicle.GetType().Name}]";
|
||||
}
|
||||
else
|
||||
{
|
||||
vehicleText.text = "[no active vehicle]";
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
StopAllCoroutines();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a83e7189e2b8857438a1ff1c25aeec05
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
51
Packages/com.nwh.common/Runtime/Demo/DemoWelcomeMessage.cs
Normal file
51
Packages/com.nwh.common/Runtime/Demo/DemoWelcomeMessage.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Demo
|
||||
{
|
||||
/// <summary>
|
||||
/// Controls the display of a welcome message panel in demo scenes.
|
||||
/// Shows the message when running outside the editor.
|
||||
/// </summary>
|
||||
public class DemoWelcomeMessage : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Button used to close the welcome message panel.
|
||||
/// </summary>
|
||||
public Button closeButton;
|
||||
|
||||
/// <summary>
|
||||
/// GameObject containing the welcome message UI.
|
||||
/// </summary>
|
||||
public GameObject welcomeMessageGO;
|
||||
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (!Application.isEditor)
|
||||
{
|
||||
welcomeMessageGO.SetActive(true);
|
||||
}
|
||||
|
||||
closeButton.onClick.AddListener(Close);
|
||||
}
|
||||
|
||||
|
||||
private void Close()
|
||||
{
|
||||
welcomeMessageGO.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 404404949c2d4e4b8b17c373de46d923
|
||||
timeCreated: 1593263505
|
||||
125
Packages/com.nwh.common/Runtime/Demo/DragObject.cs
Normal file
125
Packages/com.nwh.common/Runtime/Demo/DragObject.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using UnityEngine;
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Demo
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple script that drags Rigidbody behind the mouse cursor when MMB is held down.
|
||||
/// </summary>
|
||||
public class DragObject : MonoBehaviour
|
||||
{
|
||||
private Camera _cam;
|
||||
private Vector3 _direction;
|
||||
private float _distance;
|
||||
private bool _dragging;
|
||||
private bool _draggingButtonWasPressed;
|
||||
private bool _draggingButtonWasReleased;
|
||||
private Vector3 _force;
|
||||
private float _forceMagnitude;
|
||||
private float _forceXz;
|
||||
private Vector3 _globalHitPoint;
|
||||
private RaycastHit _hit;
|
||||
private Vector3 _localHitPoint;
|
||||
private Vector2 _mousePosition;
|
||||
private Ray _mouseRay;
|
||||
private Rigidbody _rb;
|
||||
private Vector3 _rbScreenPos;
|
||||
private Vector3 _resultantForce;
|
||||
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_cam = GetComponent<Camera>();
|
||||
}
|
||||
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_cam == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
_mousePosition = Mouse.current.position.ReadValue();
|
||||
_mouseRay = _cam.ScreenPointToRay(_mousePosition);
|
||||
_draggingButtonWasPressed = Mouse.current.middleButton.wasPressedThisFrame;
|
||||
_draggingButtonWasReleased = Mouse.current.middleButton.wasReleasedThisFrame;
|
||||
#elif ENABLE_LEGACY_INPUT_MANAGER
|
||||
_mousePosition = UnityEngine.Input.mousePosition;
|
||||
_mouseRay = _cam.ScreenPointToRay(_mousePosition);
|
||||
_draggingButtonWasPressed = UnityEngine.Input.GetKeyDown(KeyCode.Mouse2);
|
||||
_draggingButtonWasReleased = UnityEngine.Input.GetKeyUp(KeyCode.Mouse2);
|
||||
#endif
|
||||
|
||||
_mouseRay = _cam.ScreenPointToRay(_mousePosition);
|
||||
|
||||
if (_draggingButtonWasPressed && !_dragging)
|
||||
{
|
||||
if (Physics.Raycast(_mouseRay, out _hit, 600f))
|
||||
{
|
||||
_rb = _hit.transform.GetComponent<Rigidbody>();
|
||||
if (_rb != null)
|
||||
{
|
||||
_dragging = true;
|
||||
_localHitPoint = _rb.transform.InverseTransformPoint(_hit.point);
|
||||
}
|
||||
else
|
||||
{
|
||||
_dragging = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_draggingButtonWasReleased)
|
||||
{
|
||||
_dragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (_dragging)
|
||||
{
|
||||
_globalHitPoint = _rb.transform.TransformPoint(_localHitPoint);
|
||||
_rbScreenPos = _cam.WorldToScreenPoint(_globalHitPoint);
|
||||
|
||||
_distance = Vector2.Distance(_mousePosition, _rbScreenPos);
|
||||
_forceMagnitude = _distance * _rb.mass * 0.1f;
|
||||
|
||||
_direction = ((Vector3)_mousePosition - _rbScreenPos).normalized;
|
||||
_force = _forceMagnitude * _direction;
|
||||
_forceXz = _force.x + _force.z;
|
||||
_resultantForce = new Vector3(_forceXz * transform.right.x, _force.y, _forceXz * transform.right.z);
|
||||
_resultantForce = Vector3.ClampMagnitude(_resultantForce, _rb.mass * 100f);
|
||||
_rb.AddForceAtPosition(_resultantForce, _globalHitPoint);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
if (_dragging)
|
||||
{
|
||||
Gizmos.color = Color.green;
|
||||
Gizmos.DrawSphere(_globalHitPoint, 0.01f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Packages/com.nwh.common/Runtime/Demo/DragObject.cs.meta
Normal file
3
Packages/com.nwh.common/Runtime/Demo/DragObject.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84bc63739ffe466a9c8804e2c882873f
|
||||
timeCreated: 1593436050
|
||||
50
Packages/com.nwh.common/Runtime/Demo/DragObjectEditor.cs
Normal file
50
Packages/com.nwh.common/Runtime/Demo/DragObjectEditor.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
#region
|
||||
|
||||
using NWH.NUI;
|
||||
using UnityEditor;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Demo
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom inspector for DragObject demo component.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(DragObject))]
|
||||
[CanEditMultipleObjects]
|
||||
public class DragObjectEditor : NUIEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Draws custom inspector GUI for DragObject.
|
||||
/// </summary>
|
||||
/// <returns>True if inspector should continue drawing</returns>
|
||||
public override bool OnInspectorNUI()
|
||||
{
|
||||
if (!base.OnInspectorNUI())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
drawer.EndEditor(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public override bool UseDefaultMargins()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a1815302ab69459eb48d1d8c93c0c30e
|
||||
timeCreated: 1593466455
|
||||
239
Packages/com.nwh.common/Runtime/Demo/FpsToText.cs
Normal file
239
Packages/com.nwh.common/Runtime/Demo/FpsToText.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
#endregion
|
||||
|
||||
// Based on: https://forum.unity.com/threads/fpstotext-free-fps-framerate-calculator-with-options.463667/
|
||||
namespace NWH.Common.Demo
|
||||
{
|
||||
/// <summary>
|
||||
/// Displays the current framerate in a Text component with optional color coding.
|
||||
/// Supports both instantaneous and averaged FPS measurements.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Text))]
|
||||
public class FpsToText : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Text color when framerate is below badBelow threshold.
|
||||
/// </summary>
|
||||
public Color bad = Color.red;
|
||||
|
||||
/// <summary>
|
||||
/// FPS threshold below which the color changes to bad (red).
|
||||
/// </summary>
|
||||
public int badBelow = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Round FPS to nearest integer for cleaner display.
|
||||
/// </summary>
|
||||
public bool forceIntResult = true;
|
||||
|
||||
/// <summary>
|
||||
/// Text color when framerate is above okayBelow threshold.
|
||||
/// </summary>
|
||||
public Color good = Color.green;
|
||||
|
||||
/// <summary>
|
||||
/// Use averaging over multiple samples instead of single frame measurement.
|
||||
/// </summary>
|
||||
public bool groupSampling = true;
|
||||
|
||||
/// <summary>
|
||||
/// Text color when framerate is between badBelow and okayBelow.
|
||||
/// </summary>
|
||||
public Color okay = Color.yellow;
|
||||
|
||||
/// <summary>
|
||||
/// FPS threshold below which the color changes to okay (yellow).
|
||||
/// </summary>
|
||||
public int okayBelow = 60;
|
||||
|
||||
/// <summary>
|
||||
/// Number of samples to average when groupSampling is enabled.
|
||||
/// </summary>
|
||||
public int sampleSize = 120;
|
||||
|
||||
/// <summary>
|
||||
/// Use Time.smoothDeltaTime instead of Time.deltaTime for calculations.
|
||||
/// </summary>
|
||||
public bool smoothed = true;
|
||||
|
||||
/// <summary>
|
||||
/// Update text display every N frames. 1 = every frame.
|
||||
/// </summary>
|
||||
public int updateTextEvery = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Enable color coding based on framerate thresholds.
|
||||
/// </summary>
|
||||
public bool useColors = true;
|
||||
|
||||
/// <summary>
|
||||
/// Use Environment.TickCount instead of Time.deltaTime for calculations.
|
||||
/// </summary>
|
||||
public bool useSystemTick;
|
||||
private float _fps;
|
||||
private float[] _fpsSamples;
|
||||
private int _sampleIndex;
|
||||
private int _sysFrameRate;
|
||||
private int _sysLastFrameRate;
|
||||
|
||||
private int _sysLastSysTick;
|
||||
|
||||
private Text _targetText;
|
||||
private int _textUpdateIndex;
|
||||
private System.Text.StringBuilder _stringBuilder;
|
||||
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
_targetText = GetComponent<Text>();
|
||||
_fpsSamples = new float[sampleSize];
|
||||
for (int i = 0; i < _fpsSamples.Length; i++)
|
||||
{
|
||||
_fpsSamples[i] = 0.001f;
|
||||
}
|
||||
|
||||
_stringBuilder = new System.Text.StringBuilder(16);
|
||||
|
||||
if (!_targetText)
|
||||
{
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected virtual void Update()
|
||||
{
|
||||
if (_targetText == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (groupSampling)
|
||||
{
|
||||
Group();
|
||||
}
|
||||
else
|
||||
{
|
||||
SingleFrame();
|
||||
}
|
||||
|
||||
_sampleIndex = _sampleIndex < sampleSize - 1 ? _sampleIndex + 1 : 0;
|
||||
_textUpdateIndex = _textUpdateIndex > updateTextEvery ? 0 : _textUpdateIndex + 1;
|
||||
if (_textUpdateIndex == updateTextEvery)
|
||||
{
|
||||
_stringBuilder.Clear();
|
||||
_stringBuilder.Append((int)_fps);
|
||||
_targetText.text = _stringBuilder.ToString();
|
||||
}
|
||||
|
||||
if (!useColors)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_fps < badBelow)
|
||||
{
|
||||
_targetText.color = bad;
|
||||
return;
|
||||
}
|
||||
|
||||
_targetText.color = _fps < okayBelow ? okay : good;
|
||||
}
|
||||
|
||||
|
||||
protected virtual void Reset()
|
||||
{
|
||||
sampleSize = 20;
|
||||
updateTextEvery = 1;
|
||||
smoothed = true;
|
||||
useColors = true;
|
||||
good = Color.green;
|
||||
okay = Color.yellow;
|
||||
bad = Color.red;
|
||||
okayBelow = 60;
|
||||
badBelow = 30;
|
||||
useSystemTick = false;
|
||||
forceIntResult = true;
|
||||
}
|
||||
|
||||
|
||||
protected virtual void SingleFrame()
|
||||
{
|
||||
if (useSystemTick)
|
||||
{
|
||||
_fps = GetSystemFramerate();
|
||||
}
|
||||
else
|
||||
{
|
||||
float deltaTime = smoothed ? Time.smoothDeltaTime : Time.deltaTime;
|
||||
_fps = deltaTime > 0.0001f ? 1f / deltaTime : 0f;
|
||||
}
|
||||
|
||||
if (forceIntResult)
|
||||
{
|
||||
_fps = (int)_fps;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected virtual void Group()
|
||||
{
|
||||
if (useSystemTick)
|
||||
{
|
||||
_fpsSamples[_sampleIndex] = GetSystemFramerate();
|
||||
}
|
||||
else
|
||||
{
|
||||
float deltaTime = smoothed ? Time.smoothDeltaTime : Time.deltaTime;
|
||||
_fpsSamples[_sampleIndex] = deltaTime > 0.0001f ? 1f / deltaTime : 0f;
|
||||
}
|
||||
|
||||
_fps = 0;
|
||||
bool loop = true;
|
||||
int i = 0;
|
||||
while (loop)
|
||||
{
|
||||
if (i == sampleSize - 1)
|
||||
{
|
||||
loop = false;
|
||||
}
|
||||
|
||||
_fps += _fpsSamples[i];
|
||||
i++;
|
||||
}
|
||||
|
||||
_fps /= _fpsSamples.Length;
|
||||
if (forceIntResult)
|
||||
{
|
||||
_fps = (int)_fps;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected virtual int GetSystemFramerate()
|
||||
{
|
||||
if (Environment.TickCount - _sysLastSysTick >= 1000)
|
||||
{
|
||||
_sysLastFrameRate = _sysFrameRate;
|
||||
_sysFrameRate = 0;
|
||||
_sysLastSysTick = Environment.TickCount;
|
||||
}
|
||||
|
||||
_sysFrameRate++;
|
||||
return _sysLastFrameRate;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Packages/com.nwh.common/Runtime/Demo/FpsToText.cs.meta
Normal file
11
Packages/com.nwh.common/Runtime/Demo/FpsToText.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6e252fc1685d1145bf43bca380cde0e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
137
Packages/com.nwh.common/Runtime/Demo/RigidbodyFPSController.cs
Normal file
137
Packages/com.nwh.common/Runtime/Demo/RigidbodyFPSController.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using NWH.Common.Input;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Demo
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple first-person controller using physics-based movement.
|
||||
/// Useful for testing and navigating demo scenes on foot.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Based on Unity Community Wiki example. Uses Rigidbody for physics-accurate movement
|
||||
/// with mouse-look camera control.
|
||||
/// </remarks>
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
[RequireComponent(typeof(CapsuleCollider))]
|
||||
public class RigidbodyFPSController : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Downward acceleration force in m/s^2.
|
||||
/// </summary>
|
||||
public float gravity = 10.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum height of jumps in meters.
|
||||
/// </summary>
|
||||
public float jumpHeight = 2.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum upward look angle in degrees.
|
||||
/// </summary>
|
||||
public float maximumY = 60f;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum velocity change per fixed update, controls acceleration responsiveness.
|
||||
/// </summary>
|
||||
public float maxVelocityChange = 10.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum downward look angle in degrees.
|
||||
/// </summary>
|
||||
public float minimumY = -60f;
|
||||
|
||||
/// <summary>
|
||||
/// Horizontal mouse look sensitivity.
|
||||
/// </summary>
|
||||
public float sensitivityX = 15f;
|
||||
|
||||
/// <summary>
|
||||
/// Vertical mouse look sensitivity.
|
||||
/// </summary>
|
||||
public float sensitivityY = 15f;
|
||||
|
||||
/// <summary>
|
||||
/// Movement speed in meters per second.
|
||||
/// </summary>
|
||||
public float speed = 10.0f;
|
||||
private Vector2 _cameraRotationInput;
|
||||
|
||||
private bool _grounded;
|
||||
|
||||
private Vector2 _movement;
|
||||
private Rigidbody _rb;
|
||||
private float _rotationY;
|
||||
|
||||
private bool PointerOverUI
|
||||
{
|
||||
get { return EventSystem.current.IsPointerOverGameObject(); }
|
||||
}
|
||||
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_rb = GetComponent<Rigidbody>();
|
||||
_rb.freezeRotation = true;
|
||||
_rb.useGravity = false;
|
||||
}
|
||||
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
_movement = InputProvider.CombinedInput<SceneInputProviderBase>(i => i.CharacterMovement());
|
||||
_cameraRotationInput = InputProvider.CombinedInput<SceneInputProviderBase>(i => i.CameraRotation());
|
||||
|
||||
if (_grounded)
|
||||
{
|
||||
// Calculate how fast we should be moving
|
||||
Vector3 targetVelocity = new(_movement.x, 0, _movement.y);
|
||||
targetVelocity = transform.TransformDirection(targetVelocity);
|
||||
targetVelocity *= speed;
|
||||
|
||||
// Apply a force that attempts to reach our target velocity
|
||||
Vector3 velocity = _rb.linearVelocity;
|
||||
Vector3 velocityChange = targetVelocity - velocity;
|
||||
velocityChange.x = Mathf.Clamp(velocityChange.x, -maxVelocityChange, maxVelocityChange);
|
||||
velocityChange.z = Mathf.Clamp(velocityChange.z, -maxVelocityChange, maxVelocityChange);
|
||||
velocityChange.y = 0;
|
||||
_rb.AddForce(velocityChange, ForceMode.VelocityChange);
|
||||
}
|
||||
|
||||
float timeFactor = Time.deltaTime * 20f;
|
||||
float rotationX = transform.localEulerAngles.y + _cameraRotationInput.x * sensitivityX * timeFactor;
|
||||
_rotationY += _cameraRotationInput.y * sensitivityY * timeFactor;
|
||||
_rotationY = Mathf.Clamp(_rotationY, minimumY, maximumY);
|
||||
transform.localEulerAngles = new Vector3(-_rotationY, rotationX, 0);
|
||||
_rb.AddForce(new Vector3(0, -gravity * _rb.mass, 0));
|
||||
|
||||
_grounded = false;
|
||||
}
|
||||
|
||||
|
||||
private float CalculateJumpVerticalSpeed()
|
||||
{
|
||||
// From the jump height and gravity we deduce the upwards speed
|
||||
// for the character to reach at the apex.
|
||||
return Mathf.Sqrt(2 * jumpHeight * gravity);
|
||||
}
|
||||
|
||||
|
||||
private void OnCollisionStay()
|
||||
{
|
||||
_grounded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7bf5130586c2cd149921367e3e97a8be
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,58 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
#region
|
||||
|
||||
using NWH.NUI;
|
||||
using UnityEditor;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Demo.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom inspector for RigidbodyFPSController demo component.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(RigidbodyFPSController))]
|
||||
[CanEditMultipleObjects]
|
||||
public class RigidbodyFPSControllerEditor : NUIEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Draws custom inspector GUI for RigidbodyFPSController.
|
||||
/// </summary>
|
||||
/// <returns>True if inspector should continue drawing</returns>
|
||||
public override bool OnInspectorNUI()
|
||||
{
|
||||
if (!base.OnInspectorNUI())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
drawer.Field("gravity");
|
||||
drawer.Field("maximumY");
|
||||
drawer.Field("maxVelocityChange");
|
||||
drawer.Field("minimumY");
|
||||
drawer.Field("sensitivityX");
|
||||
drawer.Field("sensitivityY");
|
||||
drawer.Field("speed");
|
||||
|
||||
drawer.EndEditor(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public override bool UseDefaultMargins()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2fefeb2837a4d9b8a048fd5e3ef15ec
|
||||
timeCreated: 1593353781
|
||||
638
Packages/com.nwh.common/Runtime/Demo/WelcomeMessageWindow.cs
Normal file
638
Packages/com.nwh.common/Runtime/Demo/WelcomeMessageWindow.cs
Normal file
@@ -0,0 +1,638 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
#region
|
||||
|
||||
using NWH.NUI;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.AssetInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// EditorWindow displaying welcome message with package information and useful links.
|
||||
/// Shown on first import or version update via CommonInitializationMethods.
|
||||
/// </summary>
|
||||
public class WelcomeMessageWindow : EditorWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// AssetInfo containing package metadata and URLs to display.
|
||||
/// </summary>
|
||||
public AssetInfo assetInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Callback invoked when window is closed. Used to trigger next window in queue.
|
||||
/// </summary>
|
||||
internal System.Action onCloseCallback;
|
||||
|
||||
/// <summary>
|
||||
/// Currently selected sidebar category index.
|
||||
/// </summary>
|
||||
private int selectedSidebarIndex = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Cached logo texture for package branding.
|
||||
/// </summary>
|
||||
private Texture2D logoTexture;
|
||||
|
||||
/// <summary>
|
||||
/// Scroll position for content area.
|
||||
/// </summary>
|
||||
private Vector2 scrollPosition;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Unity callback when window is enabled.
|
||||
/// Logo loading moved to OnGUI due to Unity lifecycle (assetInfo not set until after OnEnable).
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
// Logo will be loaded lazily in OnGUI when assetInfo is available
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Loads package-specific logo texture with fallback to generic NWH logo.
|
||||
/// </summary>
|
||||
private void LoadLogoTexture()
|
||||
{
|
||||
if (assetInfo == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to load package-specific logo based on asset name
|
||||
string assetName = assetInfo.assetName;
|
||||
string logoPath = null;
|
||||
|
||||
if (assetName.Contains("Vehicle Physics"))
|
||||
{
|
||||
logoPath = "NWH Vehicle Physics 2/Editor/logo_light";
|
||||
}
|
||||
else if (assetName.Contains("Dynamic Water Physics"))
|
||||
{
|
||||
logoPath = "Dynamic Water Physics 2/Logos/dwp_logo";
|
||||
}
|
||||
else if (assetName.Contains("Aerodynamics"))
|
||||
{
|
||||
logoPath = "NWH Aerodynamics/Editor/NAE Logo";
|
||||
}
|
||||
else if (assetName.Contains("Wheel Controller"))
|
||||
{
|
||||
logoPath = "Wheel Controller 3D/Editor/logo_wc3d_light";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(logoPath))
|
||||
{
|
||||
logoTexture = Resources.Load<Texture2D>(logoPath);
|
||||
}
|
||||
|
||||
// Fallback to generic NWH logo
|
||||
if (logoTexture == null)
|
||||
{
|
||||
logoPath = "Editor/NWHLogoSquare";
|
||||
logoTexture = Resources.Load<Texture2D>(logoPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Draws welcome message GUI with package info, documentation links, and support resources.
|
||||
/// </summary>
|
||||
/// <param name="assetInfo">AssetInfo containing package metadata</param>
|
||||
/// <param name="width">Window width in pixels</param>
|
||||
public static void DrawWelcomeMessage(AssetInfo assetInfo, float width = 300f)
|
||||
{
|
||||
if (assetInfo == null)
|
||||
{
|
||||
Debug.LogWarning("AssetInfo is null");
|
||||
return;
|
||||
}
|
||||
|
||||
GUIStyle style = new(EditorStyles.helpBox);
|
||||
style.margin = new RectOffset(10, 10, 10, 12);
|
||||
style.padding = new RectOffset(10, 10, 10, 12);
|
||||
|
||||
GUILayout.BeginVertical(style, GUILayout.Width(width - 35f));
|
||||
GUILayout.Space(8);
|
||||
GUILayout.Label($"Welcome to {assetInfo.assetName}", EditorStyles.boldLabel);
|
||||
GUILayout.Space(15);
|
||||
GUILayout.Label($"Thank you for purchasing {assetInfo.assetName}.\n" +
|
||||
"Check out the following links:");
|
||||
GUILayout.Space(10);
|
||||
GUILayout.Label("Existing customer?", EditorStyles.centeredGreyMiniLabel);
|
||||
if (GUILayout.Button("Upgrade Notes"))
|
||||
{
|
||||
Application.OpenURL(assetInfo.upgradeNotesURL);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Changelog"))
|
||||
{
|
||||
Application.OpenURL(assetInfo.changelogURL);
|
||||
}
|
||||
|
||||
GUILayout.Space(5);
|
||||
GUILayout.Label("New to the asset?", EditorStyles.centeredGreyMiniLabel);
|
||||
if (GUILayout.Button("Quick Start"))
|
||||
{
|
||||
Application.OpenURL(assetInfo.quickStartURL);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Documentation"))
|
||||
{
|
||||
Application.OpenURL(assetInfo.documentationURL);
|
||||
}
|
||||
|
||||
GUILayout.Space(15);
|
||||
GUILayout.Label("Also, don't forget to join us at Discord:", EditorStyles.centeredGreyMiniLabel);
|
||||
if (GUILayout.Button("Discord Server"))
|
||||
{
|
||||
Application.OpenURL(assetInfo.discordURL);
|
||||
}
|
||||
|
||||
GUILayout.Space(15);
|
||||
GUILayout.Label("Don't have Discord? You can also contact us through:", EditorStyles.centeredGreyMiniLabel);
|
||||
|
||||
if (GUILayout.Button("Email"))
|
||||
{
|
||||
Application.OpenURL(assetInfo.emailURL);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Forum"))
|
||||
{
|
||||
Application.OpenURL(assetInfo.forumURL);
|
||||
}
|
||||
|
||||
GUILayout.Space(15);
|
||||
GUILayout.Label("Enjoying the asset? Please consider leaving a review, \n" +
|
||||
"it means a lot to us developers. Thank you.", EditorStyles.centeredGreyMiniLabel);
|
||||
if (GUILayout.Button("Leave a Review"))
|
||||
{
|
||||
Application.OpenURL(assetInfo.assetURL);
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Unity callback to draw window GUI.
|
||||
/// </summary>
|
||||
private void OnGUI()
|
||||
{
|
||||
if (assetInfo == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Lazy load logo on first draw if needed
|
||||
if (logoTexture == null)
|
||||
{
|
||||
LoadLogoTexture();
|
||||
}
|
||||
|
||||
// Set minimum window size
|
||||
minSize = new Vector2(650f, 450f);
|
||||
|
||||
// Draw logo at top
|
||||
DrawLogoSection();
|
||||
|
||||
// Draw main content area (sidebar + content)
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
// Draw sidebar
|
||||
DrawSidebar();
|
||||
|
||||
// Draw content based on selection
|
||||
DrawContent();
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Draws logo section at top of window with background and padding.
|
||||
/// </summary>
|
||||
private void DrawLogoSection()
|
||||
{
|
||||
// Background box for logo section with NWH brand color
|
||||
Color originalColor = GUI.backgroundColor;
|
||||
GUI.backgroundColor = NUISettings.editorHeaderColor;
|
||||
|
||||
GUIStyle logoBackgroundStyle = new GUIStyle(EditorStyles.helpBox)
|
||||
{
|
||||
padding = new RectOffset(20, 20, 15, 15)
|
||||
};
|
||||
|
||||
EditorGUILayout.BeginVertical(logoBackgroundStyle, GUILayout.Height(120));
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
if (logoTexture != null)
|
||||
{
|
||||
Rect logoRect = GUILayoutUtility.GetRect(100, 90, GUILayout.ExpandWidth(true));
|
||||
GUI.DrawTexture(logoRect, logoTexture, ScaleMode.ScaleToFit);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: show asset name if logo fails to load
|
||||
GUIStyle titleStyle = new GUIStyle(EditorStyles.boldLabel)
|
||||
{
|
||||
fontSize = 18,
|
||||
alignment = TextAnchor.MiddleCenter
|
||||
};
|
||||
GUILayout.Label(assetInfo.assetName, titleStyle);
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
GUI.backgroundColor = originalColor;
|
||||
GUILayout.Space(5);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Draws sidebar with category selection buttons.
|
||||
/// </summary>
|
||||
private void DrawSidebar()
|
||||
{
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox, GUILayout.Width(200));
|
||||
|
||||
GUILayout.Space(10);
|
||||
GUILayout.Label("Categories", EditorStyles.boldLabel);
|
||||
GUILayout.Space(15);
|
||||
|
||||
string[] categories = { "Getting Started", "What's New", "Documentation", "Support & Community", "Other Assets" };
|
||||
|
||||
for (int i = 0; i < categories.Length; i++)
|
||||
{
|
||||
GUIStyle buttonStyle = new GUIStyle(GUI.skin.button)
|
||||
{
|
||||
alignment = TextAnchor.MiddleLeft,
|
||||
padding = new RectOffset(10, 10, 8, 8),
|
||||
fontStyle = selectedSidebarIndex == i ? FontStyle.Bold : FontStyle.Normal
|
||||
};
|
||||
|
||||
// NWH brand color for selected item
|
||||
if (selectedSidebarIndex == i)
|
||||
{
|
||||
Color originalColor = GUI.backgroundColor;
|
||||
GUI.backgroundColor = NUISettings.lightBlueColor;
|
||||
if (GUILayout.Button(categories[i], buttonStyle, GUILayout.Height(32)))
|
||||
{
|
||||
selectedSidebarIndex = i;
|
||||
}
|
||||
GUI.backgroundColor = originalColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button(categories[i], buttonStyle, GUILayout.Height(32)))
|
||||
{
|
||||
selectedSidebarIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Space(5);
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Draws content area based on selected sidebar category.
|
||||
/// </summary>
|
||||
private void DrawContent()
|
||||
{
|
||||
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
||||
EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true));
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
switch (selectedSidebarIndex)
|
||||
{
|
||||
case 0: // Getting Started
|
||||
DrawGettingStartedContent();
|
||||
break;
|
||||
case 1: // What's New
|
||||
DrawWhatsNewContent();
|
||||
break;
|
||||
case 2: // Documentation
|
||||
DrawDocumentationContent();
|
||||
break;
|
||||
case 3: // Support & Community
|
||||
DrawSupportContent();
|
||||
break;
|
||||
case 4: // Other Assets
|
||||
DrawOtherAssetsContent();
|
||||
break;
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Draws Getting Started category content.
|
||||
/// </summary>
|
||||
private void DrawGettingStartedContent()
|
||||
{
|
||||
// Welcome section
|
||||
GUIStyle headerStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 14 };
|
||||
GUILayout.Label($"Welcome to {assetInfo.assetName}!", headerStyle);
|
||||
GUILayout.Space(8);
|
||||
|
||||
GUILayout.Label($"Thank you for purchasing {assetInfo.assetName}. " +
|
||||
"We're excited to have you on board!", EditorStyles.wordWrappedLabel);
|
||||
GUILayout.Space(20);
|
||||
|
||||
// Quick Start section
|
||||
DrawSectionHeader("Quick Start");
|
||||
GUILayout.Label("Get up and running quickly with our comprehensive Quick Start guide.",
|
||||
EditorStyles.wordWrappedLabel);
|
||||
GUILayout.Space(8);
|
||||
if (GUILayout.Button("Open Quick Start Guide", GUILayout.Height(35)))
|
||||
{
|
||||
Application.OpenURL(assetInfo.quickStartURL);
|
||||
}
|
||||
|
||||
GUILayout.Space(25);
|
||||
|
||||
// Existing Customer section
|
||||
DrawSectionHeader("Existing Customer?");
|
||||
GUILayout.Label("Already familiar with the asset? Check what's new in this version.",
|
||||
EditorStyles.wordWrappedLabel);
|
||||
GUILayout.Space(8);
|
||||
if (GUILayout.Button("View Upgrade Notes", GUILayout.Height(35)))
|
||||
{
|
||||
Application.OpenURL(assetInfo.upgradeNotesURL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Draws Documentation category content.
|
||||
/// </summary>
|
||||
private void DrawDocumentationContent()
|
||||
{
|
||||
GUIStyle headerStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 14 };
|
||||
GUILayout.Label("Documentation & Resources", headerStyle);
|
||||
GUILayout.Space(15);
|
||||
|
||||
// Full Documentation section
|
||||
DrawSectionHeader("Full Documentation");
|
||||
GUILayout.Label("Complete reference for all features, components, and API documentation.",
|
||||
EditorStyles.wordWrappedLabel);
|
||||
GUILayout.Space(8);
|
||||
if (GUILayout.Button("Open Documentation", GUILayout.Height(35)))
|
||||
{
|
||||
Application.OpenURL(assetInfo.documentationURL);
|
||||
}
|
||||
|
||||
GUILayout.Space(25);
|
||||
|
||||
// Changelog section
|
||||
DrawSectionHeader("Changelog");
|
||||
GUILayout.Label("View complete history of changes, fixes, and improvements across all versions.",
|
||||
EditorStyles.wordWrappedLabel);
|
||||
GUILayout.Space(8);
|
||||
if (GUILayout.Button("View Changelog", GUILayout.Height(35)))
|
||||
{
|
||||
Application.OpenURL(assetInfo.changelogURL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Draws Support & Community category content.
|
||||
/// </summary>
|
||||
private void DrawSupportContent()
|
||||
{
|
||||
GUIStyle headerStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 14 };
|
||||
GUILayout.Label("Support & Community", headerStyle);
|
||||
GUILayout.Space(15);
|
||||
|
||||
// Discord Community section
|
||||
DrawSectionHeader("Discord Community");
|
||||
GUILayout.Label("Join our Discord server for quick help, discussions, and community support.",
|
||||
EditorStyles.wordWrappedLabel);
|
||||
GUILayout.Space(8);
|
||||
if (GUILayout.Button("Join Discord Server", GUILayout.Height(35)))
|
||||
{
|
||||
Application.OpenURL(assetInfo.discordURL);
|
||||
}
|
||||
|
||||
GUILayout.Space(25);
|
||||
|
||||
// Direct Support section
|
||||
DrawSectionHeader("Direct Support");
|
||||
GUILayout.Label("Contact us directly for technical support and assistance.",
|
||||
EditorStyles.wordWrappedLabel);
|
||||
GUILayout.Space(8);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("Email Support", GUILayout.Height(35)))
|
||||
{
|
||||
Application.OpenURL(assetInfo.emailURL);
|
||||
}
|
||||
GUILayout.Space(10);
|
||||
if (GUILayout.Button("Forum", GUILayout.Height(35)))
|
||||
{
|
||||
Application.OpenURL(assetInfo.forumURL);
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(25);
|
||||
|
||||
// Review section
|
||||
DrawSectionHeader("Enjoying the Asset?");
|
||||
GUILayout.Label("Please consider leaving a review. It means a lot to us developers and helps others discover our work!",
|
||||
EditorStyles.wordWrappedLabel);
|
||||
GUILayout.Space(8);
|
||||
if (GUILayout.Button("Leave a Review on Asset Store", GUILayout.Height(35)))
|
||||
{
|
||||
Application.OpenURL(assetInfo.assetURL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Draws What's New category content showing recent updates.
|
||||
/// </summary>
|
||||
private void DrawWhatsNewContent()
|
||||
{
|
||||
GUIStyle headerStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 14 };
|
||||
GUILayout.Label($"What's New in {assetInfo.assetName}", headerStyle);
|
||||
GUILayout.Space(8);
|
||||
|
||||
// Version info
|
||||
GUIStyle versionStyle = new GUIStyle(EditorStyles.label)
|
||||
{
|
||||
fontSize = 12,
|
||||
fontStyle = FontStyle.Italic
|
||||
};
|
||||
GUILayout.Label($"Version {assetInfo.version}", versionStyle);
|
||||
GUILayout.Space(15);
|
||||
|
||||
// Recent updates
|
||||
if (assetInfo.recentUpdates != null && assetInfo.recentUpdates.Length > 0)
|
||||
{
|
||||
DrawSectionHeader("Recent Updates");
|
||||
GUILayout.Space(5);
|
||||
|
||||
foreach (string update in assetInfo.recentUpdates)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(update))
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.Label("•", GUILayout.Width(15));
|
||||
GUILayout.Label(update, EditorStyles.wordWrappedLabel);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
GUILayout.Space(5);
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Space(20);
|
||||
|
||||
// Link to full changelog
|
||||
GUILayout.Label("For complete version history and detailed changes:",
|
||||
EditorStyles.wordWrappedLabel);
|
||||
GUILayout.Space(8);
|
||||
if (GUILayout.Button("View Full Changelog", GUILayout.Height(35)))
|
||||
{
|
||||
Application.OpenURL(assetInfo.changelogURL);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("No recent updates information available.",
|
||||
EditorStyles.centeredGreyMiniLabel);
|
||||
GUILayout.Space(15);
|
||||
if (GUILayout.Button("View Full Changelog", GUILayout.Height(35)))
|
||||
{
|
||||
Application.OpenURL(assetInfo.changelogURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Draws Other Assets category content showing other NWH products.
|
||||
/// </summary>
|
||||
private void DrawOtherAssetsContent()
|
||||
{
|
||||
GUIStyle headerStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 14 };
|
||||
GUILayout.Label("Other NWH Assets", headerStyle);
|
||||
GUILayout.Space(15);
|
||||
|
||||
GUILayout.Label("Explore our other high-quality Unity assets:",
|
||||
EditorStyles.wordWrappedLabel);
|
||||
GUILayout.Space(20);
|
||||
|
||||
// Define other NWH assets (excluding current one)
|
||||
string currentAsset = assetInfo.assetName;
|
||||
|
||||
if (!currentAsset.Contains("Vehicle Physics"))
|
||||
{
|
||||
DrawAssetCard("NWH Vehicle Physics 2",
|
||||
"Complete vehicle physics solution with realistic wheel physics, sound, damage, and more.",
|
||||
"https://assetstore.unity.com/packages/tools/physics/nwh-vehicle-physics-2-166252?aid=1011ljhgE");
|
||||
}
|
||||
|
||||
if (!currentAsset.Contains("Dynamic Water Physics"))
|
||||
{
|
||||
DrawAssetCard("Dynamic Water Physics 2",
|
||||
"Advanced water simulation with buoyancy, ship controllers, and realistic water interactions.",
|
||||
"https://assetstore.unity.com/packages/tools/physics/dynamic-water-physics-2-147990?aid=1011ljhgE");
|
||||
}
|
||||
|
||||
if (!currentAsset.Contains("Wheel Controller"))
|
||||
{
|
||||
DrawAssetCard("Wheel Controller 3D",
|
||||
"Standalone wheel physics controller with advanced tire friction and suspension simulation.",
|
||||
"https://assetstore.unity.com/packages/tools/physics/wheel-controller-3d-49574?aid=1011ljhgE");
|
||||
}
|
||||
|
||||
if (!currentAsset.Contains("Aerodynamics"))
|
||||
{
|
||||
DrawAssetCard("NWH Aerodynamics",
|
||||
"Realistic aerodynamics simulation for aircraft and vehicles with customizable airfoils.",
|
||||
"https://assetstore.unity.com/packages/tools/physics/nwh-aerodynamics-288831?aid=1011ljhgE");
|
||||
}
|
||||
|
||||
GUILayout.Space(15);
|
||||
|
||||
// Link to publisher page
|
||||
DrawSectionHeader("View All Assets");
|
||||
if (GUILayout.Button("Visit NWH Publisher Page", GUILayout.Height(35)))
|
||||
{
|
||||
Application.OpenURL(assetInfo.publisherURL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to draw a section header with consistent styling.
|
||||
/// </summary>
|
||||
private void DrawSectionHeader(string title)
|
||||
{
|
||||
GUIStyle sectionStyle = new GUIStyle(EditorStyles.boldLabel)
|
||||
{
|
||||
fontSize = 12
|
||||
};
|
||||
|
||||
Color originalColor = GUI.contentColor;
|
||||
GUI.contentColor = NUISettings.propertyHeaderColor;
|
||||
GUILayout.Label(title, sectionStyle);
|
||||
GUI.contentColor = originalColor;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to draw an asset card with name, description, and link.
|
||||
/// </summary>
|
||||
private void DrawAssetCard(string assetName, string description, string url)
|
||||
{
|
||||
GUIStyle cardStyle = new GUIStyle(EditorStyles.helpBox)
|
||||
{
|
||||
padding = new RectOffset(12, 12, 10, 10)
|
||||
};
|
||||
|
||||
EditorGUILayout.BeginVertical(cardStyle);
|
||||
|
||||
GUILayout.Label(assetName, EditorStyles.boldLabel);
|
||||
GUILayout.Space(5);
|
||||
GUILayout.Label(description, EditorStyles.wordWrappedLabel);
|
||||
GUILayout.Space(8);
|
||||
|
||||
if (GUILayout.Button("View on Asset Store", GUILayout.Height(30)))
|
||||
{
|
||||
Application.OpenURL(url);
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
GUILayout.Space(12);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Unity callback when window is destroyed. Triggers next window in queue.
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
onCloseCallback?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 776e7980898ca2e488c38390076a85a9
|
||||
8
Packages/com.nwh.common/Runtime/Input.meta
Normal file
8
Packages/com.nwh.common/Runtime/Input.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7644a1abcbe0cde4cadfd526de32b486
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
3
Packages/com.nwh.common/Runtime/Input/InputManager.meta
Normal file
3
Packages/com.nwh.common/Runtime/Input/InputManager.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7fe077b4c52343fa8e5847a73b494315
|
||||
timeCreated: 1593368872
|
||||
@@ -0,0 +1,110 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using NWH.NUI;
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Scene input provider using Unity's legacy Input Manager system.
|
||||
/// Requires input axes and buttons to be configured in Project Settings > Input Manager.
|
||||
/// </summary>
|
||||
public class InputManagerSceneInputProvider : SceneInputProviderBase
|
||||
{
|
||||
public override bool ChangeCamera()
|
||||
{
|
||||
return InputUtils.TryGetButtonDown("ChangeCamera", KeyCode.C);
|
||||
}
|
||||
|
||||
|
||||
public override Vector2 CameraRotation()
|
||||
{
|
||||
return new Vector2(InputUtils.TryGetAxis("CameraRotationX"), InputUtils.TryGetAxis("CameraRotationY"));
|
||||
}
|
||||
|
||||
|
||||
public override Vector2 CameraPanning()
|
||||
{
|
||||
return new Vector2(InputUtils.TryGetAxis("CameraPanningX"), InputUtils.TryGetAxis("CameraPanningY"));
|
||||
}
|
||||
|
||||
|
||||
public override bool CameraRotationModifier()
|
||||
{
|
||||
return InputUtils.TryGetButton("CameraRotationModifier", KeyCode.Mouse0) || !requireCameraRotationModifier;
|
||||
}
|
||||
|
||||
|
||||
public override bool CameraPanningModifier()
|
||||
{
|
||||
return InputUtils.TryGetButton("CameraPanningModifier", KeyCode.Mouse1) || !requireCameraPanningModifier;
|
||||
}
|
||||
|
||||
|
||||
public override float CameraZoom()
|
||||
{
|
||||
return InputUtils.TryGetAxis("CameraZoom");
|
||||
}
|
||||
|
||||
|
||||
public override bool ChangeVehicle()
|
||||
{
|
||||
return InputUtils.TryGetButtonDown("ChangeVehicle", KeyCode.V);
|
||||
}
|
||||
|
||||
|
||||
public override Vector2 CharacterMovement()
|
||||
{
|
||||
return new Vector2(InputUtils.TryGetAxis("FPSMovementX"), InputUtils.TryGetAxis("FPSMovementY"));
|
||||
}
|
||||
|
||||
|
||||
public override bool ToggleGUI()
|
||||
{
|
||||
return InputUtils.TryGetButtonDown("ToggleGUI", KeyCode.Tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
namespace NWH.Common.Input
|
||||
{
|
||||
[CustomEditor(typeof(InputManagerSceneInputProvider))]
|
||||
public class InputManagerSceneInputProviderEditor : NUIEditor
|
||||
{
|
||||
public override bool OnInspectorNUI()
|
||||
{
|
||||
if (!base.OnInspectorNUI())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
drawer.Field("requireCameraRotationModifier");
|
||||
drawer.Field("requireCameraPanningModifier");
|
||||
|
||||
drawer.EndEditor(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public override bool UseDefaultMargins()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec707ef612184d5e9a0dfbf4bdba1e7d
|
||||
timeCreated: 1593368891
|
||||
148
Packages/com.nwh.common/Runtime/Input/InputProvider.cs
Normal file
148
Packages/com.nwh.common/Runtime/Input/InputProvider.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class from which all input providers inherit.
|
||||
/// </summary>
|
||||
public abstract class InputProvider : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// List of all InputProviders in the scene.
|
||||
/// </summary>
|
||||
public static List<InputProvider> Instances = new();
|
||||
|
||||
|
||||
public virtual void Awake()
|
||||
{
|
||||
Instances.Add(this);
|
||||
}
|
||||
|
||||
|
||||
public virtual void OnDestroy()
|
||||
{
|
||||
Instances.Remove(this);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns combined input of all InputProviders present in the scene.
|
||||
/// Result will be a sum of all inputs of the selected type.
|
||||
/// T is a type of InputProvider that the input will be retrieved from.
|
||||
/// </summary>
|
||||
public static int CombinedInput<T>(Func<T, int> selector) where T : InputProvider
|
||||
{
|
||||
int sum = 0;
|
||||
int count = Instances.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (i >= Instances.Count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
InputProvider ip = Instances[i];
|
||||
if (ip != null && ip is T provider)
|
||||
{
|
||||
sum += selector(provider);
|
||||
}
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns combined input of all InputProviders present in the scene.
|
||||
/// Result will be a sum of all inputs of the selected type.
|
||||
/// T is a type of InputProvider that the input will be retrieved from.
|
||||
/// </summary>
|
||||
public static float CombinedInput<T>(Func<T, float> selector) where T : InputProvider
|
||||
{
|
||||
float sum = 0;
|
||||
int count = Instances.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (i >= Instances.Count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
InputProvider ip = Instances[i];
|
||||
if (ip != null && ip is T provider)
|
||||
{
|
||||
sum += selector(provider);
|
||||
}
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns combined input of all InputProviders present in the scene.
|
||||
/// Result will be positive if any InputProvider has the selected input set to true.
|
||||
/// T is a type of InputProvider that the input will be retrieved from.
|
||||
/// </summary>
|
||||
public static bool CombinedInput<T>(Func<T, bool> selector) where T : InputProvider
|
||||
{
|
||||
int count = Instances.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (i >= Instances.Count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
InputProvider ip = Instances[i];
|
||||
if (ip != null && ip is T provider && selector(provider))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns combined input of all InputProviders present in the scene.
|
||||
/// Result will be a sum of all inputs of the selected type.
|
||||
/// T is a type of InputProvider that the input will be retrieved from.
|
||||
/// </summary>
|
||||
public static Vector2 CombinedInput<T>(Func<T, Vector2> selector) where T : InputProvider
|
||||
{
|
||||
Vector2 sum = Vector2.zero;
|
||||
int count = Instances.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (i >= Instances.Count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
InputProvider ip = Instances[i];
|
||||
if (ip != null && ip is T provider)
|
||||
{
|
||||
sum += selector(provider);
|
||||
}
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Packages/com.nwh.common/Runtime/Input/InputProvider.cs.meta
Normal file
11
Packages/com.nwh.common/Runtime/Input/InputProvider.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06379092b8e2d95448e18813ec259a12
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
3
Packages/com.nwh.common/Runtime/Input/InputSystem.meta
Normal file
3
Packages/com.nwh.common/Runtime/Input/InputSystem.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c1d12a1d99348ce902816a92f250e14
|
||||
timeCreated: 1593369086
|
||||
@@ -0,0 +1,135 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using NWH.NUI;
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Unity Input System implementation of scene input provider.
|
||||
/// Handles camera controls and scene navigation using the new Input System.
|
||||
/// </summary>
|
||||
public class InputSystemSceneInputProvider : SceneInputProviderBase
|
||||
{
|
||||
public SceneInputActions sceneInputActions;
|
||||
private bool _panningModifier;
|
||||
|
||||
private bool _rotationModifier;
|
||||
|
||||
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
sceneInputActions = new SceneInputActions();
|
||||
sceneInputActions.Enable();
|
||||
|
||||
sceneInputActions.CameraControls.CameraRotationModifier.started += ctx => _rotationModifier = true;
|
||||
sceneInputActions.CameraControls.CameraRotationModifier.canceled += ctx => _rotationModifier = false;
|
||||
|
||||
sceneInputActions.CameraControls.CameraPanningModifier.started += ctx => _panningModifier = true;
|
||||
sceneInputActions.CameraControls.CameraPanningModifier.canceled += ctx => _panningModifier = false;
|
||||
}
|
||||
|
||||
|
||||
public override bool ChangeCamera()
|
||||
{
|
||||
return sceneInputActions.CameraControls.ChangeCamera.triggered;
|
||||
}
|
||||
|
||||
|
||||
public override Vector2 CameraRotation()
|
||||
{
|
||||
return sceneInputActions.CameraControls.CameraRotation.ReadValue<Vector2>();
|
||||
}
|
||||
|
||||
|
||||
public override Vector2 CameraPanning()
|
||||
{
|
||||
return sceneInputActions.CameraControls.CameraPanning.ReadValue<Vector2>();
|
||||
}
|
||||
|
||||
|
||||
public override bool CameraRotationModifier()
|
||||
{
|
||||
return _rotationModifier || !requireCameraRotationModifier;
|
||||
}
|
||||
|
||||
|
||||
public override bool CameraPanningModifier()
|
||||
{
|
||||
return _panningModifier || !requireCameraPanningModifier;
|
||||
}
|
||||
|
||||
|
||||
public override float CameraZoom()
|
||||
{
|
||||
return sceneInputActions.CameraControls.CameraZoom.ReadValue<float>() * 0.1f;
|
||||
}
|
||||
|
||||
|
||||
public override bool ChangeVehicle()
|
||||
{
|
||||
return sceneInputActions.SceneControls.ChangeVehicle.triggered;
|
||||
}
|
||||
|
||||
|
||||
public override Vector2 CharacterMovement()
|
||||
{
|
||||
return sceneInputActions.SceneControls.FPSMovement.ReadValue<Vector2>();
|
||||
}
|
||||
|
||||
|
||||
public override bool ToggleGUI()
|
||||
{
|
||||
return sceneInputActions.SceneControls.ToggleGUI.triggered;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
namespace NWH.Common.Input
|
||||
{
|
||||
[CustomEditor(typeof(InputSystemSceneInputProvider))]
|
||||
public class InputSystemSceneInputProviderEditor : NUIEditor
|
||||
{
|
||||
public override bool OnInspectorNUI()
|
||||
{
|
||||
if (!base.OnInspectorNUI())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
drawer.Info("Input settings for Unity's new input system can be changed by modifying 'SceneInputActions' " +
|
||||
"file (double click on it to open).");
|
||||
|
||||
drawer.Field("requireCameraRotationModifier");
|
||||
drawer.Field("requireCameraPanningModifier");
|
||||
|
||||
drawer.EndEditor(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public override bool UseDefaultMargins()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c48880c19d04dabb53da07d15024184
|
||||
timeCreated: 1593369107
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dcfa47ed47a318b4db1780a3b325ff0d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,528 @@
|
||||
{
|
||||
"name": "SceneInputActions",
|
||||
"maps": [
|
||||
{
|
||||
"name": "CameraControls",
|
||||
"id": "f9b2c2eb-8265-4430-a0ac-4cf8495a2002",
|
||||
"actions": [
|
||||
{
|
||||
"name": "ChangeCamera",
|
||||
"type": "Button",
|
||||
"id": "71ec0b0c-0911-4b04-a2cc-424b01ebe88e",
|
||||
"expectedControlType": "",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": false
|
||||
},
|
||||
{
|
||||
"name": "CameraRotation",
|
||||
"type": "Value",
|
||||
"id": "8f870466-b390-4fae-a439-ccb19a4537c2",
|
||||
"expectedControlType": "Vector2",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": true
|
||||
},
|
||||
{
|
||||
"name": "CameraPanning",
|
||||
"type": "Value",
|
||||
"id": "08d3e09d-7ab8-4f42-976a-530f947fe4c8",
|
||||
"expectedControlType": "Vector2",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": true
|
||||
},
|
||||
{
|
||||
"name": "CameraRotationModifier",
|
||||
"type": "Button",
|
||||
"id": "124e3374-e4a2-4e74-b0cf-c8959a11ac39",
|
||||
"expectedControlType": "Button",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": false
|
||||
},
|
||||
{
|
||||
"name": "CameraPanningModifier",
|
||||
"type": "Button",
|
||||
"id": "ce8eda53-b48a-45c4-83c7-3f0b44ad36f7",
|
||||
"expectedControlType": "Button",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": false
|
||||
},
|
||||
{
|
||||
"name": "CameraZoom",
|
||||
"type": "Value",
|
||||
"id": "018cdf61-e865-49da-9064-33dc2ae63580",
|
||||
"expectedControlType": "Analog",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": true
|
||||
}
|
||||
],
|
||||
"bindings": [
|
||||
{
|
||||
"name": "",
|
||||
"id": "24fa1b4b-fa43-49bc-ba60-3aedbe8d6c1f",
|
||||
"path": "<Keyboard>/c",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "ChangeCamera",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "530b85ac-4cae-49f9-804b-3a0dbaeb4a7b",
|
||||
"path": "<Gamepad>/start",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "ChangeCamera",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "6d0ae04c-f252-4dd6-824a-27baa3d26db7",
|
||||
"path": "<Mouse>/delta",
|
||||
"interactions": "",
|
||||
"processors": "ScaleVector2(x=0.2,y=0.2)",
|
||||
"groups": "",
|
||||
"action": "CameraRotation",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "Gamepad",
|
||||
"id": "2cb8a8bc-5e28-4393-bc30-fe55c9d9ffc7",
|
||||
"path": "2DVector(mode=2)",
|
||||
"interactions": "",
|
||||
"processors": "InvertVector2",
|
||||
"groups": "",
|
||||
"action": "CameraRotation",
|
||||
"isComposite": true,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "up",
|
||||
"id": "a144950a-0314-41fb-b0a3-0fa7943d12f1",
|
||||
"path": "<Gamepad>/rightStick/up",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "CameraRotation",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "down",
|
||||
"id": "a039c90a-129d-43ea-b2ec-bffde20e618a",
|
||||
"path": "<Gamepad>/rightStick/down",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "CameraRotation",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "left",
|
||||
"id": "e78f01bc-414a-4ba8-83e0-02deb5f631c6",
|
||||
"path": "<Gamepad>/rightStick/left",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "CameraRotation",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "right",
|
||||
"id": "3cd6cbde-a6f8-4da4-8cc2-9c8c1edc133e",
|
||||
"path": "<Gamepad>/rightStick/right",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "CameraRotation",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "9e78c527-4641-4f9b-98e4-fb7f87edf64d",
|
||||
"path": "<Mouse>/delta",
|
||||
"interactions": "",
|
||||
"processors": "ScaleVector2(x=0.2,y=0.2)",
|
||||
"groups": "",
|
||||
"action": "CameraPanning",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "3968d956-a143-403b-87e5-0b91afb999eb",
|
||||
"path": "<Mouse>/leftButton",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "CameraRotationModifier",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "8f3a4e0e-6782-4b53-8c26-e06e68d8e1ee",
|
||||
"path": "<Gamepad>/rightStickPress",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "CameraRotationModifier",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "8a15b75c-fd20-4def-8b73-5d8273fe3364",
|
||||
"path": "<Mouse>/rightButton",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "CameraPanningModifier",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "dee7ac85-80d0-4018-bbe7-114eecc930ae",
|
||||
"path": "<Gamepad>/leftStickPress",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "CameraPanningModifier",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "Gamepad",
|
||||
"id": "93e86e22-3ea3-4e7f-b800-9fc9575e9190",
|
||||
"path": "2DVector",
|
||||
"interactions": "",
|
||||
"processors": "InvertVector2",
|
||||
"groups": "",
|
||||
"action": "CameraPanning",
|
||||
"isComposite": true,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "up",
|
||||
"id": "9e35bef4-dec2-47ce-a040-063273bd2183",
|
||||
"path": "<Gamepad>/rightStick/up",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "CameraPanning",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "down",
|
||||
"id": "ca312712-12f3-438c-a542-d998b4fca387",
|
||||
"path": "<Gamepad>/rightStick/down",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "CameraPanning",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "left",
|
||||
"id": "16a19e86-eb75-40ef-a937-cc69f5c57971",
|
||||
"path": "<Gamepad>/rightStick/left",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "CameraPanning",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "right",
|
||||
"id": "338131ba-47f8-4fc8-b137-39be986200ed",
|
||||
"path": "<Gamepad>/rightStick/right",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "CameraPanning",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "2553a5ac-0892-4d77-a408-8b5fced329a8",
|
||||
"path": "<Mouse>/scroll/y",
|
||||
"interactions": "",
|
||||
"processors": "Scale(factor=0.1)",
|
||||
"groups": "",
|
||||
"action": "CameraZoom",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "Gamepad",
|
||||
"id": "aeaabcc3-6825-4a24-b1b3-13b3a70fff59",
|
||||
"path": "1DAxis(whichSideWins=1)",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "CameraZoom",
|
||||
"isComposite": true,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"id": "d55890ea-00d2-483e-9b0a-e2ba85f4b2dd",
|
||||
"path": "<Gamepad>/dpad/down",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "CameraZoom",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"id": "3ce859ed-6297-4bab-b40b-d6436bacd5ab",
|
||||
"path": "<Gamepad>/dpad/up",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "CameraZoom",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SceneControls",
|
||||
"id": "abb87e97-bffa-439c-a42d-7b1a9497c4cc",
|
||||
"actions": [
|
||||
{
|
||||
"name": "ChangeVehicle",
|
||||
"type": "Button",
|
||||
"id": "a6ddd2a4-de73-4949-8b79-fef6d4b4bc3f",
|
||||
"expectedControlType": "",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": false
|
||||
},
|
||||
{
|
||||
"name": "FPSMovement",
|
||||
"type": "Value",
|
||||
"id": "347a1c7d-d6ca-4838-9d67-ca3bece4074f",
|
||||
"expectedControlType": "Vector2",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": true
|
||||
},
|
||||
{
|
||||
"name": "ToggleGUI",
|
||||
"type": "Button",
|
||||
"id": "420fdb48-6cea-444b-8cd6-256097129d3b",
|
||||
"expectedControlType": "Button",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": false
|
||||
},
|
||||
{
|
||||
"name": "DragObjectModifier",
|
||||
"type": "Button",
|
||||
"id": "1fd9ef37-8fcf-43c4-9b96-ed432f843af4",
|
||||
"expectedControlType": "Button",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": false
|
||||
},
|
||||
{
|
||||
"name": "ShowCursor",
|
||||
"type": "Button",
|
||||
"id": "4566d436-6301-4d31-bd9b-984b19b6cc9b",
|
||||
"expectedControlType": "Button",
|
||||
"processors": "",
|
||||
"interactions": "",
|
||||
"initialStateCheck": false
|
||||
}
|
||||
],
|
||||
"bindings": [
|
||||
{
|
||||
"name": "",
|
||||
"id": "02e5b759-a74a-41e1-af72-80c6990f0d95",
|
||||
"path": "<Keyboard>/v",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "ChangeVehicle",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "01597fdb-29e0-4e77-a920-ba59240fe6d6",
|
||||
"path": "<Gamepad>/select",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "ChangeVehicle",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "Keyboard",
|
||||
"id": "59431748-63e9-4210-8dd9-590e23bcdf0c",
|
||||
"path": "2DVector(mode=1)",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "FPSMovement",
|
||||
"isComposite": true,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "up",
|
||||
"id": "e0b8875d-06d4-467d-b8f0-61da2e804895",
|
||||
"path": "<Keyboard>/w",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "FPSMovement",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "down",
|
||||
"id": "87e4ea7c-c07f-491e-8dc6-36f79dbf9805",
|
||||
"path": "<Keyboard>/s",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "FPSMovement",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "left",
|
||||
"id": "9bba77a1-921c-493d-b881-6f14f1eb377b",
|
||||
"path": "<Keyboard>/a",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "FPSMovement",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "right",
|
||||
"id": "cfa4cf6d-3fe9-4930-847f-b59a8277a8fc",
|
||||
"path": "<Keyboard>/d",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "FPSMovement",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "Gamepad",
|
||||
"id": "208efade-fdb9-49b9-a679-eb44b6ed6ac2",
|
||||
"path": "2DVector(mode=2)",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "FPSMovement",
|
||||
"isComposite": true,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "up",
|
||||
"id": "8087e701-d9e1-454b-8b70-50813a31516b",
|
||||
"path": "<Gamepad>/leftStick/up",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "FPSMovement",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "down",
|
||||
"id": "0b594ea6-b48c-4805-9cea-77058ade6d6a",
|
||||
"path": "<Gamepad>/leftStick/down",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "FPSMovement",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "left",
|
||||
"id": "e7548400-0520-4980-aded-b6d0ac753e4a",
|
||||
"path": "<Gamepad>/leftStick/left",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "FPSMovement",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "right",
|
||||
"id": "b46be990-1176-4948-8642-dddc1bf5ee6c",
|
||||
"path": "<Gamepad>/leftStick/right",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "FPSMovement",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": true
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "9f9f8d86-cd0b-4953-8490-e72ab4b7d8f0",
|
||||
"path": "<Keyboard>/tab",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "ToggleGUI",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "2d06a9ed-570c-45df-ae1b-aec7652096fd",
|
||||
"path": "<Mouse>/middleButton",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "DragObjectModifier",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"id": "2685a4d7-beae-479c-a63b-f7cd494f9c8a",
|
||||
"path": "<Keyboard>/leftCtrl",
|
||||
"interactions": "",
|
||||
"processors": "",
|
||||
"groups": "",
|
||||
"action": "ShowCursor",
|
||||
"isComposite": false,
|
||||
"isPartOfComposite": false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"controlSchemes": []
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e6287ac585a812c479a27c6d0be9f915
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3}
|
||||
generateWrapperCode: 1
|
||||
wrapperCodePath:
|
||||
wrapperClassName:
|
||||
wrapperCodeNamespace: NWH.Common.Input
|
||||
134
Packages/com.nwh.common/Runtime/Input/InputUtils.cs
Normal file
134
Packages/com.nwh.common/Runtime/Input/InputUtils.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility methods for safe input retrieval with automatic fallback to default keys.
|
||||
/// Prevents errors when Input Manager bindings are missing.
|
||||
/// </summary>
|
||||
public class InputUtils
|
||||
{
|
||||
private static int _warningCount;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve button state from Input Manager, falls back to KeyCode if binding is missing.
|
||||
/// </summary>
|
||||
/// <param name="buttonName">Input Manager button name to query.</param>
|
||||
/// <param name="altKey">Fallback KeyCode to use if binding is missing.</param>
|
||||
/// <param name="showWarning">Display warning message when falling back to default key.</param>
|
||||
/// <returns>True if button is currently held down.</returns>
|
||||
public static bool TryGetButton(string buttonName, KeyCode altKey, bool showWarning = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
return UnityEngine.Input.GetButton(buttonName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Make sure warning is not spammed as some users tend to ignore the warning and never set up the input,
|
||||
// resulting in bad performance in editor.
|
||||
if (_warningCount < 100 && showWarning)
|
||||
{
|
||||
Debug.LogWarning(buttonName +
|
||||
" input binding missing, falling back to default. Check Input section in manual for more info.");
|
||||
_warningCount++;
|
||||
}
|
||||
|
||||
return UnityEngine.Input.GetKey(altKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve button press from Input Manager, falls back to KeyCode if binding is missing.
|
||||
/// </summary>
|
||||
/// <param name="buttonName">Input Manager button name to query.</param>
|
||||
/// <param name="altKey">Fallback KeyCode to use if binding is missing.</param>
|
||||
/// <param name="showWarning">Display warning message when falling back to default key.</param>
|
||||
/// <returns>True on the frame the button was pressed.</returns>
|
||||
public static bool TryGetButtonDown(string buttonName, KeyCode altKey, bool showWarning = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
return UnityEngine.Input.GetButtonDown(buttonName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (_warningCount < 100 && showWarning)
|
||||
{
|
||||
Debug.LogWarning(buttonName +
|
||||
" input binding missing, falling back to default. Check Input section in manual for more info.");
|
||||
_warningCount++;
|
||||
}
|
||||
|
||||
return UnityEngine.Input.GetKeyDown(altKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve axis value from Input Manager, returns 0 if binding is missing.
|
||||
/// </summary>
|
||||
/// <param name="axisName">Input Manager axis name to query.</param>
|
||||
/// <param name="showWarning">Display warning message when axis is missing.</param>
|
||||
/// <returns>Axis value between -1 and 1, or 0 if binding is missing.</returns>
|
||||
public static float TryGetAxis(string axisName, bool showWarning = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
return UnityEngine.Input.GetAxis(axisName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (_warningCount < 100 && showWarning)
|
||||
{
|
||||
Debug.LogWarning(axisName +
|
||||
" input binding missing. Check Input section in manual for more info.");
|
||||
_warningCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve raw axis value from Input Manager, returns 0 if binding is missing.
|
||||
/// Raw axes return only -1, 0, or 1 without smoothing.
|
||||
/// </summary>
|
||||
/// <param name="axisName">Input Manager axis name to query.</param>
|
||||
/// <param name="showWarning">Display warning message when axis is missing.</param>
|
||||
/// <returns>Raw axis value (-1, 0, or 1), or 0 if binding is missing.</returns>
|
||||
public static float TryGetAxisRaw(string axisName, bool showWarning = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
return UnityEngine.Input.GetAxisRaw(axisName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (_warningCount < 100 && showWarning)
|
||||
{
|
||||
Debug.LogWarning(axisName +
|
||||
" input binding missing. Check Input section in manual for more info.");
|
||||
_warningCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Packages/com.nwh.common/Runtime/Input/InputUtils.cs.meta
Normal file
3
Packages/com.nwh.common/Runtime/Input/InputUtils.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e808b630f95a45838f09520152a38a93
|
||||
timeCreated: 1593368933
|
||||
3
Packages/com.nwh.common/Runtime/Input/MobileInput.meta
Normal file
3
Packages/com.nwh.common/Runtime/Input/MobileInput.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: deaa0d3d555c4273bde9ce8e169f76b6
|
||||
timeCreated: 1593369326
|
||||
@@ -0,0 +1,51 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Extended Unity UI Button with state tracking for mobile input handling.
|
||||
/// Provides hasBeenClicked and isPressed flags for easier input polling.
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(1000)]
|
||||
public class MobileInputButton : Button
|
||||
{
|
||||
/// <summary>
|
||||
/// True for one frame after the button is clicked. Automatically resets to false.
|
||||
/// </summary>
|
||||
public bool hasBeenClicked;
|
||||
|
||||
/// <summary>
|
||||
/// True while the button is being held down. Updates every frame.
|
||||
/// </summary>
|
||||
public bool isPressed;
|
||||
|
||||
|
||||
private void Update()
|
||||
{
|
||||
isPressed = IsPressed();
|
||||
hasBeenClicked = false;
|
||||
}
|
||||
|
||||
|
||||
public override void OnPointerDown(PointerEventData eventData)
|
||||
{
|
||||
base.OnPointerDown(eventData);
|
||||
|
||||
hasBeenClicked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2117e63fb06445ababc499401d5c062
|
||||
timeCreated: 1593369326
|
||||
@@ -0,0 +1,126 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using NWH.NUI;
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Scene input provider for mobile platforms using on-screen UI buttons.
|
||||
/// Requires MobileInputButton components assigned to changeCameraButton and changeVehicleButton fields.
|
||||
/// </summary>
|
||||
public class MobileSceneInputProvider : SceneInputProviderBase
|
||||
{
|
||||
/// <summary>
|
||||
/// UI button for changing camera. Should reference a MobileInputButton in the scene.
|
||||
/// </summary>
|
||||
public MobileInputButton changeCameraButton;
|
||||
|
||||
/// <summary>
|
||||
/// UI button for changing vehicle. Should reference a MobileInputButton in the scene.
|
||||
/// </summary>
|
||||
public MobileInputButton changeVehicleButton;
|
||||
|
||||
|
||||
public override bool ChangeCamera()
|
||||
{
|
||||
return changeCameraButton != null && changeCameraButton.hasBeenClicked;
|
||||
}
|
||||
|
||||
|
||||
public override bool ChangeVehicle()
|
||||
{
|
||||
return changeVehicleButton != null && changeVehicleButton.hasBeenClicked;
|
||||
}
|
||||
|
||||
|
||||
public override Vector2 CharacterMovement()
|
||||
{
|
||||
return Vector2.zero;
|
||||
}
|
||||
|
||||
|
||||
public override bool ToggleGUI()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public override Vector2 CameraRotation()
|
||||
{
|
||||
return Vector2.zero;
|
||||
}
|
||||
|
||||
|
||||
public override Vector2 CameraPanning()
|
||||
{
|
||||
return Vector2.zero;
|
||||
}
|
||||
|
||||
|
||||
public override bool CameraRotationModifier()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public override bool CameraPanningModifier()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public override float CameraZoom()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
namespace NWH.Common.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Editor for MobileInputProvider.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(MobileSceneInputProvider))]
|
||||
public class MobileSceneInputProviderEditor : NUIEditor
|
||||
{
|
||||
public override bool OnInspectorNUI()
|
||||
{
|
||||
if (!base.OnInspectorNUI())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
drawer.BeginSubsection("Scene Buttons");
|
||||
drawer.Field("changeVehicleButton");
|
||||
drawer.Field("changeCameraButton");
|
||||
drawer.EndSubsection();
|
||||
|
||||
drawer.EndEditor(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public override bool UseDefaultMargins()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 907294c7523f458dba9b3b3e8955d681
|
||||
timeCreated: 1593369326
|
||||
117
Packages/com.nwh.common/Runtime/Input/SceneInputProviderBase.cs
Normal file
117
Packages/com.nwh.common/Runtime/Input/SceneInputProviderBase.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// InputProvider for scene and camera related behavior.
|
||||
/// </summary>
|
||||
public abstract class SceneInputProviderBase : InputProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// If true a button press will be required to unlock camera panning.
|
||||
/// </summary>
|
||||
[Tooltip(" If true a button press will be required to unlock camera panning.")]
|
||||
public bool requireCameraPanningModifier = true;
|
||||
|
||||
/// <summary>
|
||||
/// If true a button press will be required to unlock camera rotation.
|
||||
/// </summary>
|
||||
[Tooltip(" If true a button press will be required to unlock camera rotation.")]
|
||||
public bool requireCameraRotationModifier = true;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns true when the change camera button is pressed.
|
||||
/// </summary>
|
||||
public virtual bool ChangeCamera()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns camera rotation input as a Vector2 (x = horizontal, y = vertical).
|
||||
/// </summary>
|
||||
public virtual Vector2 CameraRotation()
|
||||
{
|
||||
return Vector2.zero;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns camera panning input as a Vector2 (x = horizontal, y = vertical).
|
||||
/// </summary>
|
||||
public virtual Vector2 CameraPanning()
|
||||
{
|
||||
return Vector2.zero;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns true when the camera rotation modifier button is held.
|
||||
/// If requireCameraRotationModifier is false, always returns true.
|
||||
/// </summary>
|
||||
public virtual bool CameraRotationModifier()
|
||||
{
|
||||
return !requireCameraRotationModifier;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns true when the camera panning modifier button is held.
|
||||
/// If requireCameraPanningModifier is false, always returns true.
|
||||
/// </summary>
|
||||
public virtual bool CameraPanningModifier()
|
||||
{
|
||||
return !requireCameraPanningModifier;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns camera zoom input value. Positive = zoom in, negative = zoom out.
|
||||
/// </summary>
|
||||
public virtual float CameraZoom()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns true when the change vehicle button is pressed.
|
||||
/// </summary>
|
||||
public virtual bool ChangeVehicle()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns character movement input as a Vector2 (x = horizontal, y = forward/back).
|
||||
/// </summary>
|
||||
public virtual Vector2 CharacterMovement()
|
||||
{
|
||||
return Vector2.zero;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns true when the toggle GUI button is pressed.
|
||||
/// </summary>
|
||||
public virtual bool ToggleGUI()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9acc2544c2f24c428877837814870a58
|
||||
timeCreated: 1593334735
|
||||
8
Packages/com.nwh.common/Runtime/NUI.meta
Normal file
8
Packages/com.nwh.common/Runtime/NUI.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e4b83cddccfe6934f9df3ce424f9519b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Packages/com.nwh.common/Runtime/NUI/Editor.meta
Normal file
8
Packages/com.nwh.common/Runtime/NUI/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 553b71a9f03c40a4d8551653823451e4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a804ce60c07e08648b7961d49d7c2999
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 57bf16eb97e2c164f997df4cf6c27e38
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 146 B |
@@ -0,0 +1,103 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3fec6694e308c524bb292f84e8eafdc2
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 10
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
mipBias: -100
|
||||
wrapU: -1
|
||||
wrapV: -1
|
||||
wrapW: -1
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 126 B |
@@ -0,0 +1,103 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 87a3220b3c062c0449c545ddc8473dc8
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 10
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
mipBias: -100
|
||||
wrapU: -1
|
||||
wrapV: -1
|
||||
wrapW: -1
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
@@ -0,0 +1,103 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38e8124ac66cf414bbe9ba84c4628ae3
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 10
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
mipBias: -100
|
||||
wrapU: -1
|
||||
wrapV: -1
|
||||
wrapW: -1
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
301
Packages/com.nwh.common/Runtime/NUI/EditorCache.cs
Normal file
301
Packages/com.nwh.common/Runtime/NUI/EditorCache.cs
Normal file
@@ -0,0 +1,301 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
#region
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.NUI
|
||||
{
|
||||
public static class EditorCache
|
||||
{
|
||||
// Workaround to get around the issue of .NET 2.0, asmdef and dynamic.
|
||||
private static readonly Dictionary<string, float> heightCache = new();
|
||||
|
||||
private static readonly Dictionary<string, ReorderableList> reorderableListCache = new();
|
||||
|
||||
private static readonly Dictionary<string, bool> guiWasEnabledCache = new();
|
||||
private static readonly Dictionary<string, NUIEditor> nuiEditorCache = new();
|
||||
private static readonly Dictionary<string, bool> isExpandedCache = new();
|
||||
private static readonly Dictionary<string, int> tabIndexCache = new();
|
||||
private static readonly Dictionary<string, Texture2D> texture2DCache = new();
|
||||
|
||||
private static readonly Dictionary<string, SerializedProperty> serializedPropertyCache = new();
|
||||
|
||||
// Auto-clear cache on assembly reload to prevent unbounded growth
|
||||
[UnityEditor.Callbacks.DidReloadScripts]
|
||||
private static void OnScriptsReloaded()
|
||||
{
|
||||
ClearAll();
|
||||
}
|
||||
|
||||
|
||||
public static bool GetHeightCacheValue(string key, ref float value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return heightCache.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
|
||||
public static bool GetReorderableListCacheValue(string key, ref ReorderableList value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return reorderableListCache.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
|
||||
public static bool GetGuiWasEnabledCValue(string key, ref bool value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return guiWasEnabledCache.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
|
||||
public static bool GetNUIEditorCacheValue(string key, ref NUIEditor value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return nuiEditorCache.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
|
||||
public static bool GetIsExpandedCacheValue(string key, ref bool value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return isExpandedCache.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
|
||||
public static bool GetTabIndexCacheValue(string key, ref int value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return tabIndexCache.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
|
||||
public static bool GetTexture2DCacheValue(string key, ref Texture2D value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return texture2DCache.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
|
||||
public static bool GetSerializedPropertyCacheValue(string key, ref SerializedProperty value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return serializedPropertyCache.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
|
||||
public static bool SetHeightCacheValue(string key, float value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
heightCache[key] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static bool SetReorderableListCacheValue(string key, ReorderableList value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
reorderableListCache[key] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static bool SetGuiWasEnabledCacheValue(string key, bool value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
guiWasEnabledCache[key] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static bool SetNUIEditorCacheValue(string key, NUIEditor value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
nuiEditorCache[key] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static bool SetIsExpandedCacheValue(string key, bool value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
isExpandedCache[key] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static bool SetTabIndexCacheValue(string key, int value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
tabIndexCache[key] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static bool SetTexture2DCacheValue(string key, Texture2D value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
texture2DCache[key] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static bool SetSerializedPropertyCacheValue(string key, SerializedProperty value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
serializedPropertyCache[key] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Clears all cached data. Useful to prevent unbounded memory growth during long editor sessions.
|
||||
/// </summary>
|
||||
public static void ClearAll()
|
||||
{
|
||||
heightCache.Clear();
|
||||
reorderableListCache.Clear();
|
||||
guiWasEnabledCache.Clear();
|
||||
nuiEditorCache.Clear();
|
||||
isExpandedCache.Clear();
|
||||
tabIndexCache.Clear();
|
||||
texture2DCache.Clear();
|
||||
serializedPropertyCache.Clear();
|
||||
}
|
||||
|
||||
|
||||
// // Store data for each property as property drawer gets reused multiple times and local values overwritten
|
||||
// private static readonly Dictionary<string, dynamic> Cache = new Dictionary<string, dynamic>
|
||||
// {
|
||||
// {"height", new Dictionary<string, float>()},
|
||||
// {"ReorderableList", new Dictionary<string, ReorderableList>()},
|
||||
// {"guiWasEnabled", new Dictionary<string, bool>()},
|
||||
// {"NUIEditor", new Dictionary<string, NUIEditor>()},
|
||||
// {"isExpanded", new Dictionary<string, bool>()},
|
||||
// {"tabIndex", new Dictionary<string, int>()},
|
||||
// {"Texture2D", new Dictionary<string, Texture2D>()},
|
||||
// {"SerializedProperty", new Dictionary<string, SerializedProperty>()},
|
||||
// };
|
||||
|
||||
// public static bool GetCachedValue<T>(string variableName, ref T value, string key)
|
||||
// {
|
||||
// if (string.IsNullOrEmpty(key))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if (!Cache.ContainsKey(variableName) || !Cache[variableName].ContainsKey(key))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// value = Cache[variableName][key];
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// public static bool SetCachedValue<T>(string variableName, T value, string key)
|
||||
// {
|
||||
// if (string.IsNullOrEmpty(key))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if (Cache.ContainsKey(variableName))
|
||||
// {
|
||||
// if (!Cache[variableName].ContainsKey(key))
|
||||
// {
|
||||
// Cache[variableName].Add(key, value);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Cache[variableName][key] = value;
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
11
Packages/com.nwh.common/Runtime/NUI/EditorCache.cs.meta
Normal file
11
Packages/com.nwh.common/Runtime/NUI/EditorCache.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8cf6a4455b5e57544b00112d46ccf043
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1014
Packages/com.nwh.common/Runtime/NUI/NUIDrawer.cs
Normal file
1014
Packages/com.nwh.common/Runtime/NUI/NUIDrawer.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Packages/com.nwh.common/Runtime/NUI/NUIDrawer.cs.meta
Normal file
11
Packages/com.nwh.common/Runtime/NUI/NUIDrawer.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f9ca09d742ea9d469503cf7022161fa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
87
Packages/com.nwh.common/Runtime/NUI/NUIEditor.cs
Normal file
87
Packages/com.nwh.common/Runtime/NUI/NUIEditor.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
#region
|
||||
|
||||
using UnityEditor;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.NUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Base custom editor class for NWH NUI (NWH User Interface) system.
|
||||
/// Provides common infrastructure for drawing foldable inspector sections with documentation links.
|
||||
/// </summary>
|
||||
[CanEditMultipleObjects]
|
||||
public class NUIEditor : Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// NUIDrawer instance used to render inspector GUI.
|
||||
/// </summary>
|
||||
public NUIDrawer drawer = new();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Unity callback to draw inspector GUI. Delegates to OnInspectorNUI.
|
||||
/// </summary>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
OnInspectorNUI();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Draws custom NUI inspector. Override this method in derived classes to add custom GUI.
|
||||
/// Initializes drawer and renders collapsible header.
|
||||
/// </summary>
|
||||
/// <returns>True if header is expanded and GUI should continue, false if collapsed</returns>
|
||||
public virtual bool OnInspectorNUI()
|
||||
{
|
||||
if (drawer == null)
|
||||
{
|
||||
drawer = new NUIDrawer();
|
||||
}
|
||||
|
||||
drawer.documentationBaseURL = GetDocumentationBaseURL();
|
||||
|
||||
drawer.BeginEditor(serializedObject);
|
||||
if (!drawer.Header(serializedObject.targetObject.GetType().Name))
|
||||
{
|
||||
drawer.EndEditor();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets base URL for documentation links. Override in derived classes to specify package-specific docs.
|
||||
/// </summary>
|
||||
/// <returns>Base documentation URL</returns>
|
||||
public virtual string GetDocumentationBaseURL()
|
||||
{
|
||||
return "http://nwhvehiclephysics.com";
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Disables default Unity inspector margins for custom NUI layout control.
|
||||
/// </summary>
|
||||
/// <returns>Always false to disable default margins</returns>
|
||||
public override bool UseDefaultMargins()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
11
Packages/com.nwh.common/Runtime/NUI/NUIEditor.cs.meta
Normal file
11
Packages/com.nwh.common/Runtime/NUI/NUIEditor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 265c8a5e778724d40bdd0fa7f07534e7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
73
Packages/com.nwh.common/Runtime/NUI/NUIPropertyDrawer.cs
Normal file
73
Packages/com.nwh.common/Runtime/NUI/NUIPropertyDrawer.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
#region
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.NUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom NWH.NUI property drawer with links to documentation.
|
||||
/// </summary>
|
||||
public class NUIPropertyDrawer : PropertyDrawer
|
||||
{
|
||||
protected NUIDrawer drawer = new();
|
||||
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
return drawer.GetHeight(NUIDrawer.GenerateKey(property));
|
||||
}
|
||||
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
OnNUI(position, property, label);
|
||||
}
|
||||
|
||||
|
||||
public virtual string GetDocumentationBaseURL()
|
||||
{
|
||||
return "http://nwhvehiclephysics.com";
|
||||
}
|
||||
|
||||
|
||||
public virtual bool OnNUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
if (drawer == null)
|
||||
{
|
||||
drawer = new NUIDrawer();
|
||||
}
|
||||
|
||||
drawer.documentationBaseURL = GetDocumentationBaseURL();
|
||||
drawer.BeginProperty(position, property, label);
|
||||
|
||||
string name = property.FindPropertyRelative("name")?.stringValue;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
name = property.displayName;
|
||||
}
|
||||
|
||||
if (!drawer.Header(name))
|
||||
{
|
||||
drawer.EndProperty();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d441a0a5e57f9444d89059c8b692ec7e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
78
Packages/com.nwh.common/Runtime/NUI/NUISettings.cs
Normal file
78
Packages/com.nwh.common/Runtime/NUI/NUISettings.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.NUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Global configuration settings for NWH NUI (NWH User Interface) editor system.
|
||||
/// Defines layout constants and color scheme used across all NWH custom editors.
|
||||
/// </summary>
|
||||
public static class NUISettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Standard height for inspector fields in pixels.
|
||||
/// </summary>
|
||||
public const float fieldHeight = 23f;
|
||||
|
||||
/// <summary>
|
||||
/// Vertical spacing between inspector fields in pixels.
|
||||
/// </summary>
|
||||
public const float fieldSpacing = 3f;
|
||||
|
||||
/// <summary>
|
||||
/// Resources folder path for NUI assets.
|
||||
/// </summary>
|
||||
public const string resourcesPath = "NUI/";
|
||||
|
||||
/// <summary>
|
||||
/// Margin around text elements in pixels.
|
||||
/// </summary>
|
||||
public const float textMargin = 2f;
|
||||
|
||||
/// <summary>
|
||||
/// Header background color for ScriptableObject editors.
|
||||
/// </summary>
|
||||
public static Color scriptableObjectHeaderColor = new Color32(220, 122, 32, 255);
|
||||
|
||||
/// <summary>
|
||||
/// Header background color for MonoBehaviour editors.
|
||||
/// </summary>
|
||||
public static Color editorHeaderColor = new Color32(20, 125, 211, 255);
|
||||
|
||||
/// <summary>
|
||||
/// Header background color for property drawers.
|
||||
/// </summary>
|
||||
public static Color propertyHeaderColor = new Color32(78, 152, 213, 255);
|
||||
|
||||
/// <summary>
|
||||
/// UI tint color indicating disabled state.
|
||||
/// </summary>
|
||||
public static Color disabledColor = new(1f, 0.5f, 0.5f);
|
||||
|
||||
/// <summary>
|
||||
/// UI tint color indicating enabled state.
|
||||
/// </summary>
|
||||
public static Color enabledColor = new(0.5f, 1f, 0.5f);
|
||||
|
||||
/// <summary>
|
||||
/// Light blue accent color for UI elements.
|
||||
/// </summary>
|
||||
public static Color lightBlueColor = new Color32(70, 170, 220, 255);
|
||||
|
||||
/// <summary>
|
||||
/// Light grey color for secondary UI elements.
|
||||
/// </summary>
|
||||
public static Color lightGreyColor = new Color32(192, 192, 192, 255);
|
||||
}
|
||||
}
|
||||
3
Packages/com.nwh.common/Runtime/NUI/NUISettings.cs.meta
Normal file
3
Packages/com.nwh.common/Runtime/NUI/NUISettings.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4569c8453c17406d83a470d6e849d57f
|
||||
timeCreated: 1581892208
|
||||
145
Packages/com.nwh.common/Runtime/NUI/SerializedPropertyHelper.cs
Normal file
145
Packages/com.nwh.common/Runtime/NUI/SerializedPropertyHelper.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
#region
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.NUI
|
||||
{
|
||||
public static class SerializedPropertyHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the object the property represents.
|
||||
/// </summary>
|
||||
/// <param name="prop"> </param>
|
||||
/// <returns> </returns>
|
||||
public static object GetTargetObjectOfProperty(SerializedProperty prop)
|
||||
{
|
||||
if (prop == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string path = prop.propertyPath.Replace(".Array.data[", "[");
|
||||
object obj = prop.serializedObject.targetObject;
|
||||
string[] elements = path.Split('.');
|
||||
foreach (string element in elements)
|
||||
{
|
||||
if (element.Contains("["))
|
||||
{
|
||||
string elementName = element.Substring(0, element.IndexOf("["));
|
||||
int index = Convert.ToInt32(element.Substring(element.IndexOf("[")).Replace("[", "")
|
||||
.Replace("]", ""));
|
||||
obj = GetValue_Imp(obj, elementName, index);
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = GetValue_Imp(obj, element);
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object that the property is a member of
|
||||
/// </summary>
|
||||
/// <param name="prop"> </param>
|
||||
/// <returns> </returns>
|
||||
public static object GetTargetObjectWithProperty(SerializedProperty prop)
|
||||
{
|
||||
string path = prop.propertyPath.Replace(".Array.data[", "[");
|
||||
object obj = prop.serializedObject.targetObject;
|
||||
string[] elements = path.Split('.');
|
||||
foreach (string element in elements.Take(elements.Length - 1))
|
||||
{
|
||||
if (element.Contains("["))
|
||||
{
|
||||
string elementName = element.Substring(0, element.IndexOf("["));
|
||||
int index = Convert.ToInt32(element.Substring(element.IndexOf("[")).Replace("[", "")
|
||||
.Replace("]", ""));
|
||||
obj = GetValue_Imp(obj, elementName, index);
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = GetValue_Imp(obj, element);
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
private static object GetValue_Imp(object source, string name)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Type type = source.GetType();
|
||||
|
||||
while (type != null)
|
||||
{
|
||||
FieldInfo f = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
||||
if (f != null)
|
||||
{
|
||||
return f.GetValue(source);
|
||||
}
|
||||
|
||||
PropertyInfo p = type.GetProperty(name,
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance |
|
||||
BindingFlags.IgnoreCase);
|
||||
if (p != null)
|
||||
{
|
||||
return p.GetValue(source, null);
|
||||
}
|
||||
|
||||
type = type.BaseType;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private static object GetValue_Imp(object source, string name, int index)
|
||||
{
|
||||
IEnumerable enumerable = GetValue_Imp(source, name) as IEnumerable;
|
||||
if (enumerable == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
IEnumerator enm = enumerable.GetEnumerator();
|
||||
//while (index-- >= 0)
|
||||
// enm.MoveNext();
|
||||
//return enm.Current;
|
||||
|
||||
for (int i = 0; i <= index; i++)
|
||||
{
|
||||
if (!enm.MoveNext())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return enm.Current;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76156108e69e9ba4897a469d702f4d40
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
16
Packages/com.nwh.common/Runtime/NWH.Common.asmdef
Normal file
16
Packages/com.nwh.common/Runtime/NWH.Common.asmdef
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "NWH.Common",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:75469ad4d38634e559750d17036d5f7c"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
7
Packages/com.nwh.common/Runtime/NWH.Common.asmdef.meta
Normal file
7
Packages/com.nwh.common/Runtime/NWH.Common.asmdef.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c76e28da8ce572043b1fb2da95817e18
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Packages/com.nwh.common/Runtime/Utility.meta
Normal file
8
Packages/com.nwh.common/Runtime/Utility.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7451ff9348d187449ec4a6dc9f72de3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,106 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for AnimationCurve manipulation and processing.
|
||||
/// </summary>
|
||||
public static class AnimationCurveExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Smooths out a scripting-generated AnimationCurve by calculating appropriate tangents.
|
||||
/// Creates smooth transitions between keyframes.
|
||||
/// </summary>
|
||||
/// <param name="inCurve">The curve to smooth.</param>
|
||||
/// <returns>A new smoothed AnimationCurve.</returns>
|
||||
public static AnimationCurve MakeSmooth(this AnimationCurve inCurve)
|
||||
{
|
||||
AnimationCurve outCurve = new();
|
||||
|
||||
for (int i = 0; i < inCurve.keys.Length; i++)
|
||||
{
|
||||
float inTangent = 0;
|
||||
float outTangent = 0;
|
||||
bool intangentSet = false;
|
||||
bool outtangentSet = false;
|
||||
Vector2 point1;
|
||||
Vector2 point2;
|
||||
Vector2 deltapoint;
|
||||
Keyframe key = inCurve[i];
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
inTangent = 0;
|
||||
intangentSet = true;
|
||||
}
|
||||
|
||||
if (i == inCurve.keys.Length - 1)
|
||||
{
|
||||
outTangent = 0;
|
||||
outtangentSet = true;
|
||||
}
|
||||
|
||||
if (!intangentSet)
|
||||
{
|
||||
point1.x = inCurve.keys[i - 1].time;
|
||||
point1.y = inCurve.keys[i - 1].value;
|
||||
point2.x = inCurve.keys[i].time;
|
||||
point2.y = inCurve.keys[i].value;
|
||||
|
||||
deltapoint = point2 - point1;
|
||||
|
||||
inTangent = deltapoint.y / deltapoint.x;
|
||||
}
|
||||
|
||||
if (!outtangentSet)
|
||||
{
|
||||
point1.x = inCurve.keys[i].time;
|
||||
point1.y = inCurve.keys[i].value;
|
||||
point2.x = inCurve.keys[i + 1].time;
|
||||
point2.y = inCurve.keys[i + 1].value;
|
||||
|
||||
deltapoint = point2 - point1;
|
||||
|
||||
outTangent = deltapoint.y / deltapoint.x;
|
||||
}
|
||||
|
||||
key.inTangent = inTangent;
|
||||
key.outTangent = outTangent;
|
||||
outCurve.AddKey(key);
|
||||
}
|
||||
|
||||
return outCurve;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Samples an AnimationCurve at regular intervals and returns the values as an array.
|
||||
/// Useful for pre-calculating curve values for performance-critical code.
|
||||
/// </summary>
|
||||
/// <param name="self">The curve to sample.</param>
|
||||
/// <param name="resolution">Number of samples to take. Higher values provide more precision.</param>
|
||||
/// <returns>Array of sampled values from 0 to 1.</returns>
|
||||
public static float[] GenerateCurveArray(this AnimationCurve self, int resolution = 256)
|
||||
{
|
||||
float[] returnArray = new float[resolution];
|
||||
for (int j = 0; j < resolution; j++)
|
||||
{
|
||||
returnArray[j] = self.Evaluate(j / (float)resolution);
|
||||
}
|
||||
|
||||
return returnArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34b51e6bc0d3493cbab61632bb388338
|
||||
timeCreated: 1607279175
|
||||
54
Packages/com.nwh.common/Runtime/Utility/ArrayExtensions.cs
Normal file
54
Packages/com.nwh.common/Runtime/Utility/ArrayExtensions.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using System;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for array manipulation.
|
||||
/// </summary>
|
||||
public static class ArrayExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Efficiently fills an array by repeating a pattern of values.
|
||||
/// Uses doubling strategy for performance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of array elements.</typeparam>
|
||||
/// <param name="destinationArray">Array to fill.</param>
|
||||
/// <param name="value">Pattern of values to repeat throughout the array.</param>
|
||||
public static void Fill<T>(this T[] destinationArray, params T[] value)
|
||||
{
|
||||
int destinationLength = destinationArray.Length;
|
||||
if (destinationLength == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int valueLength = value.Length;
|
||||
|
||||
// set the initial array value
|
||||
Array.Copy(value, destinationArray, valueLength);
|
||||
|
||||
int arrayToFillHalfLength = destinationLength / 2;
|
||||
int copyLength;
|
||||
|
||||
for (copyLength = valueLength; copyLength < arrayToFillHalfLength; copyLength <<= 1)
|
||||
{
|
||||
Array.Copy(destinationArray, 0, destinationArray, copyLength, copyLength);
|
||||
}
|
||||
|
||||
Array.Copy(destinationArray, 0, destinationArray, copyLength,
|
||||
destinationLength - copyLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4845cbe074fb994d8b8e2fd57cf6009
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,86 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for GameObject and Transform operations.
|
||||
/// </summary>
|
||||
public static class GameObjectExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates the combined bounds of all MeshRenderers in a GameObject and its children.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">GameObject to calculate bounds for.</param>
|
||||
/// <returns>Combined bounds encapsulating all child renderers.</returns>
|
||||
public static Bounds FindBoundsIncludeChildren(this GameObject gameObject)
|
||||
{
|
||||
Bounds bounds = new();
|
||||
foreach (MeshRenderer mr in gameObject.GetComponentsInChildren<MeshRenderer>())
|
||||
{
|
||||
bounds.Encapsulate(mr.bounds);
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Searches for a component in parent GameObjects, with option to include inactive objects.
|
||||
/// More flexible than Unity's built-in GetComponentInParent.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of component to find.</typeparam>
|
||||
/// <param name="transform">Starting transform.</param>
|
||||
/// <param name="includeInactive">Include inactive GameObjects in search.</param>
|
||||
/// <returns>First component of type T found in parents, or null if none found.</returns>
|
||||
public static T GetComponentInParent<T>(this Transform transform, bool includeInactive = true)
|
||||
where T : Component
|
||||
{
|
||||
Transform here = transform;
|
||||
T result = null;
|
||||
while (here && !result)
|
||||
{
|
||||
if (includeInactive || here.gameObject.activeSelf)
|
||||
{
|
||||
result = here.GetComponent<T>();
|
||||
}
|
||||
|
||||
here = here.parent;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Searches for a component in parents first, then children if not found.
|
||||
/// Combines functionality of GetComponentInParent and GetComponentInChildren.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of component to find.</typeparam>
|
||||
/// <param name="transform">Starting transform.</param>
|
||||
/// <param name="includeInactive">Include inactive GameObjects in search.</param>
|
||||
/// <returns>First component of type T found, or null if none found.</returns>
|
||||
public static T GetComponentInParentsOrChildren<T>(this Transform transform, bool includeInactive = true)
|
||||
where T : Component
|
||||
{
|
||||
T result = transform.GetComponentInParent<T>(includeInactive);
|
||||
if (result == null)
|
||||
{
|
||||
result = transform.GetComponentInChildren<T>(includeInactive);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f2992a3b4066f44cb0e705d0b20db1a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
771
Packages/com.nwh.common/Runtime/Utility/GeomUtility.cs
Normal file
771
Packages/com.nwh.common/Runtime/Utility/GeomUtility.cs
Normal file
@@ -0,0 +1,771 @@
|
||||
// ╔════════════════════════════════════════════════════════════════╗
|
||||
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
|
||||
// ║ Licensed under Unity Asset Store Terms of Service: ║
|
||||
// ║ https://unity.com/legal/as-terms ║
|
||||
// ║ Use permitted only in compliance with the License. ║
|
||||
// ║ Distributed "AS IS", without warranty of any kind. ║
|
||||
// ╚════════════════════════════════════════════════════════════════╝
|
||||
|
||||
#region
|
||||
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace NWH.Common.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Collection of geometric utility functions for 3D math operations.
|
||||
/// Includes vector math, mesh calculations, triangle operations, and spatial queries.
|
||||
/// </summary>
|
||||
public static class GeomUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if two Vector3 values are approximately equal within a threshold.
|
||||
/// Uses squared magnitude for performance.
|
||||
/// </summary>
|
||||
/// <param name="a">First vector.</param>
|
||||
/// <param name="b">Second vector.</param>
|
||||
/// <param name="threshold">Maximum squared distance to consider equal.</param>
|
||||
/// <returns>True if vectors are within threshold distance.</returns>
|
||||
public static bool NearEqual(this Vector3 a, Vector3 b, float threshold = 0.01f)
|
||||
{
|
||||
return Vector3.SqrMagnitude(a - b) < threshold;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two Quaternion values are approximately equal.
|
||||
/// </summary>
|
||||
/// <param name="a">First quaternion.</param>
|
||||
/// <param name="b">Second quaternion.</param>
|
||||
/// <returns>True if angle between quaternions is less than 0.1 degrees.</returns>
|
||||
public static bool Equal(this Quaternion a, Quaternion b)
|
||||
{
|
||||
return Mathf.Abs(Quaternion.Angle(a, b)) < 0.1f;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Clamps the magnitude of a vector between minimum and maximum values.
|
||||
/// </summary>
|
||||
/// <param name="v">Vector to clamp.</param>
|
||||
/// <param name="min">Minimum magnitude.</param>
|
||||
/// <param name="max">Maximum magnitude.</param>
|
||||
/// <returns>Vector with clamped magnitude.</returns>
|
||||
public static Vector3 ClampMagnitude(this Vector3 v, float min, float max)
|
||||
{
|
||||
float mag = v.magnitude;
|
||||
if (mag == 0)
|
||||
{
|
||||
return Vector3.zero;
|
||||
}
|
||||
return Mathf.Clamp(mag, min, max) / mag * v;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates a perpendicular vector to the given vector.
|
||||
/// </summary>
|
||||
/// <param name="v">Input vector.</param>
|
||||
/// <returns>Perpendicular vector.</returns>
|
||||
public static Vector3 Perpendicular(this Vector3 v)
|
||||
{
|
||||
return new Vector3(CopySign(v.z, v.x), CopySign(v.z, v.y), -CopySign(Mathf.Abs(v.x) + Mathf.Abs(v.y), v.z));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Copies the sign from one float to the magnitude of another.
|
||||
/// </summary>
|
||||
/// <param name="mag">Magnitude value.</param>
|
||||
/// <param name="sgn">Sign donor value.</param>
|
||||
/// <returns>Magnitude with the sign of sgn.</returns>
|
||||
public static float CopySign(float mag, float sgn)
|
||||
{
|
||||
ref uint magI = ref UnsafeUtility.As<float, uint>(ref mag);
|
||||
ref uint sgnI = ref UnsafeUtility.As<float, uint>(ref sgn);
|
||||
uint result = (magI & ~(1u << 31)) | (sgnI & (1u << 31));
|
||||
return UnsafeUtility.As<uint, float>(ref result);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns a vector with only the largest component preserved (rounded to 1 or -1), others set to 0.
|
||||
/// </summary>
|
||||
/// <param name="v">Input vector.</param>
|
||||
/// <returns>Vector with dominant axis isolated.</returns>
|
||||
public static Vector3 RoundedMax(this Vector3 v)
|
||||
{
|
||||
int maxIndex = -1;
|
||||
float maxValue = -Mathf.Infinity;
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
float value = Mathf.Abs(v[i]);
|
||||
if (value > maxValue)
|
||||
{
|
||||
maxValue = value;
|
||||
maxIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
v[i] = i == maxIndex ? Mathf.Sign(v[i]) * 1f : 0f;
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Finds the nearest point on an infinite line to a given point.
|
||||
/// </summary>
|
||||
/// <param name="linePnt">Point on the line.</param>
|
||||
/// <param name="lineDir">Direction of the line.</param>
|
||||
/// <param name="pnt">Point to find nearest point from.</param>
|
||||
/// <returns>Nearest point on the line.</returns>
|
||||
public static Vector3 NearestPointOnLine(Vector3 linePnt, Vector3 lineDir, Vector3 pnt)
|
||||
{
|
||||
lineDir.Normalize(); //this needs to be a unit vector
|
||||
Vector3 v = pnt - linePnt;
|
||||
float d = Vector3.Dot(v, lineDir);
|
||||
return linePnt + lineDir * d;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the distance between a point and a line segment.
|
||||
/// </summary>
|
||||
/// <param name="pt">Point to measure from.</param>
|
||||
/// <param name="p1">First endpoint of segment.</param>
|
||||
/// <param name="p2">Second endpoint of segment.</param>
|
||||
/// <returns>Distance to the segment.</returns>
|
||||
public static float FindDistanceToSegment(Vector3 pt, Vector3 p1, Vector3 p2)
|
||||
{
|
||||
float dx = p2.x - p1.x;
|
||||
float dy = p2.y - p1.y;
|
||||
if (dx == 0 && dy == 0)
|
||||
{
|
||||
// It's a point not a line segment.
|
||||
dx = pt.x - p1.x;
|
||||
dy = pt.y - p1.y;
|
||||
return Mathf.Sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
// Calculate the t that minimizes the distance.
|
||||
float t = ((pt.x - p1.x) * dx + (pt.y - p1.y) * dy) /
|
||||
(dx * dx + dy * dy);
|
||||
|
||||
// See if this represents one of the segment's
|
||||
// end points or a point in the middle.
|
||||
if (t < 0)
|
||||
{
|
||||
dx = pt.x - p1.x;
|
||||
dy = pt.y - p1.y;
|
||||
}
|
||||
else if (t > 1)
|
||||
{
|
||||
dx = pt.x - p2.x;
|
||||
dy = pt.y - p2.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector3 closest = new(p1.x + t * dx, p1.y + t * dy);
|
||||
dx = pt.x - closest.x;
|
||||
dy = pt.y - closest.y;
|
||||
}
|
||||
|
||||
return Mathf.Sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates squared distance between two points. Faster than regular distance.
|
||||
/// </summary>
|
||||
/// <param name="a">First point.</param>
|
||||
/// <param name="b">Second point.</param>
|
||||
/// <returns>Squared distance.</returns>
|
||||
public static float SquareDistance(Vector3 a, Vector3 b)
|
||||
{
|
||||
float x = a.x - b.x;
|
||||
float y = a.y - b.y;
|
||||
float z = a.z - b.z;
|
||||
return x * x + y * y + z * z;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Finds the intersection point between a line and a plane.
|
||||
/// </summary>
|
||||
/// <param name="planePoint">Point on the plane.</param>
|
||||
/// <param name="planeNormal">Normal vector of the plane.</param>
|
||||
/// <param name="linePoint">Point on the line.</param>
|
||||
/// <param name="lineDirection">Direction of the line.</param>
|
||||
/// <returns>Intersection point, or Vector3.zero if parallel.</returns>
|
||||
public static Vector3 LinePlaneIntersection(Vector3 planePoint, Vector3 planeNormal, Vector3 linePoint,
|
||||
Vector3 lineDirection)
|
||||
{
|
||||
if (Vector3.Dot(planeNormal, lineDirection.normalized) == 0)
|
||||
{
|
||||
return Vector3.zero;
|
||||
}
|
||||
|
||||
float t = (Vector3.Dot(planeNormal, planePoint) - Vector3.Dot(planeNormal, linePoint)) /
|
||||
Vector3.Dot(planeNormal, lineDirection.normalized);
|
||||
return linePoint + lineDirection.normalized * t;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Finds a point along the chord line of a quad at the specified percentage.
|
||||
/// </summary>
|
||||
/// <param name="a">First corner.</param>
|
||||
/// <param name="b">Second corner.</param>
|
||||
/// <param name="c">Third corner.</param>
|
||||
/// <param name="d">Fourth corner.</param>
|
||||
/// <param name="chordPercent">Position along chord (0-1).</param>
|
||||
/// <returns>Point on chord line.</returns>
|
||||
public static Vector3 FindChordLine(Vector3 a, Vector3 b, Vector3 c, Vector3 d, float chordPercent)
|
||||
{
|
||||
return QuadLerp(a, b, c, d, 0.5f, chordPercent);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Finds a point along the span line of a quad at the specified percentage.
|
||||
/// </summary>
|
||||
/// <param name="a">First corner.</param>
|
||||
/// <param name="b">Second corner.</param>
|
||||
/// <param name="c">Third corner.</param>
|
||||
/// <param name="d">Fourth corner.</param>
|
||||
/// <param name="spanPercent">Position along span (0-1).</param>
|
||||
/// <returns>Point on span line.</returns>
|
||||
public static Vector3 FindSpanLine(Vector3 a, Vector3 b, Vector3 c, Vector3 d, float spanPercent)
|
||||
{
|
||||
return QuadLerp(a, b, c, d, spanPercent, 0.5f);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the area of a quadrilateral defined by four points.
|
||||
/// </summary>
|
||||
/// <param name="A">First corner.</param>
|
||||
/// <param name="B">Second corner.</param>
|
||||
/// <param name="C">Third corner.</param>
|
||||
/// <param name="D">Fourth corner.</param>
|
||||
/// <returns>Area of the quad.</returns>
|
||||
public static float FindArea(Vector3 A, Vector3 B, Vector3 C, Vector3 D)
|
||||
{
|
||||
return TriArea(A, B, D) + TriArea(B, C, D);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Finds the center point of a quad or triangle defined by 3 or 4 points.
|
||||
/// </summary>
|
||||
/// <param name="a">First corner.</param>
|
||||
/// <param name="b">Second corner.</param>
|
||||
/// <param name="c">Third corner.</param>
|
||||
/// <param name="d">Fourth corner (can equal first corner for triangle).</param>
|
||||
/// <returns>Center point.</returns>
|
||||
public static Vector3 FindCenter(Vector3 a, Vector3 b, Vector3 c, Vector3 d)
|
||||
{
|
||||
if (a == d)
|
||||
{
|
||||
return (a + b + c) / 4f;
|
||||
}
|
||||
|
||||
return (a + b + c + d) / 4f;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the distance between two points projected along a normal vector.
|
||||
/// </summary>
|
||||
/// <param name="a">First point.</param>
|
||||
/// <param name="b">Second point.</param>
|
||||
/// <param name="normal">Normal vector to project along.</param>
|
||||
/// <returns>Distance along normal.</returns>
|
||||
public static float DistanceAlongNormal(Vector3 a, Vector3 b, Vector3 normal)
|
||||
{
|
||||
Vector3 dir = b - a;
|
||||
return Vector3.Project(dir, normal).magnitude;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a point lies inside a triangle.
|
||||
/// </summary>
|
||||
/// <param name="A">First triangle vertex.</param>
|
||||
/// <param name="B">Second triangle vertex.</param>
|
||||
/// <param name="C">Third triangle vertex.</param>
|
||||
/// <param name="P">Point to test.</param>
|
||||
/// <param name="dotThreshold">Tolerance for point-on-plane test.</param>
|
||||
/// <returns>True if point is inside triangle.</returns>
|
||||
public static bool PointInTriangle(Vector3 A, Vector3 B, Vector3 C, Vector3 P, float dotThreshold = 0.001f)
|
||||
{
|
||||
if (SameSide(P, A, B, C) && SameSide(P, B, A, C) && SameSide(P, C, A, B))
|
||||
{
|
||||
Vector3 vc1 = Vector3.Cross(B - A, C - A).normalized;
|
||||
if (Mathf.Abs(Vector3.Dot(P - A, vc1)) <= dotThreshold)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private static bool SameSide(Vector3 p1, Vector3 p2, Vector3 A, Vector3 B)
|
||||
{
|
||||
Vector3 cp1 = Vector3.Cross(B - A, p1 - A).normalized;
|
||||
Vector3 cp2 = Vector3.Cross(B - A, p2 - A).normalized;
|
||||
if (Vector3.Dot(cp1, cp2) > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a 2D point is inside the screen rectangle.
|
||||
/// </summary>
|
||||
/// <param name="point">Point to check.</param>
|
||||
/// <returns>True if point is inside screen bounds.</returns>
|
||||
public static bool PointIsInsideRect(Vector2 point)
|
||||
{
|
||||
return new Rect(0, 0, Screen.width, Screen.height).Contains(point);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two float values are nearly equal within an epsilon threshold.
|
||||
/// </summary>
|
||||
/// <param name="a">First value.</param>
|
||||
/// <param name="b">Second value.</param>
|
||||
/// <param name="epsilon">Maximum difference to consider equal.</param>
|
||||
/// <returns>True if values are within epsilon.</returns>
|
||||
public static bool NearlyEqual(this float a, float b, double epsilon)
|
||||
{
|
||||
return Mathf.Abs(a - b) < epsilon;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the area of a triangle from three points.
|
||||
/// </summary>
|
||||
/// <param name="p1">First point.</param>
|
||||
/// <param name="p2">Second point.</param>
|
||||
/// <param name="p3">Third point.</param>
|
||||
/// <returns>Area of the triangle.</returns>
|
||||
public static float AreaFromThreePoints(Vector3 p1, Vector3 p2, Vector3 p3)
|
||||
{
|
||||
Vector3 u, v;
|
||||
|
||||
u.x = p2.x - p1.x;
|
||||
u.y = p2.y - p1.y;
|
||||
u.z = p2.z - p1.z;
|
||||
|
||||
v.x = p3.x - p1.x;
|
||||
v.y = p3.y - p1.y;
|
||||
v.z = p3.z - p1.z;
|
||||
|
||||
Vector3 crossUV = Vector3.Cross(u, v);
|
||||
return Mathf.Sqrt(crossUV.x * crossUV.x + crossUV.y * crossUV.y + crossUV.z * crossUV.z) * 0.5f;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the area of a quadrilateral from four points.
|
||||
/// </summary>
|
||||
/// <param name="p1">First point.</param>
|
||||
/// <param name="p2">Second point.</param>
|
||||
/// <param name="p3">Third point.</param>
|
||||
/// <param name="p4">Fourth point.</param>
|
||||
/// <returns>Area of the quadrilateral.</returns>
|
||||
public static float AreaFromFourPoints(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4)
|
||||
{
|
||||
return AreaFromThreePoints(p1, p2, p4) + AreaFromThreePoints(p2, p3, p4);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates area of a single triangle from it's three points.
|
||||
/// </summary>
|
||||
public static float TriArea(Vector3 p1, Vector3 p2, Vector3 p3)
|
||||
{
|
||||
Vector3 u, v, crossUV;
|
||||
|
||||
u.x = p2.x - p1.x;
|
||||
u.y = p2.y - p1.y;
|
||||
u.z = p2.z - p1.z;
|
||||
|
||||
v.x = p3.x - p1.x;
|
||||
v.y = p3.y - p1.y;
|
||||
v.z = p3.z - p1.z;
|
||||
|
||||
crossUV = Vector3.Cross(u, v);
|
||||
return Mathf.Sqrt(crossUV.x * crossUV.x + crossUV.y * crossUV.y + crossUV.z * crossUV.z) * 0.5f;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates area of a complete mesh.
|
||||
/// </summary>
|
||||
public static float MeshArea(Mesh mesh)
|
||||
{
|
||||
if (mesh.vertices.Length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
float area = 0;
|
||||
|
||||
Vector3[] verts = mesh.vertices;
|
||||
int[] tris = mesh.triangles;
|
||||
|
||||
for (int i = 0; i < tris.Length; i += 3)
|
||||
{
|
||||
area += TriArea(verts[tris[i]], verts[tris[i + 1]], verts[tris[i + 2]]);
|
||||
}
|
||||
|
||||
return area;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates area of a mesh as viewed from the direction vector.
|
||||
/// </summary>
|
||||
public static float ProjectedMeshArea(Mesh mesh, Vector3 direction)
|
||||
{
|
||||
float area = 0;
|
||||
|
||||
Vector3[] verts = mesh.vertices;
|
||||
int[] tris = mesh.triangles;
|
||||
Vector3[] normals = mesh.normals;
|
||||
|
||||
int count = 0;
|
||||
for (int i = 0; i < tris.Length; i += 3)
|
||||
{
|
||||
area += TriArea(verts[tris[i]], verts[tris[i + 1]], verts[tris[i + 2]], direction);
|
||||
count++;
|
||||
}
|
||||
|
||||
return area;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the area of a rectangle from four corner points.
|
||||
/// </summary>
|
||||
/// <param name="p1">First corner.</param>
|
||||
/// <param name="p2">Second corner.</param>
|
||||
/// <param name="p3">Third corner.</param>
|
||||
/// <param name="p4">Fourth corner.</param>
|
||||
/// <returns>Area of the rectangle.</returns>
|
||||
public static float RectArea(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4)
|
||||
{
|
||||
return TriArea(p1, p2, p4) + TriArea(p2, p3, p4);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Find mesh center by averaging. Returns local center.
|
||||
/// </summary>
|
||||
public static Vector3 FindMeshCenter(Mesh mesh)
|
||||
{
|
||||
if (mesh.vertices.Length == 0)
|
||||
{
|
||||
return Vector3.zero;
|
||||
}
|
||||
|
||||
Vector3 sum = Vector3.zero;
|
||||
int count = 0;
|
||||
if (mesh != null)
|
||||
{
|
||||
foreach (Vector3 vert in mesh.vertices)
|
||||
{
|
||||
sum += vert;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return sum / count;
|
||||
}
|
||||
|
||||
|
||||
public static float TriArea(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 view)
|
||||
{
|
||||
Vector3 u, v, crossUV, normal;
|
||||
float crossMagnitude;
|
||||
|
||||
u.x = p2.x - p1.x;
|
||||
u.y = p2.y - p1.y;
|
||||
u.z = p2.z - p1.z;
|
||||
|
||||
v.x = p3.x - p1.x;
|
||||
v.y = p3.y - p1.y;
|
||||
v.z = p3.z - p1.z;
|
||||
|
||||
crossUV = Vector3.Cross(u, v);
|
||||
crossMagnitude = Mathf.Sqrt(crossUV.x * crossUV.x + crossUV.y * crossUV.y + crossUV.z * crossUV.z);
|
||||
|
||||
// Normal
|
||||
if (crossMagnitude == 0)
|
||||
{
|
||||
normal.x = normal.y = normal.z = 0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
normal.x = crossUV.x / crossMagnitude;
|
||||
normal.y = crossUV.y / crossMagnitude;
|
||||
normal.z = crossUV.z / crossMagnitude;
|
||||
}
|
||||
|
||||
float angle = Vector3.Angle(normal, view);
|
||||
float cos = Mathf.Cos(angle);
|
||||
|
||||
if (cos < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Mathf.Sqrt(crossUV.x * crossUV.x + crossUV.y * crossUV.y + crossUV.z * crossUV.z) * 0.5f * cos;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the signed volume contribution of a triangle relative to the origin.
|
||||
/// </summary>
|
||||
/// <param name="p1">First vertex.</param>
|
||||
/// <param name="p2">Second vertex.</param>
|
||||
/// <param name="p3">Third vertex.</param>
|
||||
/// <returns>Signed volume.</returns>
|
||||
public static float SignedVolumeOfTriangle(Vector3 p1, Vector3 p2, Vector3 p3)
|
||||
{
|
||||
float v321 = p3.x * p2.y * p1.z;
|
||||
float v231 = p2.x * p3.y * p1.z;
|
||||
float v312 = p3.x * p1.y * p2.z;
|
||||
float v132 = p1.x * p3.y * p2.z;
|
||||
float v213 = p2.x * p1.y * p3.z;
|
||||
float v123 = p1.x * p2.y * p3.z;
|
||||
return 1.0f / 6.0f * (-v321 + v231 + v312 - v132 - v213 + v123);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the volume enclosed by a mesh.
|
||||
/// </summary>
|
||||
/// <param name="mesh">Mesh to calculate volume for.</param>
|
||||
/// <returns>Volume of the mesh.</returns>
|
||||
public static float VolumeOfMesh(Mesh mesh)
|
||||
{
|
||||
float volume = 0;
|
||||
Vector3[] vertices = mesh.vertices;
|
||||
int[] triangles = mesh.triangles;
|
||||
for (int i = 0; i < mesh.triangles.Length; i += 3)
|
||||
{
|
||||
Vector3 p1 = vertices[triangles[i + 0]];
|
||||
Vector3 p2 = vertices[triangles[i + 1]];
|
||||
Vector3 p3 = vertices[triangles[i + 2]];
|
||||
volume += SignedVolumeOfTriangle(p1, p2, p3);
|
||||
}
|
||||
|
||||
return Mathf.Abs(volume);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Transforms a point from local to world space without applying scale.
|
||||
/// </summary>
|
||||
/// <param name="transform">Transform to use.</param>
|
||||
/// <param name="position">Local position.</param>
|
||||
/// <returns>World position without scale.</returns>
|
||||
public static Vector3 TransformPointUnscaled(this Transform transform, Vector3 position)
|
||||
{
|
||||
return Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one).MultiplyPoint3x4(position);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Transforms a point from world to local space without applying scale.
|
||||
/// </summary>
|
||||
/// <param name="transform">Transform to use.</param>
|
||||
/// <param name="position">World position.</param>
|
||||
/// <returns>Local position without scale.</returns>
|
||||
public static Vector3 InverseTransformPointUnscaled(this Transform transform, Vector3 position)
|
||||
{
|
||||
return Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one).inverse
|
||||
.MultiplyPoint3x4(position);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Changes the layer of a transform and all its children recursively.
|
||||
/// </summary>
|
||||
/// <param name="trans">Root transform.</param>
|
||||
/// <param name="name">Layer name.</param>
|
||||
public static void ChangeLayersRecursively(this Transform trans, string name)
|
||||
{
|
||||
trans.gameObject.layer = LayerMask.NameToLayer(name);
|
||||
foreach (Transform child in trans)
|
||||
{
|
||||
child.ChangeLayersRecursively(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Changes the color of a GameObject's material.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">GameObject to modify.</param>
|
||||
/// <param name="color">New color.</param>
|
||||
public static void ChangeObjectColor(GameObject gameObject, Color color)
|
||||
{
|
||||
gameObject.GetComponent<MeshRenderer>().material.SetColor("_Color", color);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Changes the alpha value of a GameObject's material color.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">GameObject to modify.</param>
|
||||
/// <param name="alpha">New alpha value (0-1).</param>
|
||||
public static void ChangeObjectAlpha(GameObject gameObject, float alpha)
|
||||
{
|
||||
MeshRenderer mr = gameObject.GetComponent<MeshRenderer>();
|
||||
Color currentColor = mr.material.GetColor("_Color");
|
||||
currentColor.a = alpha;
|
||||
mr.material.SetColor("_Color", currentColor);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns a vector with absolute values of all components.
|
||||
/// </summary>
|
||||
/// <param name="v">Input vector.</param>
|
||||
/// <returns>Vector with absolute values.</returns>
|
||||
public static Vector3 Vector3Abs(Vector3 v)
|
||||
{
|
||||
return new Vector3(Mathf.Abs(v.x), Mathf.Abs(v.y), Mathf.Abs(v.z));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Rounds all components of a vector to nearest integer.
|
||||
/// </summary>
|
||||
/// <param name="v">Input vector.</param>
|
||||
/// <returns>Rounded vector.</returns>
|
||||
public static Vector3 Vector3RoundToInt(Vector3 v)
|
||||
{
|
||||
return new Vector3(Mathf.RoundToInt(v.x), Mathf.RoundToInt(v.y), Mathf.RoundToInt(v.z));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns a vector with reciprocal values (1/x, 1/y, 1/z).
|
||||
/// </summary>
|
||||
/// <param name="v">Input vector.</param>
|
||||
/// <returns>Vector with reciprocal values.</returns>
|
||||
public static Vector3 Vector3OneOver(Vector3 v)
|
||||
{
|
||||
return new Vector3(1f / v.x, 1f / v.y, 1f / v.z);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Rounds a value to the nearest multiple of step.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to round.</param>
|
||||
/// <param name="step">Step size.</param>
|
||||
/// <returns>Rounded value.</returns>
|
||||
public static float RoundToStep(float value, float step)
|
||||
{
|
||||
return Mathf.Round(value / step) * step;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Rounds a value to the nearest multiple of step.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to round.</param>
|
||||
/// <param name="step">Step size.</param>
|
||||
/// <returns>Rounded value.</returns>
|
||||
public static float RoundToStep(int value, int step)
|
||||
{
|
||||
return Mathf.RoundToInt(Mathf.Round(value / step) * step);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Rotates a point around a pivot by the specified angles.
|
||||
/// </summary>
|
||||
/// <param name="point">Point to rotate.</param>
|
||||
/// <param name="pivot">Pivot point.</param>
|
||||
/// <param name="angles">Euler angles for rotation.</param>
|
||||
/// <returns>Rotated point.</returns>
|
||||
public static Vector3 RotatePointAroundPivot(Vector3 point, Vector3 pivot, Vector3 angles)
|
||||
{
|
||||
return Quaternion.Euler(angles) * (point - pivot) + pivot;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Performs bilinear interpolation on a quad defined by four points.
|
||||
/// </summary>
|
||||
/// <param name="a">First corner.</param>
|
||||
/// <param name="b">Second corner.</param>
|
||||
/// <param name="c">Third corner.</param>
|
||||
/// <param name="d">Fourth corner.</param>
|
||||
/// <param name="u">U parameter (0-1).</param>
|
||||
/// <param name="v">V parameter (0-1).</param>
|
||||
/// <returns>Interpolated point.</returns>
|
||||
public static Vector3 QuadLerp(Vector3 a, Vector3 b, Vector3 c, Vector3 d, float u, float v)
|
||||
{
|
||||
Vector3 abu = Vector3Lerp(a, b, u);
|
||||
Vector3 dcu = Vector3Lerp(d, c, u);
|
||||
return Vector3Lerp(abu, dcu, v);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Linear interpolation between two vectors with value clamping.
|
||||
/// </summary>
|
||||
/// <param name="v1">Start vector.</param>
|
||||
/// <param name="v2">End vector.</param>
|
||||
/// <param name="value">Interpolation value (0-1).</param>
|
||||
/// <returns>Interpolated vector.</returns>
|
||||
public static Vector3 Vector3Lerp(Vector3 v1, Vector3 v2, float value)
|
||||
{
|
||||
if (value > 1.0f)
|
||||
{
|
||||
return v2;
|
||||
}
|
||||
|
||||
if (value < 0.0f)
|
||||
{
|
||||
return v1;
|
||||
}
|
||||
|
||||
return new Vector3(v1.x + (v2.x - v1.x) * value,
|
||||
v1.y + (v2.y - v1.y) * value,
|
||||
v1.z + (v2.z - v1.z) * value);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the magnitude of a quaternion.
|
||||
/// </summary>
|
||||
/// <param name="q">Quaternion.</param>
|
||||
/// <returns>Magnitude.</returns>
|
||||
public static float QuaternionMagnitude(Quaternion q)
|
||||
{
|
||||
return Mathf.Sqrt(q.w * q.w + q.x * q.x + q.y * q.y + q.z * q.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user