This commit is contained in:
SoulliesOfficial
2025-07-21 05:42:20 -04:00
parent e483cfe502
commit bae0bfbc20
533 changed files with 172709 additions and 125965 deletions

View File

@@ -5,10 +5,11 @@ using Ichni.RhythmGame;
using Ichni.RhythmGame.UI;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Ichni
{
public class GameManager : SerializedMonoBehaviour
public partial class GameManager : SerializedMonoBehaviour
{
public static GameManager instance;
@@ -33,6 +34,8 @@ namespace Ichni
public ProjectInformation projectInformation;
public SongInformation songInformation;
public NoteManager noteManager;
public BasePrefabsCollection basePrefabs;
public Dictionary<string, CustomPrefabsCollection> customPrefabs;
@@ -42,7 +45,7 @@ namespace Ichni
public GameLoadingCanvas gameLoadingCanvas;
public SummaryPageCanvas summaryPageCanvas;
public float songTime => audioManager.songPlayer.songTimeSegment;
public float songTime => audioManager.songPlayer.songTimeSegment - songInformation.offset;
public bool isDebugging;
@@ -57,4 +60,19 @@ namespace Ichni
projectLoader.TestLoad();
}
}
public partial class GameManager
{
public static void RestartGame()
{
SceneManager.LoadScene("GameScene");
Time.timeScale = 1f; // 确保重启时时间缩放恢复正常
}
public static void ReturnToMenu()
{
SceneManager.LoadScene("MenuScene");
Time.timeScale = 1f; // 确保返回时时间缩放恢复正常
}
}
}

View File

@@ -1,5 +1,6 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Ichni.RhythmGame;
using Sirenix.OdinInspector;
using UnityEngine;
@@ -18,17 +19,21 @@ namespace Ichni
public List<Flick> checkingFlickList;
public List<InputUnitTap> inputUnitTapList;
public List<InputUnitSlide> inputUnitSlideList;
public List<InputUnitRelease> inputUnitReleaseList;
public List<InputUnitTouch> inputUnitTouchList;
public List<InputUnitSwipe> inputUnitSwipeList;
private void Start()
{
for (int i = -2; i <= 12; i++)
{
inputUnitTapList.Add(new InputUnitTap(i, Vector2.zero));
inputUnitSlideList.Add(new InputUnitSlide(i, Vector2.zero, Vector2.zero));
inputUnitReleaseList.Add(new InputUnitRelease(i, Vector2.zero));
}
checkingTapList = new List<Tap>();
checkingStayList = new List<Stay>();
checkingHoldList = new List<Hold>();
checkingFlickList = new List<Flick>();
inputUnitTapList = new List<InputUnitTap>();
inputUnitTouchList = new List<InputUnitTouch>();
inputUnitSwipeList = new List<InputUnitSwipe>();
RhythmInputManager.OnTap += SetNewInputUnitTap;
RhythmInputManager.OnTouch += SetNewInputUnitTouch;
RhythmInputManager.OnSwipe += SetNewInputUnitSwipe;
}
public void Update()
@@ -37,58 +42,8 @@ namespace Ichni
{
return;
}
if (Application.platform == RuntimePlatform.WindowsPlayer ||
Application.platform == RuntimePlatform.OSXPlayer ||
Application.platform == RuntimePlatform.WindowsEditor ||
Application.platform == RuntimePlatform.OSXEditor)
{
if (Mouse.current.leftButton.wasPressedThisFrame)
{
SetNewInputUnitTap(-1, Mouse.current.position.ReadValue());
}
if (Mouse.current.leftButton.isPressed)
{
SetNewInputUnitSlide(-1, Mouse.current.position.ReadValue(), Mouse.current.delta.ReadValue());
}
if (Mouse.current.rightButton.wasPressedThisFrame)
{
SetNewInputUnitTap(-2, Mouse.current.position.ReadValue());
}
if (Mouse.current.rightButton.isPressed)
{
SetNewInputUnitSlide(-2, Mouse.current.position.ReadValue(), Mouse.current.delta.ReadValue());
}
}
foreach (var touch in Touch.activeTouches)
{
switch (touch.phase)
{
case TouchPhase.Began:
SetNewInputUnitTap(touch.finger.index, touch.screenPosition);
SetNewInputUnitSlide(touch.finger.index, touch.screenPosition, Vector2.zero);
break;
case TouchPhase.Stationary:
SetNewInputUnitSlide(touch.finger.index, touch.screenPosition, Vector2.zero);
break;
case TouchPhase.Moved:
SetNewInputUnitSlide(touch.finger.index, touch.screenPosition, touch.delta);
break;
case TouchPhase.Ended:
SetNewInputUnitRelease(touch.finger.index, touch.screenPosition);
break;
}
}
List<InputUnitTap> enablingInputUnitTapList = inputUnitTapList.FindAll(x => x.isEnabling);
List<InputUnitSlide> enablingInputUnitSlideList = inputUnitSlideList.FindAll(x => x.isEnabling);
List<InputUnitRelease> enablingInputUnitReleaseList = inputUnitReleaseList.FindAll(x => x.isEnabling);
foreach (InputUnitTap inputUnitTap in enablingInputUnitTapList)
foreach (InputUnitTap inputUnitTap in inputUnitTapList)
{
List<Tap> availableTaps = new List<Tap>();
foreach (Tap tap in checkingTapList)
@@ -98,101 +53,133 @@ namespace Ichni
availableTaps.Add(tap);
}
}
if (availableTaps.Count > 0)
List<Hold> availableHolds = new List<Hold>();
foreach (Hold hold in checkingHoldList)
{
if (hold.CheckJudgeAvailability(inputUnitTap))
{
availableHolds.Add(hold);
}
}
bool haveHold = availableHolds.Count > 0;
bool haveTap = availableTaps.Count > 0;
Hold closestHold = availableHolds.Min();
Tap closestTap = availableTaps.Min();
if (haveHold && haveTap)
{
if (closestHold.exactJudgeTime < closestTap.exactJudgeTime)
{
closestHold.ExecuteStartJudge();
}
else
{
closestTap.ExecuteStartJudge();
}
}
else if (haveHold)
{
closestHold.ExecuteStartJudge();
}
else if (haveTap)
{
availableTaps.Sort();
Tap closestTap = availableTaps[0];
Debug.Log(closestTap.exactJudgeTime);
closestTap.ExecuteStartJudge();
}
}
foreach (InputUnitSlide inputUnitSlide in enablingInputUnitSlideList)
foreach (InputUnitSwipe inputUnitSwipe in inputUnitSwipeList)
{
/*List<Flick> availableFlicks = new List<Flick>();
List<Flick> availableFlicks = new List<Flick>();
foreach (Flick flick in checkingFlickList)
{
if (flick.CheckJudgeAvailability(inputUnitSlide))
if (flick.CheckJudgeAvailability(inputUnitSwipe))
{
availableFlicks.Add(flick);
}
}
if (availableFlicks.Count > 0)
{
availableFlicks.Sort();
Flick closestFlick = availableFlicks[0];
closestFlick.SetFirstJudge(inputUnitSlide.inputDeltaPosition);
}*/
closestFlick.ExecuteStartJudge();
}
}
foreach (InputUnitTouch inputUnitTouch in inputUnitTouchList)
{
List<Stay> availableStays = new List<Stay>();
foreach (Stay stay in checkingStayList)
{
if (stay.CheckJudgeAvailability(inputUnitSlide))
if (stay.CheckJudgeAvailability(inputUnitTouch))
{
availableStays.Add(stay);
}
}
List<Hold> availableHolds = new List<Hold>();
foreach (Hold hold in checkingHoldList)
{
if (hold.CheckJudgeAvailability(inputUnitTouch))
{
availableHolds.Add(hold);
}
}
foreach (Stay stay in availableStays)
{
stay.ExecuteStartJudge();
}
foreach (Hold hold in availableHolds)
{
hold.ExecuteProcessJudge();
}
}
}
private void LateUpdate()
{
if (!GameManager.instance.audioManager.isPlaying)
{
return;
}
for (int i = 0; i < inputUnitTapList.Count; i++)
{
inputUnitTapList[i].isEnabling = false;
inputUnitSlideList[i].isEnabling = false;
inputUnitSlideList[i].inputDeltaPosition = Vector2.zero;
inputUnitSlideList[i].isEnabling = false;
}
inputUnitTapList.Clear();
inputUnitTouchList.Clear();
inputUnitSwipeList.Clear();
}
public void SetNewInputUnitTap(int fingerId, Vector2 inputPosition)
{
InputUnitTap inputUnitTap = inputUnitTapList.Find(x => x.fingerId == fingerId);
inputUnitTap.isEnabling = true;
inputUnitTap.inputPosition = inputPosition;
//Debug.Log("Tap: " + fingerId + " " + inputPosition);
InputUnitTap inputUnitTap = new InputUnitTap(fingerId, inputPosition);
if(!inputUnitTapList.Exists(x => x.fingerId == fingerId))
{
inputUnitTapList.Add(inputUnitTap);
//Debug.Log("Tap: " + fingerId + " " + inputPosition);
}
}
public void SetNewInputUnitSlide(int fingerId, Vector2 inputPosition, Vector2 inputDeltaPosition)
public void SetNewInputUnitTouch(int fingerId, Vector2 inputPosition)
{
InputUnitSlide inputUnitSlide = inputUnitSlideList.Find(x => x.fingerId == fingerId);
inputUnitSlide.isEnabling = true;
inputUnitSlide.inputPosition = inputPosition;
inputUnitSlide.inputDeltaPosition = inputDeltaPosition;
//Debug.Log("Slide: " + fingerId + " " + inputPosition + " " + inputDeltaPosition);
InputUnitTouch inputUnitTouch = new InputUnitTouch(fingerId, inputPosition);
if(!inputUnitTouchList.Exists(x => x.fingerId == fingerId))
{
inputUnitTouchList.Add(inputUnitTouch);
}
//Debug.Log("Touch: " + fingerId + " " + inputPosition);
}
public void SetNewInputUnitRelease(int fingerId, Vector2 inputPosition)
public void SetNewInputUnitSwipe(int fingerId, Vector2 inputPosition, Vector2 delta)
{
InputUnitRelease inputUnitRelease = inputUnitReleaseList.Find(x => x.fingerId == fingerId);
inputUnitRelease.isEnabling = true;
inputUnitRelease.inputPosition = inputPosition;
//Debug.Log("Release: " + fingerId + " " + inputPosition);
InputUnitSwipe inputUnitSwipe = new InputUnitSwipe(fingerId, inputPosition, delta);
if(!inputUnitSwipeList.Exists(x => x.fingerId == fingerId))
{
inputUnitSwipeList.Add(inputUnitSwipe);
}
//Debug.Log("Swipe: " + fingerId + " " + inputPosition + " " + delta);
}
}
public class InputUnit
{
public int fingerId;
public bool isEnabling;
public Vector2 inputPosition;
public InputManager inputManager => GameManager.instance.inputManager;
}
public class InputUnitTap : InputUnit
@@ -204,24 +191,24 @@ namespace Ichni
}
}
public class InputUnitSlide : InputUnit
public class InputUnitTouch : InputUnit
{
public Vector2 inputDeltaPosition;
public InputUnitSlide(int fingerId, Vector2 inputPosition, Vector2 inputDeltaPosition)
public InputUnitTouch(int fingerId, Vector2 inputPosition)
{
this.fingerId = fingerId;
this.inputPosition = inputPosition;
this.inputDeltaPosition = inputDeltaPosition;
}
}
public class InputUnitRelease : InputUnit
public class InputUnitSwipe : InputUnit
{
public InputUnitRelease(int fingerId, Vector2 inputPosition)
public Vector2 swipeDirection;
public InputUnitSwipe(int fingerId, Vector2 inputPosition, Vector2 swipeDirection)
{
this.fingerId = fingerId;
this.inputPosition = inputPosition;
this.swipeDirection = swipeDirection.normalized;
}
}
}

View File

@@ -0,0 +1,40 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Ichni.RhythmGame
{
public class NoteManager : MonoBehaviour
{
private List<(float activationTime, NoteBase note)> pendingNotes = new List<(float, NoteBase)>();
private int nextNoteIndex = 0;
public void RegisterNote(NoteBase note, float activationTime)
{
pendingNotes.Add((activationTime, note));
}
// 在所有物体注册完毕后,对列表进行一次排序
public void AllNotesRegistered()
{
pendingNotes.Sort((a, b) => a.activationTime.CompareTo(b.activationTime));
}
void Update()
{
// 如果所有音符都已激活,则不执行任何操作
if (nextNoteIndex >= pendingNotes.Count)
{
return;
}
// 检查下一个待激活的音符
while (nextNoteIndex < pendingNotes.Count &&
GameManager.instance.songTime >= pendingNotes[nextNoteIndex].activationTime)
{
pendingNotes[nextNoteIndex].note.gameObject.SetActive(true);
nextNoteIndex++;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9e50ec54a2c71f744bfced70191dc2d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using Ichni.RhythmGame.Beatmap;
using Sirenix.OdinInspector;
using UniRx;
@@ -28,13 +29,12 @@ namespace Ichni
format = ES3.Format.JSON,
location = ES3.Location.Resources
};
[Button("TestLoad")]
public void TestLoad()
{
string beatMapFolderPath = "Beatmaps/" + InformationTransistor.instance.chapterName +
"/" + InformationTransistor.instance.songName +
"/" + InformationTransistor.instance.difficultyName;
string beatMapFolderPath = "Beatmaps/" + InformationTransistor.instance.chapter.chapterIndex +
"/" + InformationTransistor.instance.song.songName +
"/" + InformationTransistor.instance.difficulty.difficultyName;
LoadProjectInfo(beatMapFolderPath);
LoadSongInfo(beatMapFolderPath);
@@ -49,27 +49,30 @@ namespace Ichni
.Subscribe(_ =>
{
LoadBeatMap(beatMapFolderPath);
Observable.EveryUpdate()
.Where(_ => ((BeatmapContainer_BM)GameManager.instance.beatmapContainer.matchedBM).remainingElementAmount.Value == 0)
.First()
.Subscribe(_ =>
{
GameManager.instance.gameUICanvas.readyText.DOFade(1, 0.5f)
.SetLoops(4, LoopType.Yoyo).SetEase(Ease.InOutFlash).Play();
Observable.Timer(TimeSpan.FromSeconds(2)).First().Subscribe(_ =>
{
GameManager.instance.audioManager.isStarting = false;
GameManager.instance.audioManager.songPlayer.PlaySong();
});
});
});
Observable.EveryUpdate()
.Where(_ => ThemeBundleManager.instance.waitingBundleAmount.Value == 0 &&
(GameManager.instance.beatmapContainer.matchedBM as BeatmapContainer_BM).remainingElementAmount.Value == 0)
.First()
.Subscribe(_ =>
{
Observable.Timer(TimeSpan.FromSeconds(2)).First().Subscribe(_ =>
{
GameManager.instance.audioManager.isStarting = false;
});
});
Observable.EveryUpdate()
/*Observable.EveryUpdate()
.Where(_ => !GameManager.instance.audioManager.isStarting)
.First()
.Subscribe(_ =>
{
GameManager.instance.audioManager.songPlayer.PlaySong();
});
});*/
}
public void Load(string chapterName, string musicName, string difficultyName)

View File

@@ -0,0 +1,209 @@
using UnityEngine;
using UnityEngine.InputSystem;
using System;
using System.Collections.Generic;
using Touch = UnityEngine.InputSystem.EnhancedTouch.Touch;
using TouchPhase = UnityEngine.InputSystem.TouchPhase;
/// <summary>
/// 为节奏游戏设计的输入管理器,处理多点触控并分发三种主要事件。
/// 【重要】此版本内置了编辑器内的鼠标模拟功能,无需手机即可测试。
/// </summary>
public class RhythmInputManager : MonoBehaviour
{
// =====================================================================
// 公共事件 (Public Events)
// =====================================================================
public static event Action<int, Vector2> OnTap;
public static event Action<int, Vector2> OnTouch;
public static event Action<int, Vector2, Vector2> OnSwipe;
// =====================================================================
// 可配置参数 (Configurable Parameters)
// =====================================================================
[Header("划动设置 (Swipe Settings)")]
[Tooltip("识别为划动的最小移动距离(像素)")]
[SerializeField] private float minSwipeDistance = 50.0f;
// =====================================================================
// 内部状态 (Internal State)
// =====================================================================
private class TouchState
{
public int TouchId;
public Vector2 StartPosition;
public float StartTime;
public Vector2 LastSwipeDirection = Vector2.zero;
public bool IsTapCandidate = true;
}
private readonly Dictionary<int, TouchState> _activeTouches = new Dictionary<int, TouchState>();
// 为鼠标模拟专门设置一个固定的Touch ID
private const int MOUSE_TOUCH_ID = 999;
// =====================================================================
// MonoBehaviour 生命周期方法 (Lifecycle Methods)
// =====================================================================
private void Awake()
{
//OnTap += (id, pos)=> Debug.Log($"Tap Detected: Touch ID {id}, Position {pos}");
//OnTouch += (id, pos) => Debug.Log($"Touch Detected: Touch ID {id}, Position {pos}");
//OnSwipe += (id, pos, dir) => Debug.Log($"Swipe Detected: Touch ID {id}, Position {pos}, Direction {dir}");
}
private void Update()
{
// 使用预处理指令区分平台
#if UNITY_EDITOR
// --- 在Unity编辑器中使用鼠标模拟触摸 ---
ProcessMouseInput();
#else
// --- 在真机设备上,使用真实的触摸输入 ---
ProcessRealTouchInput();
#endif
}
// =====================================================================
// 核心处理逻辑 (Core Processing Logic)
// =====================================================================
#if UNITY_EDITOR
/// <summary>
/// 【仅在编辑器中运行】处理鼠标输入并模拟触摸事件。
/// </summary>
private void ProcessMouseInput()
{
if (Mouse.current == null) return;
Vector2 position = Mouse.current.position.ReadValue();
if (Mouse.current.leftButton.wasPressedThisFrame)
{
ProcessInputEvent(MOUSE_TOUCH_ID, TouchPhase.Began, position);
}
else if (Mouse.current.leftButton.isPressed)
{
// 如果鼠标位置有变化则为Moved否则为Stationary
if (Mouse.current.delta.ReadValue().sqrMagnitude > 0.1f)
{
ProcessInputEvent(MOUSE_TOUCH_ID, TouchPhase.Moved, position);
}
else
{
ProcessInputEvent(MOUSE_TOUCH_ID, TouchPhase.Stationary, position);
}
}
else if (Mouse.current.leftButton.wasReleasedThisFrame)
{
ProcessInputEvent(MOUSE_TOUCH_ID, TouchPhase.Ended, position);
}
if (Mouse.current.rightButton.wasPressedThisFrame)
{
ProcessInputEvent(MOUSE_TOUCH_ID + 1, TouchPhase.Began, position);
}
else if (Mouse.current.rightButton.isPressed)
{
// 如果鼠标位置有变化则为Moved否则为Stationary
if (Mouse.current.delta.ReadValue().sqrMagnitude > 0.1f)
{
ProcessInputEvent(MOUSE_TOUCH_ID + 1, TouchPhase.Moved, position);
}
else
{
ProcessInputEvent(MOUSE_TOUCH_ID + 1, TouchPhase.Stationary, position);
}
}
else if (Mouse.current.rightButton.wasReleasedThisFrame)
{
ProcessInputEvent(MOUSE_TOUCH_ID + 1, TouchPhase.Ended, position);
}
}
#endif
/// <summary>
/// 【仅在真机上运行】处理真实的触摸屏输入。
/// </summary>
private void ProcessRealTouchInput()
{
if (Touchscreen.current == null) return;
foreach (Touch touch in Touch.activeTouches)
{
ProcessInputEvent(
touch.touchId,
touch.phase,
touch.screenPosition
);
}
}
/// <summary>
/// 所有输入事件的核心处理函数,无论是真实触摸还是鼠标模拟都会调用它。
/// </summary>
private void ProcessInputEvent(int touchId, TouchPhase phase, Vector2 position)
{
switch (phase)
{
case TouchPhase.Began:
var newState = new TouchState
{
TouchId = touchId,
StartPosition = position,
StartTime = Time.time,
};
_activeTouches[touchId] = newState;
OnTap?.Invoke(touchId, position);
OnTouch?.Invoke(touchId, position);
break;
case TouchPhase.Moved:
if (_activeTouches.TryGetValue(touchId, out TouchState movedState))
{
OnTouch?.Invoke(touchId, position);
DetectSwipe(movedState, position);
}
break;
case TouchPhase.Stationary:
if (_activeTouches.TryGetValue(touchId, out TouchState stationaryState))
{
OnTouch?.Invoke(touchId, position);
}
break;
case TouchPhase.Canceled:
if (_activeTouches.ContainsKey(touchId))
{
_activeTouches.Remove(touchId);
}
break;
}
}
/// <summary>
/// 检测划动逻辑 (无需修改)
/// </summary>
private void DetectSwipe(TouchState state, Vector2 currentPosition)
{
Vector2 swipeVector = currentPosition - state.StartPosition;
if (swipeVector.magnitude < minSwipeDistance) return;
Vector2 direction = swipeVector.normalized;
// 检查是否是新的划动方向
if (Vector2.Dot(direction, state.LastSwipeDirection) < 0.5f)
{
OnSwipe?.Invoke(state.TouchId, state.StartPosition, direction);
state.LastSwipeDirection = direction;
state.StartPosition = currentPosition;
state.StartTime = Time.time;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 99971dd6462223c4596d435e8acdcfb8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -21,15 +21,7 @@ namespace Ichni
private void Awake()
{
if (instance == null)
{
instance = this;
//DontDestroyOnLoad(gameObject); TODO: 后续处理
}
else if (instance != this)
{
Destroy(gameObject);
}
instance = this;
loadedThemeBundleList = new List<ThemeBundle>();
AssetBundle.UnloadAllAssetBundles(true);
@@ -101,7 +93,6 @@ namespace Ichni
else if (Application.platform == RuntimePlatform.OSXEditor ||
Application.platform == RuntimePlatform.OSXPlayer)
{
uri = Application.streamingAssetsPath + "/ThemeBundles/OSX/" + themeBundleName;
}
else if (Application.platform == RuntimePlatform.Android)
@@ -118,11 +109,12 @@ namespace Ichni
yield break;
}
UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(uri, 0);
yield return request.SendWebRequest();
AssetBundle bundle = AssetBundle.LoadFromFile(uri);
AssetBundleCreateRequest createRequest = AssetBundle.LoadFromFileAsync(uri);
yield return createRequest;
AssetBundle bundle = createRequest.assetBundle;
Object[] ob = bundle.LoadAllAssets<Object>();
loadedThemeBundleList.Add(new ThemeBundle(themeBundleName));
for (int i = 0; i < ob.Length; i++)
{
if (ob[i].GetType() == typeof(GameObject))