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