using UnityEngine; using System.IO; using System; using SFB; using System.Collections; public enum GeneralOptionPanelEvent { MouseGudies, BuildingModel, EditModel } /// /// 用于持续检测用户的鼠标输入,并且做出对应UI的响应,这是一个测试中的脚本,之后需要重构 /// public class MainMenuController : MonoBehaviour { private Transform _Transform; private MainMenuView _MainMenuView; private MainMenuModel _MainMenuModel; private GeneralOptionPanelController GeneralOptionPanel; private BuildingOptionPanelController BuildingOptionPanel; private SelectView _SelectView; /// /// 用于指示当前选择的时间组序号,默认为0 /// private int _SelectedBPMGroupIndex = 0; [SerializeField] private bool isGuidesEnabled = true;//是否启用鼠标参考线 [SerializeField] private bool isBuildingModelEnabled = false;//目前是否是创建模式,左键点击放置物件 [SerializeField] private bool optionHasBeenCreated = false;//是否已经创建了一个会话选项,再次点击退出会话 [SerializeField] private bool isAnyModelEnabled = false;//是否处于任何一种专注模式之下,这会关闭通常选项,使得用户右键会直接弹出该模式下的菜单 [SerializeField] private bool isAnyCoroutineEnabled = false;//目前是否有创造新物件的协程正在运行,防止重复开启协程 /// /// 用于在不同对象之间传递歌曲当前时间的全局容器,只负责传递,只在主控中赋值 /// private SongInformationContainer _SongTimeContainer; private BaseNote _preGeneratedObject; /// /// 当歌曲时间发生变化事触发该事件,传入的时间为改变后的时间,更新之后的UI /// private event Action OnTimeChange; #region Unity生命周期函数 void Start() { _MainMenuView = gameObject.GetComponent(); _MainMenuModel = gameObject.GetComponent(); _Transform = gameObject.GetComponent(); //启用传递歌曲信息的容器 _SongTimeContainer = new SongInformationContainer(); _SongTimeContainer.UpdateSongInformation(0); Application.targetFrameRate = 60; //初始化各个界面 InitTopPenal(); InitProgressBar(); InitAllOptionPanel(); _SelectView = _MainMenuView.NotePool.Get(_MainMenuView.MainMenuSelectView); _SelectView.Init(0, _SongTimeContainer, _MainMenuModel.MainBPMGroup); //注册事件 OnTimeChange += _MainMenuView.TopPanel.ChangeTimeText; OnTimeChange += ChangeProgressBar; } void Update() { //使用鼠标滚轮来控制当前时间 ChangeTimeWhitMouseScroll(Input.mouseScrollDelta.y); //在数据层更新当前的歌曲时间 _SongTimeContainer.UpdateSongInformation(_MainMenuModel.SongCurrentTime); //检查当前输入 CheckUserInput(); } #endregion #region 时间变化事件相关函数 /// /// 通过鼠标滚轮更改时间 /// private void ChangeTimeWhitMouseScroll(float value) { _MainMenuModel.SongCurrentTime += value * _MainMenuModel.MainBPMGroup.Beat / 4; if (value == 0) return; OnTimeChange?.Invoke(_MainMenuModel.SongCurrentTime); } /// /// 改变进度条 /// private void ChangeProgressBar(float time) { _MainMenuView.ProgressBar.progressSlider.value = _MainMenuModel.Song.time / _MainMenuModel.Song.clip.length; } #endregion #region TopPenal相关代码 /// /// 初始化顶部的UI控件 /// private void InitTopPenal() { //基准BPM输入窗口 _MainMenuView.TopPanel.BaseBPMInputField.onEndEdit?.AddListener(OnBaseBPMInputFieldEndEdit); _MainMenuView.TopPanel.BaseBPMInputField.text = _MainMenuModel.PersistentChartData.globalConfig.baseBPM.ToString(); //当前时间输入窗口 _MainMenuView.TopPanel.SongCurrentTimeInputField?.onEndEdit.AddListener(OnCurrentTimeInputFieldEndEdit); _MainMenuView.TopPanel.SongCurrentTimeInputField.text = _MainMenuModel.SongCurrentTime.ToString(); // _MainMenuView.TopPanel.PlaySongButton.onClick.AddListener(OnPlaySongButtonClick); _MainMenuView.TopPanel.StopSongButton.onClick.AddListener(OnStopSongButtonClick); } Coroutine _runningCoroutine; /// /// 开始播放按钮绑定的方法 /// private void OnPlaySongButtonClick() { if (_MainMenuModel.Song.isPlaying) return; _MainMenuModel.Song.Play(); _runningCoroutine = StartCoroutine(InPlaying()); } /// /// 暂停播放按钮绑定的方法 /// private void OnStopSongButtonClick() { _MainMenuModel.Song.Pause(); StopCoroutine(_runningCoroutine); } /// /// 歌曲在播放时开启的额外进程,实时监测歌曲是否在播放 /// private IEnumerator InPlaying() { /* * 协程只在按钮事件中开启或者停止 */ while (_MainMenuModel.Song.isPlaying) { OnTimeChange?.Invoke(_MainMenuModel.SongCurrentTime); yield return null; } } /// /// 当BaseBPM被编辑时触发的函数 /// private void OnBaseBPMInputFieldEndEdit(string str) { var temp = _MainMenuModel.PersistentChartData.globalConfig.baseBPM = float.Parse(str); _MainMenuView.TopPanel.StatusBar.text = "现在歌曲BaseBPM已经被更改为: " + temp.ToString() + " (ゝ∀・)"; } /// /// 当前时间被编辑时触发的函数 /// private void OnCurrentTimeInputFieldEndEdit(string str) { _MainMenuModel.SongCurrentTime = float.Parse(str); _MainMenuView.TopPanel.StatusBar.text = "已定位时间至: " + str + " (・ω・)"; } #endregion #region 进度条相关函数 /// /// 初始化进度条 /// private void InitProgressBar() { //替换进度条的贴图,并且设置为开启状态 _MainMenuView.ProgressBar.waveFormImage.texture = AudioWaveFormVisualization.BakeAudioWaveform(_MainMenuModel.Song.clip); _MainMenuView.ProgressBar.waveFormImage.gameObject.SetActive(true); _MainMenuView.ProgressBar.OnDrag += () => { //拖到尾时退出 if (_MainMenuView.ProgressBar.progressSlider.value == 1) { return; } //同步歌曲时间 _MainMenuModel.SongCurrentTime = _MainMenuView.ProgressBar.progressSlider.value * _MainMenuModel.Song.clip.length; OnTimeChange?.Invoke(_MainMenuModel.SongCurrentTime); }; } /// /// 预览谱面,按下空格开启,抬起空格时间回归原位 /// private IEnumerator ChartPreview(float startTime) { IEnumerator Play() { while (!Input.GetKeyUp(KeyCode.Space)) { if (!_MainMenuView.ProgressBar.isDrag) { //_MainMenuView.ProgressBar.progressSlider.value = _MainMenuModel.Song.time / _MainMenuModel.Song.clip.length; OnTimeChange?.Invoke(_MainMenuModel.SongCurrentTime); } OnTimeChange?.Invoke(_MainMenuModel.SongCurrentTime); yield return null; } _MainMenuModel.Song.Pause(); } yield return Play(); //回到开启的时间 _MainMenuModel.SongCurrentTime = startTime; OnTimeChange?.Invoke(_MainMenuModel.SongCurrentTime); } #endregion /// /// 监听用户的输入 /// private void CheckUserInput() { //之后需要更改为事件系统 if (Input.GetMouseButtonDown(0))//鼠标左键 { OnLeftClickCreateNewNote(_preGeneratedObject); } if (Input.GetMouseButtonDown(1))//鼠标右键 { OnRightClickBuildingOptionPanelBehavior(); OnRightClickGeneralOptionPanelBehavior(); } if (Input.GetMouseButtonDown(2))//鼠标中键 { print("鼠标中键被按下!"); } if (Input.GetMouseButtonDown(3))//鼠标侧键 { print("鼠标侧键被按下!"); } if (Input.GetKeyDown(KeyCode.Space))//空格键 { _MainMenuModel.Song.Play(); StartCoroutine(ChartPreview(_MainMenuModel.SongCurrentTime)); } if (Input.GetKeyDown(KeyCode.Escape))//ESC键 { if (isBuildingModelEnabled) { isBuildingModelEnabled = false; isAnyModelEnabled = false; _MainMenuView.TopPanel.StatusBar.text = "创建模式已退出 (ノ>ω<)ノ"; CloseBuildingOptionPanel(); }//使用ESC退出建造模式 } if (Input.GetKeyDown(KeyCode.F)) { ReadFile(); } } #region 文件读写 /// /// 文件写入操作 /// private void WriteFile(string fileName, string content) { // 构建完整路径(推荐使用持久化数据路径) string filePath = StandaloneFileBrowser.SaveFilePanel("选择文件夹", Application.persistentDataPath, fileName, ""); try { // 创建目录(如果不存在) Directory.CreateDirectory(Path.GetDirectoryName(filePath)); // 写入文件 File.WriteAllText(filePath, content); _MainMenuView.TopPanel.StatusBar.text = "文件写入成功: (σ′▽‵)′▽‵)σ " + filePath; } catch (System.Exception e) { _MainMenuView.TopPanel.StatusBar.text = "写入文件失败: (´;ω;`)" + e.Message; } CloseGeneralOptionPanel(); } /// /// 选择谱面文件,读取 /// private void ReadFile() { string[] filePath = StandaloneFileBrowser.OpenFolderPanel("选择文件夹", Application.persistentDataPath, false); try { _MainMenuModel.ReadChartByPath(filePath[0] + "\\Chart.json"); } catch (System.NullReferenceException e) { _MainMenuView.TopPanel.StatusBar.text = "空引用异常!" + e.Message; return; } catch (System.IndexOutOfRangeException e) { _MainMenuView.TopPanel.StatusBar.text = "读取已取消 " + e.Message; return; } } #endregion #region 创建实例 /// /// 创建新的note实例的函数 /// private void CreateNewNote(Vector3 mouseWorldPos, NoteController note, int timingGroup) { _MainMenuView.TopPanel.StatusBar.text = "已创建 " + note.gameObject.name; var selfRef = _MainMenuModel.CreateNewNoteDataItem(_MainMenuModel.SongCurrentTime, mouseWorldPos.x, mouseWorldPos.y, timingGroup); var Go = _MainMenuView.NotePool.Get(note); Go.Init( new Vector2( mouseWorldPos.x, mouseWorldPos.y), _MainMenuModel.SongCurrentTime, _SongTimeContainer, _MainMenuModel.MainBPMGroup); Go.SelfRef = selfRef; } /// /// 创建新的drag实例的函数 /// private void CreateNewDrag(Vector3 mouseWorldPos, DragController drag, int timingGroup) { _MainMenuView.TopPanel.StatusBar.text = "已创建 " + drag.gameObject.name; var selfRef = _MainMenuModel.CreateNewDragDataItem(_MainMenuModel.SongCurrentTime, mouseWorldPos.x, mouseWorldPos.y, timingGroup); var Go = _MainMenuView.NotePool.Get(drag); Go.Init( new Vector2( mouseWorldPos.x, mouseWorldPos.y), _MainMenuModel.SongCurrentTime, _SongTimeContainer, _MainMenuModel.MainBPMGroup); Go.SelfRef = selfRef; } /// /// 创建新的Flick实例的函数 /// private IEnumerator CreateNewFlick(Vector3 mouseWorldPos, FlickController flick, int timingGroup) { _MainMenuView.TopPanel.StatusBar.text = "当前位置信息已录入,请将鼠标移动至四周点击确定方向,原地点击不选择方向,右键取消创建"; FlickDirection direction = FlickDirection.Any; Vector3 currentMousePos = new Vector3(); IEnumerator WaitSecondClick() { yield return null;//卡掉最开始true状态 while (!Input.GetMouseButtonDown(0)) { yield return null;//等待至左键再次按下 } currentMousePos = Camera.main.ScreenToWorldPoint( new Vector3(Input.mousePosition.x, Input.mousePosition.y, 8));//这里获取鼠标目前的位置 } yield return WaitSecondClick(); isAnyCoroutineEnabled = false; //这里开始依据所得坐标获取向量以确定位置 var tempVector = currentMousePos - mouseWorldPos; float sqrMagnitude = tempVector.sqrMagnitude; // 模平方小于等于1视为任意方向 if (sqrMagnitude <= 1f) { direction = FlickDirection.Any; } else { // 计算向量角度(弧度转角度) float angle = Mathf.Atan2(tempVector.y, tempVector.x) * Mathf.Rad2Deg; // 转换为0-360度范围 if (angle < 0) angle += 360f; // 划分8个45度区域并添加22.5度偏移使区域居中 int sector = (int)((angle + 22.5f) / 45f) % 8; // 映射到方向枚举 direction = sector switch { 0 => FlickDirection.Right, 1 => FlickDirection.RightUp, 2 => FlickDirection.Up, 3 => FlickDirection.LeftUp, 4 => FlickDirection.Left, 5 => FlickDirection.LeftDown, 6 => FlickDirection.Down, 7 => FlickDirection.RightDown, _ => FlickDirection.Any // 安全后备 }; } _MainMenuView.TopPanel.StatusBar.text = "第一次点击位置:" + mouseWorldPos.ToString() + "第二次点击位置" + currentMousePos.ToString() + " 结果为:" + direction; var selfRef = _MainMenuModel.CreateNewFlickDataItem(_MainMenuModel.SongCurrentTime, mouseWorldPos.x, mouseWorldPos.y, timingGroup, direction); var Go = _MainMenuView.NotePool.Get(flick); //初始化一个实例 Go.Init( new Vector2( mouseWorldPos.x, mouseWorldPos.y), _MainMenuModel.SongCurrentTime, _SongTimeContainer, direction, _MainMenuView.FlickSpritePairs, _MainMenuModel.MainBPMGroup ); Go.SelfRef = selfRef; } private IEnumerator CreateNewDataFlow(Vector3 mouseWorldPos, DataFlowController dataFlow, int timingGroup) { ReferencePointLocation reference = ReferencePointLocation.Near; float startTime = _MainMenuModel.SongCurrentTime; float endTime=0; _MainMenuView.TopPanel.StatusBar.text = "当前位置时间信息已录入,请选择第二个位置,右键取消创建"; Vector3 currentMousePos = new Vector3(); //这里开始进行位置选择逻辑 IEnumerator WaitSecondClick() { yield return null;//卡掉最开始true状态 while (!Input.GetMouseButtonDown(0)) { yield return null;//等待至左键再次按下 } currentMousePos = Camera.main.ScreenToWorldPoint( new Vector3( Input.mousePosition.x, Input.mousePosition.y, 8));//这里获取鼠标目前的位置 endTime = _MainMenuModel.SongCurrentTime; } yield return WaitSecondClick(); _MainMenuView.TopPanel.StatusBar.text = "按[1-6]数字按键确定样式,[Near, Far, RightTop, RightBottom, LeftTop, LeftBottom]"; //这里开始类型选择逻辑 IEnumerator WaitNumKeyDown() { yield return null; bool isReferenceSelected = false; while (!(Input.GetMouseButtonDown(0) && isReferenceSelected)) { if (Input.GetKeyDown(KeyCode.Alpha1)|| Input.GetKeyDown(KeyCode.Keypad1)) { reference = ReferencePointLocation.Near; isReferenceSelected = true; _MainMenuView.TopPanel.StatusBar.text = $"当前位置为{reference},[Near, Far, RightTop, RightBottom, LeftTop, LeftBottom],再次单击确定选择"; } if (Input.GetKeyDown(KeyCode.Alpha2) || Input.GetKeyDown(KeyCode.Keypad2)) { reference = ReferencePointLocation.Far; isReferenceSelected = true; _MainMenuView.TopPanel.StatusBar.text = $"当前位置为{reference},[Near, Far, RightTop, RightBottom, LeftTop, LeftBottom],再次单击确定选择"; } if (Input.GetKeyDown(KeyCode.Alpha3) || Input.GetKeyDown(KeyCode.Keypad3)) { reference = ReferencePointLocation.RightTop; isReferenceSelected = true; _MainMenuView.TopPanel.StatusBar.text = $"当前位置为{reference},[Near, Far, RightTop, RightBottom, LeftTop, LeftBottom],再次单击确定选择"; } if (Input.GetKeyDown(KeyCode.Alpha4) || Input.GetKeyDown(KeyCode.Keypad4)) { reference = ReferencePointLocation.RightBottom; isReferenceSelected = true; _MainMenuView.TopPanel.StatusBar.text = $"当前位置为{reference},[Near, Far, RightTop, RightBottom, LeftTop, LeftBottom],再次单击确定选择"; } if (Input.GetKeyDown(KeyCode.Alpha5) || Input.GetKeyDown(KeyCode.Keypad5)) { reference = ReferencePointLocation.LeftTop; isReferenceSelected = true; _MainMenuView.TopPanel.StatusBar.text = $"当前位置为{reference},[Near, Far, RightTop, RightBottom, LeftTop, LeftBottom],再次单击确定选择"; } if (Input.GetKeyDown(KeyCode.Alpha6) || Input.GetKeyDown(KeyCode.Keypad6)) { reference = ReferencePointLocation.LeftBottom; isReferenceSelected = true; _MainMenuView.TopPanel.StatusBar.text = $"当前位置为{reference},[Near, Far, RightTop, RightBottom, LeftTop, LeftBottom],再次单击确定选择"; } yield return null;//等待至左键再次按下 } } yield return WaitNumKeyDown(); isAnyCoroutineEnabled = false; var selfRef = _MainMenuModel.CreateNewSnakeDataItem( startTime, mouseWorldPos.x, mouseWorldPos.y, currentMousePos.x, currentMousePos.y, endTime, timingGroup, reference); var Go = _MainMenuView.NotePool.Get(dataFlow); Go.Init( mouseWorldPos, currentMousePos, startTime, endTime, 0.4f, _SongTimeContainer, _MainMenuModel.MainBPMGroup, reference ); Go.SelfRef = selfRef; _MainMenuView.TopPanel.StatusBar.text = "开始位置:"+ mouseWorldPos.ToString()+" 时间:"+startTime+" 结束位置:"+ currentMousePos.ToString()+" 时间:"+ endTime; } /// /// 点击左键时创建新的物件 /// private void OnLeftClickCreateNewNote(BaseNote preGeneratedObject) { if (!(isBuildingModelEnabled && !BuildingOptionPanel.gameObject.activeSelf)) return; Vector3 mousePos = Input.mousePosition; //处于创造模式下,单击左键将会创造预备创造的物件 switch (preGeneratedObject) { case NoteController: { CreateNewNote( Camera.main.ScreenToWorldPoint(new Vector3(mousePos.x, mousePos.y, 8)), _MainMenuView.NotePrefab, _MainMenuModel.MainBPMGroup.GroupNum); break; } case DragController: { CreateNewDrag( Camera.main.ScreenToWorldPoint(new Vector3(mousePos.x, mousePos.y, 8)), _MainMenuView.DragPrefab, _MainMenuModel.MainBPMGroup.GroupNum); break; } case FlickController: { if (isAnyCoroutineEnabled) break;//防止首尾嵌套 StartCoroutine( CreateNewFlick( Camera.main.ScreenToWorldPoint(new Vector3(mousePos.x, mousePos.y, 8)), _MainMenuView.FlickPrefab, _MainMenuModel.MainBPMGroup.GroupNum) ); isAnyCoroutineEnabled = true; break; } case DataFlowController: { if (isAnyCoroutineEnabled) break;//防止首尾嵌套 StartCoroutine( CreateNewDataFlow(Camera.main.ScreenToWorldPoint(new Vector3(mousePos.x, mousePos.y, 8)), _MainMenuView.DataFlowPrefab, _MainMenuModel.MainBPMGroup.GroupNum) ); isAnyCoroutineEnabled = true; break; } case null: { UnityEngine.Debug.LogError("存在空引用异常,请检查预制体是否被正常加载"); break; } default: print("存在非法输入亦或是不存在该类型"); break; } } #endregion #region 右键会话窗口 /// /// 初始化所有右键会话窗口 /// private void InitAllOptionPanel() { GeneralOptionPanel = Instantiate(_MainMenuView.GeneralOptionPanel, _Transform); BuildingOptionPanel = Instantiate(_MainMenuView.BuildingOptionPanel, _Transform); GeneralOptionPanel.gameObject.SetActive(false); BuildingOptionPanel.gameObject.SetActive(false); //鼠标右键界面初始化业务逻辑 InitGeneralOptionPanel(); InitBuildingOptionPanel(); } /// /// 初始化通常会话窗口 /// void InitGeneralOptionPanel() { GeneralOptionPanel.MouseGudies.onClick.AddListener(null); GeneralOptionPanel.BuildingModel.onClick.AddListener(OpenBuildOptionPanel); GeneralOptionPanel.EditModel.onClick.AddListener(null); GeneralOptionPanel.Output.onClick.AddListener(()=> WriteFile("Chart.json", _MainMenuModel.GetChartJson())); } /// /// 初始化建造窗口 /// void InitBuildingOptionPanel() { BuildingOptionPanel.NoteButton.onClick.AddListener(() => OnBuildingModelButtonClick(BuildingOptionPanel.NoteButton.gameObject, _MainMenuView.NotePrefab)); BuildingOptionPanel.DragButton.onClick.AddListener(() => OnBuildingModelButtonClick(BuildingOptionPanel.DragButton.gameObject, _MainMenuView.DragPrefab)); BuildingOptionPanel.FlickButton.onClick.AddListener(() => OnBuildingModelButtonClick(BuildingOptionPanel.FlickButton.gameObject, _MainMenuView.FlickPrefab)); BuildingOptionPanel.DataFlowButton.onClick.AddListener(() => OnBuildingModelButtonClick(BuildingOptionPanel.DataFlowButton.gameObject, _MainMenuView.DataFlowPrefab)); } /// /// 任意创建模式下的note按钮被点击时,替换预备放置的物件,打印输出当前按钮的信息,关闭当前窗口 /// void OnBuildingModelButtonClick(GameObject buttonObject, BaseNote preGeneratedObject) { _MainMenuView.TopPanel.StatusBar.text = buttonObject.name + "已经启用,按ESC可退出创建模式 (ゝ∀・)⌒☆"; CloseBuildingOptionPanel(); //替换预生成的物件 _preGeneratedObject = preGeneratedObject; } /// /// 开启创建模式,该模式下玩家右键将直接打开建造模式面板,自由选择对应的物件进行放置 /// void OpenBuildOptionPanel() { Vector3 mousePos = Input.mousePosition; //关闭通常会话选项的同时将开启创建模式 CloseGeneralOptionPanel(); isBuildingModelEnabled = true; isAnyModelEnabled = true; BuildingOptionPanel.gameObject.SetActive(true); BuildingOptionPanel.GetComponent().position = new Vector3(mousePos.x, mousePos.y, 8); _MainMenuView.TopPanel.StatusBar.text = "已进入创建模式,按ESC可退出创建模式 (ゝ∀・)⌒☆"; } /// /// 通过鼠标来创建创建模式会话窗口 /// void OpenBuildOptionPanelWithMouse() { Vector3 mousePos = Input.mousePosition; isAnyModelEnabled = true; BuildingOptionPanel.gameObject.SetActive(true); BuildingOptionPanel.GetComponent().position = new Vector3(mousePos.x, mousePos.y, 8); } /// /// 关闭创建模式会话窗口 /// void CloseBuildingOptionPanel() { BuildingOptionPanel.gameObject.SetActive(false); } /// /// 关闭通常会话窗口 /// void CloseGeneralOptionPanel() { //如果再次点击右键这关闭窗口 optionHasBeenCreated = false; GeneralOptionPanel.gameObject.SetActive(false); } /// /// 在右键点击时通常面板的响应 /// private void OnRightClickGeneralOptionPanelBehavior() { if (isAnyModelEnabled) return; Vector3 mousePos = Input.mousePosition; if (optionHasBeenCreated) { //当不处于任何模式之下,用户右键会直接生成通常选项 CloseGeneralOptionPanel(); } else { //激活右键窗口 optionHasBeenCreated = true; GeneralOptionPanel.gameObject.SetActive(true); GeneralOptionPanel.GetComponent().position = new Vector3(mousePos.x, mousePos.y, 8); } } /// /// 在右键按下时建造面板的响应 /// private void OnRightClickBuildingOptionPanelBehavior() { if (!isBuildingModelEnabled) return; if (BuildingOptionPanel.gameObject.activeSelf) { //假如,处于创建模式下,会话窗口存在,右键将关闭窗口 CloseBuildingOptionPanel(); } else { //但是如果窗口不存在,则直接创建一个 OpenBuildOptionPanelWithMouse(); } } #endregion }