diff --git a/Assets/Scripts/Game/NumberDataControl.cs b/Assets/Scripts/Game/NumberDataControl.cs new file mode 100644 index 0000000..1364dcc --- /dev/null +++ b/Assets/Scripts/Game/NumberDataControl.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using UnityEngine; +using UnityEngine.UI; +using static PublicClass.DLL; +using Debug = UnityEngine.Debug; + +public class NumberDataControl : MonoBehaviour +{ + public Text ScoreUI_Text; + public Text TimeUI_Text; + public GameObject single_ClickTarget; + + public static bool gameIsStart = false; + /// + /// 游戏剩余时间,单位: 10ms + /// + private static int gameTime = 3000; + public static int GetGameTime { get { return gameTime; } } + /// + /// 更改游戏剩余时间且更新UI显示
+ /// 将值设置为-1时只刷新UI,不设置值 + ///
+ public int GameTimeUI + { + get { return gameTime; } + set + { + if(value!=-1) + gameTime = value; + string timeStr = gameTime.ToString().PadLeft(4, '0'); + TimeUI_Text.text = timeStr[..^2] + ":" + timeStr.Substring(timeStr.Length - 2, 2); + } + } + /// + /// 游戏得分 + /// + private static int gameScore = 0; + public static int GetGameScore + { + get { return gameScore; } + } + int GameScore + { + get { return gameScore; } + set + { + gameScore = value; + ScoreUI_Text.text = gameScore.ToString(); + } + } + + /// + /// 表示鼠标点击了某物 + /// + /// 是否命中目标 + internal delegate void MouseClickSomething(bool isHit); + internal static event MouseClickSomething MouseClickST_event; + internal static void Trigger_MouseClickST_event(bool isHit_) + { + MouseClickST_event(isHit_); + } + + /// + /// 全写: MouseClickSomething
+ /// 表示鼠标点击了某物 + ///
+ /// 是否命中目标 + internal void MouseClickST(bool isHit) + { + if (!gameIsStart) + { + gameIsStart = true; + countdownThread = new(CountdownThread); + countdownUIThread = new(CountdownUIThread); + countdownThread.Start(); + countdownUIThread.Start(); + } + if (isHit) + GameScore++; + else + GameScore--; + + } + Thread countdownThread; + Thread countdownUIThread; + void CountdownThread() + { + while (gameTime > 0) + { + Sleep(10); + gameTime--; + } + gameTime = 0; + + MessageBox(IntPtr.Zero, "最后得分为: " + gameScore.ToString(), "游戏结束", 0); + { + bool wait = false; + ThreadDelegate.QueueOnMainThread((param) => + { + single_ClickTarget.transform.localPosition = new Vector2(0, 0); + GameScore = 0; + gameIsStart = false; + GameTimeUI = 3000; + wait = true; + }, null); + while (!wait) ; + } + } + void CountdownUIThread() + { + while(gameTime > 0) + { + bool wait = false; + ThreadDelegate.QueueOnMainThread((param) => + { + GameTimeUI = -1; + wait = true; + }, null); + Sleep(10); + while (!wait) ; + } + ThreadDelegate.QueueOnMainThread((param) => + { + GameTimeUI = 0; + }, null); + } + private void Start() + { + NumberDataControl.MouseClickST_event += MouseClickST; + } + /// + /// 更精准的Sleep函数 + /// + /// 毫秒 + static void Sleep(int ms) + { + var sw = Stopwatch.StartNew(); + var sleepMs = ms - 16; + if (sleepMs > 0) + { + Thread.Sleep(sleepMs); + } + while (sw.ElapsedMilliseconds < ms) + { + Thread.Sleep(0); + } + } +} diff --git a/Assets/Scripts/Game/Single_ClickTarget.cs b/Assets/Scripts/Game/Single_ClickTarget.cs new file mode 100644 index 0000000..7f0243a --- /dev/null +++ b/Assets/Scripts/Game/Single_ClickTarget.cs @@ -0,0 +1,35 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +public class Single_ClickTarget : MonoBehaviour +{ + const float minX = -8.7f; + const float minY = -4.4f; + const float maxX = 8.7f; + const float maxY = 4.4f; + + void Update() + { + if (Input.GetMouseButtonDown(0)||Input.GetMouseButtonDown(1)) + { + if (NumberDataControl.GetGameTime != 0) + { + // 射线检测碰撞器是否被点击 + Vector2 clickPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition); + RaycastHit2D hit = Physics2D.Raycast(clickPosition, Vector2.zero); + // 不为null,则认为有物体撞到 + if (hit.collider != null) + { + var hitObj = hit.collider.gameObject; + // 自行逻辑处理 + + transform.localPosition = new Vector2(Random.Range(minX, maxX), Random.Range(minY, maxY)); + NumberDataControl.Trigger_MouseClickST_event(true); + } + else + NumberDataControl.Trigger_MouseClickST_event(false); + } + } + } +} diff --git a/Assets/Scripts/KeyManager.cs b/Assets/Scripts/KeyManager.cs new file mode 100644 index 0000000..7bbdc92 --- /dev/null +++ b/Assets/Scripts/KeyManager.cs @@ -0,0 +1,269 @@ +using System.Threading; +using UnityEngine; +using UnityEngine.Tilemaps; +using UnityEngine.UI; +using UnityEngine.SceneManagement; +using UnityEngine.UIElements; + +public class KeyManager : MonoBehaviour +{ + Thread keyListenThread; + void Start() + { + keyListenThread = new(KeyListenThread); + keyListenThread.Start(); + } + public static class WaitLock + { + internal static bool exitGameWaitLock = false; + } + + void KeyListenThread() + { + bool CheckEscKey() + { + bool tmp = false; + bool wait = false; + ThreadDelegate.QueueOnMainThread((param) => + { + tmp = GameKey.EscClick(); + wait = true; + }, null); + while (!wait) ; + return tmp; + } + while (keyListenThread.IsAlive) + { + if (!WaitLock.exitGameWaitLock && + CheckEscKey()) + { + WaitLock.exitGameWaitLock = true; + //ExitGameWait(); + ExitGame(); + } + Thread.Sleep(10); + } + } + /// + /// 长按esc退出游戏 + /// + void ExitGameWait() + { + ThreadDelegate.QueueOnMainThread((param) => + { + GameObject quitText = GameObject.Find("MainCamera").transform.Find("Quit_Text").gameObject; + quitText.SetActive(true); + Text quitText_text = quitText.transform.Find("Quit_Text_text").gameObject.GetComponent(); + const string mTxt = "quit game"; + const int maxTime = 5; + + Thread runThread = new(() => + { + int i = 0; + bool keyCheck() + { + bool wait = false; + bool tmp = false; + ThreadDelegate.QueueOnMainThread((param) => + { + quitText_text.text = mTxt.PadRight(mTxt.Length + i, '.'); + if (!GameKey.EscClick()) + tmp = false; + else + tmp = true; + wait = true; + }, null); + while (!wait) ; + return tmp; + } + for (; i < maxTime; i++) + { + if (!keyCheck()) + { + break; + } + Thread.Sleep(240); + } + if (!keyCheck())//最后一次检查,避免滞后 + { + goto exit; + } + ExitGame(); + goto over; + exit:;//停止退出程序 + { + bool wait = false; + ThreadDelegate.QueueOnMainThread((param) => + { + quitText_text.text = ""; + quitText.SetActive(false); + wait = true; + }, null); + while (!wait) ; + } + over:; + + }); + runThread.Start(); + }, null); + } + void ExitGame() + { + bool wait = false; + ThreadDelegate.QueueOnMainThread((param) => + { +#if UNITY_EDITOR + UnityEditor.EditorApplication.isPlaying = false; +#else + Application.Quit(); +#endif + wait = true; + }, null); + while (!wait) ; + + WaitLock.exitGameWaitLock = false; + } +} +public static class GameKey +{ + /// + /// XBox按键 + /// + public static class XBox + { + /// + /// 左侧摇杆水平轴 + /// X axis + /// + public const string LeftStickHorizontal = "LeftStickHorizontal"; + /// + /// 左侧摇杆垂直轴 + /// Y axis + /// + public const string LeftStickVertical = "LeftStickVertical"; + /// + /// 右侧摇杆水平轴 + /// 4th axis + /// + public const string RightStickHorizontal = "RightStickHorizontal"; + /// + /// 右侧摇杆垂直轴 + /// 5th axis + /// + public const string RightStickVertical = "RightStickVertical"; + /// + /// 十字方向盘水平轴 + /// 6th axis + /// + public const string DPadHorizontal = "DPadHorizontal"; + /// + /// 十字方向盘垂直轴 + /// 7th axis + /// + public const string DPadVertical = "DPadVertical"; + /// + /// LT + /// 9th axis + /// + public const string LT = "LT"; + /// + /// RT + /// 10th axis + /// + public const string RT = "RT"; + /// + /// 左侧摇杆按键 + /// joystick button 8 + /// + public const KeyCode LeftStick = KeyCode.JoystickButton8; + /// + /// 右侧摇杆按键 + /// joystick button 9 + /// + public const KeyCode RightStick = KeyCode.JoystickButton9; + /// + /// A键 + /// joystick button 0 + /// + public const KeyCode A = KeyCode.JoystickButton0; + /// + /// B键 + /// joystick button 1 + /// + public const KeyCode B = KeyCode.JoystickButton1; + /// + /// X键 + /// joystick button 2 + /// + public const KeyCode X = KeyCode.JoystickButton2; + /// + /// Y键 + /// joystick button 3 + /// + public const KeyCode Y = KeyCode.JoystickButton3; + /// + /// LB键 + /// joystick button 4 + /// + public const KeyCode LB = KeyCode.JoystickButton4; + /// + /// RB键 + /// joystick button 5 + /// + public const KeyCode RB = KeyCode.JoystickButton5; + /// + /// View视图键 + /// joystick button 6 + /// + public const KeyCode View = KeyCode.JoystickButton6; + /// + /// Menu菜单键 + /// joystick button 7 + /// + public const KeyCode Menu = KeyCode.JoystickButton7; + } + + + readonly static KeyCode[] escKey = new KeyCode[] { XBox.Menu, KeyCode.Escape }; + readonly static KeyCode upKey = KeyCode.UpArrow; + readonly static KeyCode downKey = KeyCode.DownArrow; + readonly static KeyCode leftKey = KeyCode.LeftArrow; + readonly static KeyCode rightKey = KeyCode.RightArrow; + static float DPadH() { return Input.GetAxis(XBox.DPadHorizontal); } + static float DPadV() { return Input.GetAxis(XBox.DPadVertical); } + + public static bool EscClick() + { + foreach (KeyCode k in escKey) + { + if (Input.GetKey(k)) + return true; + } + return false; + } + + public static bool UpKeyClick() + { + if (Input.GetKey(upKey)) return true; + else if (DPadV() < 0) return true; + else return false; + } + public static bool DownKeyClick() + { + if (Input.GetKey(downKey)) return true; + else if (DPadV() > 0) return true; + else return false; + } + public static bool LeftKeyClick() + { + if (Input.GetKey(leftKey)) return true; + else if (DPadH() < 0) return true; + else return false; + } + public static bool RightKeyClick() + { + if (Input.GetKey(rightKey)) return true; + else if (DPadH() > 0) return true; + else return false; + } +} \ No newline at end of file diff --git a/Assets/Scripts/MainMenu/MainMenu_StartGameText.cs b/Assets/Scripts/MainMenu/MainMenu_StartGameText.cs new file mode 100644 index 0000000..5327e1d --- /dev/null +++ b/Assets/Scripts/MainMenu/MainMenu_StartGameText.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEngine.UI; +using static PublicClass.DLL; + +public class MainMenu_StartGameText : MonoBehaviour +{ + public const string version = "1.0.0.20241015"; + + + + private void Update() + { + if (Input.anyKeyDown) + { + if (Input.GetKey(KeyCode.A)) + { + MessageBox(IntPtr.Zero, + "游戏名: 光标训练(CursorTraining)\r\n" + + "版本: V"+version+"\r\n" + + "Copyright (C) 2024 Hgnim, All rights reserved.\r\n" + + "Github: https://github.com/Hgnim/CursorTraining" + , "关于", 0); + goto exitVoid; + } + else if(Input.GetKey(KeyCode.Escape)) + goto exitVoid; + + + DontDestroyOnLoad(GameObject.Find("KeyManager")); + DontDestroyOnLoad(GameObject.Find("ThreadDelegate")); + SceneManager.LoadScene("Game"); + } +exitVoid:; + } +} diff --git a/Assets/Scripts/PublicClass.cs b/Assets/Scripts/PublicClass.cs new file mode 100644 index 0000000..70788b1 --- /dev/null +++ b/Assets/Scripts/PublicClass.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using UnityEngine; + +namespace PublicClass +{ + public static class DLL + { + [DllImport("User32.dll", SetLastError = true, ThrowOnUnmappableChar = true, CharSet = CharSet.Auto)] + public static extern int MessageBox(IntPtr handle, String message, String title, int type); + } +} diff --git a/Assets/Scripts/ThreadDelegate.cs b/Assets/Scripts/ThreadDelegate.cs new file mode 100644 index 0000000..6709f6f --- /dev/null +++ b/Assets/Scripts/ThreadDelegate.cs @@ -0,0 +1,131 @@ +using UnityEngine; +using System.Collections.Generic; +using System; +using System.Threading; +using System.Linq; + +public class ThreadDelegate : MonoBehaviour +{ + //是否已经初始化 + static bool isInitialized; + + private static ThreadDelegate _ins; + public static ThreadDelegate ins { get { Initialize(); return _ins; } } + + void Awake() + { + _ins = this; + isInitialized = true; + } + + //初始化 + public static void Initialize() + { + if (!isInitialized) + { + if (!Application.isPlaying) + return; + + isInitialized = true; + var obj = new GameObject("ThreadDelegate"); + _ins = obj.AddComponent(); + + DontDestroyOnLoad(obj); + } + } + + //单个执行单元(无延迟) + struct NoDelayedQueueItem + { + public Action action; + public object param; + } + //全部执行列表(无延迟) + List listNoDelayActions = new List(); + + + //单个执行单元(有延迟) + struct DelayedQueueItem + { + public Action action; + public object param; + public float time; + } + //全部执行列表(有延迟) + List listDelayedActions = new List(); + + + //加入到主线程执行队列(无延迟) + public static void QueueOnMainThread(Action taction, object param) + { + QueueOnMainThread(taction, param, 0f); + } + + //加入到主线程执行队列(有延迟) + public static void QueueOnMainThread(Action action, object param, float time) + { + if (time != 0) + { + lock (ins.listDelayedActions) + { + ins.listDelayedActions.Add(new DelayedQueueItem { time = Time.time + time, action = action, param = param }); + } + } + else + { + lock (ins.listNoDelayActions) + { + ins.listNoDelayActions.Add(new NoDelayedQueueItem { action = action, param = param }); + } + } + } + + + //当前执行的无延时函数链 + List currentActions = new List(); + //当前执行的有延时函数链 + List currentDelayed = new List(); + + void Update() + { + if (listNoDelayActions.Count > 0) + { + lock (listNoDelayActions) + { + currentActions.Clear(); + currentActions.AddRange(listNoDelayActions); + listNoDelayActions.Clear(); + } + for (int i = 0; i < currentActions.Count; i++) + { + currentActions[i].action(currentActions[i].param); + } + } + + if (listDelayedActions.Count > 0) + { + lock (listDelayedActions) + { + currentDelayed.Clear(); + currentDelayed.AddRange(listDelayedActions.Where(d => Time.time >= d.time)); + for (int i = 0; i < currentDelayed.Count; i++) + { + listDelayedActions.Remove(currentDelayed[i]); + } + } + + for (int i = 0; i < currentDelayed.Count; i++) + { + currentDelayed[i].action(currentDelayed[i].param); + } + } + } + + void OnDisable() + { + if (_ins == this) + { + _ins = null; + } + } +} \ No newline at end of file