增加skill、加入界面基础加载
This commit is contained in:
1 parent
839165ea63
commit
5921a4884e
128 files changed
+17935
-5023
No files matched your search
@@ -1,97 +0,0 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 标准的“点击地面移动角色”组件。
|
||||
///
|
||||
/// 用法:把本脚本挂到一个带 CharacterController 的对象上(本项目中是一个长方体 Cube)。
|
||||
/// 运行时**按住**鼠标左键(拖拽),角色会持续朝鼠标当前指向的地面点移动并平滑转向,
|
||||
/// 松开即停止;**按下瞬间**会在点击位置生成一个粒子预制体(可选),2 秒后自动销毁。
|
||||
/// 角色仅在地表高度移动(保持 y 不变)。
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(CharacterController))]
|
||||
public class ClickToMove : MonoBehaviour
|
||||
{
|
||||
[Tooltip("角色移动速度(单位/秒)")]
|
||||
[SerializeField] private float speed = 3f;
|
||||
|
||||
[Tooltip("点击地面时在点击位置动态生成的粒子预制体(需带 ParticleSystem 组件,实例化后 2 秒自动销毁)")]
|
||||
[SerializeField] private GameObject clickEffect;
|
||||
|
||||
private CharacterController _controller;
|
||||
private Vector3 _target;
|
||||
private bool _moving;
|
||||
|
||||
// “Ground” 层索引,与 ProjectSettings/TagManager.asset 中的配置一致(第 6 层)。
|
||||
private int _groundLayer = 6;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_controller = GetComponent<CharacterController>();
|
||||
_target = transform.position;
|
||||
|
||||
// 优先使用项目里命名的 “Ground” 层;若编辑器尚未刷新层名则回退到默认索引 6。
|
||||
int named = LayerMask.NameToLayer("Ground");
|
||||
if (named >= 0)
|
||||
_groundLayer = named;
|
||||
else
|
||||
Debug.LogWarning("[ClickToMove] 未找到 \"Ground\" 层,回退使用默认层索引 6。");
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
bool clicked = Input.GetMouseButtonDown(0);
|
||||
bool holding = Input.GetMouseButton(0);
|
||||
|
||||
// 按下瞬间 或 按住拖拽时,都朝当前鼠标指向的地面点更新目标;
|
||||
// 仅在“按下瞬间”生成一次点击粒子,避免按住过程中每帧重复生成。
|
||||
if (clicked || holding)
|
||||
{
|
||||
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
|
||||
if (Physics.Raycast(ray, out RaycastHit hit, 100f, 1 << _groundLayer))
|
||||
{
|
||||
_target = hit.point;
|
||||
_moving = true;
|
||||
if (clicked)
|
||||
SpawnClickEffect(hit.point);
|
||||
}
|
||||
}
|
||||
|
||||
// 朝目标点移动并转向
|
||||
if (!_moving) return;
|
||||
|
||||
Vector3 dir = _target - transform.position;
|
||||
dir.y = 0f; // 只在地表水平方向移动
|
||||
|
||||
if (dir.sqrMagnitude > 0.05f)
|
||||
{
|
||||
_controller.Move(dir.normalized * (speed * Time.deltaTime));
|
||||
|
||||
Vector3 facing = new Vector3(dir.x, 0f, dir.z);
|
||||
if (facing.sqrMagnitude > 0.001f)
|
||||
transform.rotation = Quaternion.Slerp(
|
||||
transform.rotation,
|
||||
Quaternion.LookRotation(facing),
|
||||
Time.deltaTime * 12f);
|
||||
}
|
||||
else
|
||||
{
|
||||
_moving = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>在点击位置动态生成粒子预制体,2 秒后自动销毁。</summary>
|
||||
private void SpawnClickEffect(Vector3 position)
|
||||
{
|
||||
if (clickEffect == null) return;
|
||||
|
||||
// 在点击位置实例化预制体
|
||||
GameObject effect = Instantiate(clickEffect, position, Quaternion.identity);
|
||||
|
||||
// 确保粒子播放(若预制体 playOnAwake 未勾选也能看到)
|
||||
ParticleSystem ps = effect.GetComponent<ParticleSystem>();
|
||||
if (ps != null) ps.Play();
|
||||
|
||||
// 延迟 2 秒后销毁
|
||||
Destroy(effect, 2f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5f2922bf442c4adb95912d403541a75
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,28 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 开机运行时设置。做成预制体放进场景,进游戏时自动应用。
|
||||
/// 参数在 Inspector / 预制体上直接改,不用动代码。
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class RuntimeSettings : MonoBehaviour
|
||||
{
|
||||
[Header("帧率")]
|
||||
[Tooltip("目标帧率:-1 = 不限制(Unity 默认),60 = 常规,120 = 高刷屏 ProMotion")]
|
||||
[SerializeField] private int targetFrameRate = 60;
|
||||
|
||||
[Tooltip("垂直同步。iOS 上无效,桌面平台有效;非 0 时会覆盖上面的目标帧率")]
|
||||
[SerializeField] private int vSyncCount = 0;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
Apply();
|
||||
}
|
||||
|
||||
/// <summary>立刻把当前参数应用到运行环境。</summary>
|
||||
public void Apply()
|
||||
{
|
||||
QualitySettings.vSyncCount = vSyncCount;
|
||||
Application.targetFrameRate = targetFrameRate;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
|
||||
guid: ec4449e5442054472af2a1b06c676efc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9763bfb9a651414d9d5aa77b79ea74a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,107 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 用 UI 虚拟摇杆(移动轮盘)驱动角色移动,替代原来的点击地面移动。
|
||||
///
|
||||
/// 用法:挂在带 CharacterController 的角色上,把场景里的 <see cref="VirtualJoystick"/>
|
||||
/// 拖到 joystick 字段(留空会在运行时自动查找)。
|
||||
/// 摇杆方向默认按摄像机朝向换算成世界方向,推杆越满速度越快;只做水平移动,y 不变。
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(CharacterController))]
|
||||
public class PlayerMoveController : MonoBehaviour
|
||||
{
|
||||
[Header("输入")]
|
||||
[Tooltip("移动轮盘;留空则在运行时自动查找场景中的 VirtualJoystick")]
|
||||
[SerializeField] private VirtualJoystick joystick;
|
||||
|
||||
[Tooltip("是否按摄像机朝向移动(关掉则按世界坐标轴移动)")]
|
||||
[SerializeField] private bool cameraRelative = true;
|
||||
|
||||
[Tooltip("朝向参考的摄像机,留空用 Camera.main")]
|
||||
[SerializeField] private Transform cameraTransform;
|
||||
|
||||
[Header("移动")]
|
||||
[Tooltip("满杆时的移动速度(单位/秒)")]
|
||||
[SerializeField] private float speed = 5f;
|
||||
|
||||
[Tooltip("转向平滑速度,越大转得越快")]
|
||||
[SerializeField] private float turnSpeed = 12f;
|
||||
|
||||
[Tooltip("输入平滑时间(0 = 立刻响应)")]
|
||||
[SerializeField] private float inputSmoothing = 0.05f;
|
||||
|
||||
private CharacterController _controller;
|
||||
private Vector2 _smoothedInput;
|
||||
private Vector2 _smoothingVelocity;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_controller = GetComponent<CharacterController>();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// 场景里只有一个轮盘时自动接管,省得手动拖引用
|
||||
if (joystick == null)
|
||||
joystick = FindObjectOfType<VirtualJoystick>(true);
|
||||
|
||||
if (joystick == null)
|
||||
Debug.LogWarning("[PlayerMoveController] 场景里没找到 VirtualJoystick(移动轮盘),角色不会移动。");
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
Vector2 input = joystick != null ? joystick.Value : Vector2.zero;
|
||||
|
||||
if (inputSmoothing > 0f)
|
||||
input = Vector2.SmoothDamp(_smoothedInput, input, ref _smoothingVelocity, inputSmoothing);
|
||||
_smoothedInput = input;
|
||||
|
||||
if (input.sqrMagnitude <= 0.0001f)
|
||||
return;
|
||||
|
||||
Vector3 direction = ToWorldDirection(input);
|
||||
if (direction.sqrMagnitude <= 0.0001f)
|
||||
return;
|
||||
|
||||
_controller.Move(direction * (speed * input.magnitude * Time.deltaTime));
|
||||
|
||||
transform.rotation = Quaternion.Slerp(
|
||||
transform.rotation,
|
||||
Quaternion.LookRotation(direction),
|
||||
Time.deltaTime * turnSpeed);
|
||||
}
|
||||
|
||||
/// <summary>把摇杆的二维输入换算成世界方向(水平面内,已归一化)。</summary>
|
||||
private Vector3 ToWorldDirection(Vector2 input)
|
||||
{
|
||||
Vector3 forward = Vector3.forward;
|
||||
Vector3 right = Vector3.right;
|
||||
|
||||
if (cameraRelative)
|
||||
{
|
||||
Transform cam = cameraTransform != null
|
||||
? cameraTransform
|
||||
: (Camera.main != null ? Camera.main.transform : null);
|
||||
|
||||
if (cam != null)
|
||||
{
|
||||
forward = cam.forward;
|
||||
forward.y = 0f;
|
||||
right = cam.right;
|
||||
right.y = 0f;
|
||||
|
||||
if (forward.sqrMagnitude > 0.0001f) forward.Normalize();
|
||||
else forward = Vector3.forward;
|
||||
|
||||
if (right.sqrMagnitude > 0.0001f) right.Normalize();
|
||||
else right = Vector3.right;
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 dir = right * input.x + forward * input.y;
|
||||
if (dir.sqrMagnitude > 1f)
|
||||
dir.Normalize();
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 331899a01a8f041c88567596518061b4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe3dfc27f716b41f2b9a78dfe5a88a52
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,45 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 把实时帧率写进 target 指定的文本,用来在真机上直接看性能。
|
||||
/// 节点引用一律在 Inspector / 预制体里指定,代码里不按名字查找节点。
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class FpsDisplay : MonoBehaviour
|
||||
{
|
||||
[Tooltip("显示帧率的文本,必须在 Inspector 里指定")]
|
||||
[SerializeField] private TMP_Text target;
|
||||
|
||||
[Tooltip("刷新间隔(秒)。0.25 = 每秒刷 4 次,数字不会跳得太厉害")]
|
||||
[SerializeField] private float updateInterval = 0.25f;
|
||||
|
||||
[Tooltip("文本格式,{0} 是帧率数值。例:FPS:{0:0}f/s")]
|
||||
[SerializeField] private string format = "FPS:{0:0}f/s";
|
||||
|
||||
private float _accumulatedTime;
|
||||
private int _frames;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (target == null)
|
||||
Debug.LogError("[FpsDisplay] 没有指定显示帧率的文本(target),请在 Inspector 里拖入。", this);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
_accumulatedTime += Time.unscaledDeltaTime;
|
||||
_frames++;
|
||||
|
||||
if (_accumulatedTime < updateInterval)
|
||||
return;
|
||||
|
||||
target.text = string.Format(format, _frames / _accumulatedTime);
|
||||
|
||||
_accumulatedTime = 0f;
|
||||
_frames = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5cd887902ce124147b00d8e0173c77f1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,54 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
/// <summary>
|
||||
/// Main 启动页。
|
||||
/// 点击事件在 Button 的 On Click() 里接线(Inspector 配置),代码里不查找节点。
|
||||
/// </summary>
|
||||
public class MainMenu : MonoBehaviour
|
||||
{
|
||||
[Tooltip("点「启动」后要加载的场景名,必须已经加进 Build Settings")]
|
||||
[SerializeField] private string startSceneName = "Start";
|
||||
|
||||
private bool _loading;
|
||||
|
||||
/// <summary>启动:加载目标场景,加载完成后卸载 Main 场景自己。</summary>
|
||||
public void OnStartClicked()
|
||||
{
|
||||
if (_loading) return; // 防连点,避免同时加载出两份
|
||||
_loading = true;
|
||||
StartCoroutine(LoadThenUnloadSelf());
|
||||
}
|
||||
|
||||
/// <summary>退出游戏。</summary>
|
||||
public void OnExitClicked()
|
||||
{
|
||||
Application.Quit();
|
||||
#if UNITY_EDITOR
|
||||
// 编辑器里 Application.Quit 不生效,方便本地测试
|
||||
UnityEditor.EditorApplication.isPlaying = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
private IEnumerator LoadThenUnloadSelf()
|
||||
{
|
||||
Scene mainScene = gameObject.scene;
|
||||
|
||||
AsyncOperation load = SceneManager.LoadSceneAsync(startSceneName, LoadSceneMode.Additive);
|
||||
if (load == null)
|
||||
{
|
||||
Debug.LogError("[MainMenu] 场景 \"" + startSceneName + "\" 加载失败,确认它已加入 Build Settings。", this);
|
||||
_loading = false;
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return load; // 等目标场景加载完成
|
||||
|
||||
Scene loaded = SceneManager.GetSceneByName(startSceneName);
|
||||
if (loaded.IsValid())
|
||||
SceneManager.SetActiveScene(loaded);
|
||||
|
||||
SceneManager.UnloadSceneAsync(mainScene); // 加载完成后卸载 Main
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c630a6a725d34ce393e88e4152a280c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 暂停按钮。点击后在指定父节点下实例化暂停菜单预制体。
|
||||
/// 菜单预制体、父节点都在 Inspector 里指定,代码不查找节点。
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class PauseButton : MonoBehaviour
|
||||
{
|
||||
[Tooltip("点击后要显示的暂停菜单预制体")]
|
||||
[SerializeField] private GameObject pauseMenuPrefab;
|
||||
|
||||
[Tooltip("菜单生成到哪个父节点下;留空则用本按钮的父节点(一般是 Canvas)")]
|
||||
[SerializeField] private Transform menuParent;
|
||||
|
||||
private GameObject _instance;
|
||||
|
||||
public void OnPauseClicked()
|
||||
{
|
||||
if (pauseMenuPrefab == null)
|
||||
{
|
||||
Debug.LogError("[PauseButton] 没有指定暂停菜单预制体(pauseMenuPrefab)。", this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_instance != null) // 菜单已经开着,不重复生成
|
||||
return;
|
||||
|
||||
Transform parent = menuParent != null ? menuParent : transform.parent;
|
||||
_instance = Instantiate(pauseMenuPrefab, parent);
|
||||
|
||||
Time.timeScale = 0f; // 暂停游戏;不需要真暂停就删掉这一行
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96966a3c5c8764fc4bedfd83928fce19
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
/// <summary>
|
||||
/// 暂停菜单:继续游戏 / 回到 Main 场景。
|
||||
/// 按钮点击在各自 Button 的 On Click() 里接线(Inspector 配置)。
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class PauseMenu : MonoBehaviour
|
||||
{
|
||||
[Tooltip("「回到Main」要加载的场景名,必须已经加进 Build Settings")]
|
||||
[SerializeField] private string mainSceneName = "Main";
|
||||
|
||||
private bool _leaving;
|
||||
|
||||
/// <summary>继续游戏:恢复时间,关掉自己。</summary>
|
||||
public void OnContinueClicked()
|
||||
{
|
||||
Time.timeScale = 1f;
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
/// <summary>回到 Main:加载 Main 场景,加载完成后卸载当前场景。</summary>
|
||||
public void OnBackToMainClicked()
|
||||
{
|
||||
if (_leaving) return;
|
||||
_leaving = true;
|
||||
Time.timeScale = 1f;
|
||||
StartCoroutine(LoadMainThenUnloadCurrent());
|
||||
}
|
||||
|
||||
private IEnumerator LoadMainThenUnloadCurrent()
|
||||
{
|
||||
Scene currentScene = gameObject.scene;
|
||||
|
||||
AsyncOperation load = SceneManager.LoadSceneAsync(mainSceneName, LoadSceneMode.Additive);
|
||||
if (load == null)
|
||||
{
|
||||
Debug.LogError("[PauseMenu] 场景 \"" + mainSceneName + "\" 加载失败,确认它已加入 Build Settings。", this);
|
||||
_leaving = false;
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return load; // 等 Main 加载完成
|
||||
|
||||
Scene loaded = SceneManager.GetSceneByName(mainSceneName);
|
||||
if (loaded.IsValid())
|
||||
SceneManager.SetActiveScene(loaded);
|
||||
|
||||
SceneManager.UnloadSceneAsync(currentScene); // 卸载当前场景
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e23dc01f92265409eb1d1cfafc1ad5dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,152 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
/// <summary>
|
||||
/// 屏幕虚拟摇杆(移动轮盘)。挂在预制体根节点上,子物体约定按名字查找:
|
||||
/// Background —— 底盘(RectTransform + Image)
|
||||
/// Handle —— 摇杆头(RectTransform + Image)
|
||||
///
|
||||
/// 鼠标按住拖拽或手指触摸都可以操作,对外输出归一化方向 <see cref="Value"/>:
|
||||
/// x = 左右(右为正),y = 前后(上为正),模长 0~1 表示推杆幅度。
|
||||
/// 松手后方向归零。
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class VirtualJoystick : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler
|
||||
{
|
||||
[Header("引用(留空则按子物体名自动查找)")]
|
||||
[SerializeField] private RectTransform background;
|
||||
[SerializeField] private RectTransform handle;
|
||||
|
||||
[Header("手感(距离单位是 UI 像素,以底盘轴心为圆心)")]
|
||||
[Tooltip("摇杆头允许离开中心的最大距离。拖到这个距离就是满值输入(Value 模长 = 1),再往外拖摇杆头也不动。" +
|
||||
"底盘 280、摇杆头 120 时,80 ≈ 摇杆头刚好贴到底盘边缘。")]
|
||||
[SerializeField] private float maxDragRadius = 80f;
|
||||
|
||||
[Tooltip("死区:拖动距离小于「最大半径 × 该比例」时不输出,避免手抖")]
|
||||
[Range(0f, 0.9f)]
|
||||
[SerializeField] private float deadZone = 0.12f;
|
||||
|
||||
[Tooltip("松手后摇杆头是否弹回中心")]
|
||||
[SerializeField] private bool returnToCenter = true;
|
||||
|
||||
[Tooltip("松手后是否隐藏整个轮盘(移动端常用)")]
|
||||
[SerializeField] private bool hideOnRelease = false;
|
||||
|
||||
/// <summary>归一化输入方向,x = 左右,y = 前后,模长 0~1。</summary>
|
||||
public Vector2 Value { get; private set; }
|
||||
|
||||
/// <summary>当前是否按住了轮盘。</summary>
|
||||
public bool IsPressed { get; private set; }
|
||||
|
||||
private Canvas _canvas;
|
||||
private Vector2 _handleHomePosition;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (background == null)
|
||||
background = FindChildRect("Background");
|
||||
// 没有名为 Background 的子物体时,就把挂脚本的这个节点本身当作底盘
|
||||
if (background == null)
|
||||
background = transform as RectTransform;
|
||||
if (handle == null)
|
||||
handle = FindChildRect("Handle");
|
||||
|
||||
_canvas = GetComponentInParent<Canvas>();
|
||||
if (handle != null)
|
||||
_handleHomePosition = handle.anchoredPosition;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
ResetInput();
|
||||
}
|
||||
|
||||
public void OnPointerDown(PointerEventData eventData)
|
||||
{
|
||||
IsPressed = true;
|
||||
gameObject.SetActive(true);
|
||||
UpdateFromPointer(eventData);
|
||||
}
|
||||
|
||||
public void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
IsPressed = true;
|
||||
UpdateFromPointer(eventData);
|
||||
}
|
||||
|
||||
public void OnPointerUp(PointerEventData eventData)
|
||||
{
|
||||
IsPressed = false;
|
||||
|
||||
if (returnToCenter)
|
||||
ResetInput();
|
||||
|
||||
if (hideOnRelease)
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>把摇杆归零,手柄回到初始位置。</summary>
|
||||
public void ResetInput()
|
||||
{
|
||||
Value = Vector2.zero;
|
||||
if (handle != null)
|
||||
handle.anchoredPosition = _handleHomePosition;
|
||||
}
|
||||
|
||||
private void UpdateFromPointer(PointerEventData eventData)
|
||||
{
|
||||
if (background == null) return;
|
||||
|
||||
Camera cam = _canvas != null && _canvas.renderMode == RenderMode.ScreenSpaceOverlay
|
||||
? null
|
||||
: (eventData.pressEventCamera != null ? eventData.pressEventCamera : (_canvas != null ? _canvas.worldCamera : null));
|
||||
|
||||
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(background, eventData.position, cam, out Vector2 local))
|
||||
return;
|
||||
|
||||
// local 是相对底盘轴心的像素偏移,直接按「最大拖动半径」归一化
|
||||
float radius = Mathf.Max(1f, maxDragRadius);
|
||||
float distance = local.magnitude;
|
||||
Vector2 direction = distance > 0.0001f ? local / distance : Vector2.zero;
|
||||
float magnitude = Mathf.Clamp01(distance / radius);
|
||||
|
||||
if (magnitude < deadZone)
|
||||
{
|
||||
Value = Vector2.zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 死区外的部分重新映射到 0~1,避免刚出死区就一大跳
|
||||
float remapped = Mathf.InverseLerp(deadZone, 1f, magnitude);
|
||||
Value = direction * Mathf.Clamp01(remapped);
|
||||
}
|
||||
|
||||
if (handle != null)
|
||||
handle.anchoredPosition = _handleHomePosition + direction * (magnitude * radius);
|
||||
}
|
||||
|
||||
private RectTransform FindChildRect(string childName)
|
||||
{
|
||||
Transform child = transform.Find(childName);
|
||||
if (child == null)
|
||||
{
|
||||
// 允许更深层级,比如 Root/Art/Background
|
||||
foreach (RectTransform rt in GetComponentsInChildren<RectTransform>(true))
|
||||
{
|
||||
if (rt != null && rt != transform && rt.name == childName)
|
||||
return rt;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return child as RectTransform;
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (handle == null)
|
||||
handle = FindChildRect("Handle");
|
||||
if (background == null)
|
||||
background = FindChildRect("Background") ?? transform as RectTransform;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a40b5077dc83e4f33a9f0718325ff7e8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in new issue
Block a user