From 557dd0e87055dfc3b9e244989cc662efef8e70ad Mon Sep 17 00:00:00 2001 From: "ALIENJACK\\alien" Date: Thu, 19 Dec 2024 20:36:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9C=AC=E5=9C=B0=E6=89=8B=E6=9F=84=E5=92=8C?= =?UTF-8?q?=E6=B8=B8=E6=88=8F=E5=86=85=E6=89=8B=E6=9F=84=E6=8F=92=E6=A7=BD?= =?UTF-8?q?=E6=8A=BD=E8=B1=A1=E6=9C=BA=E5=88=B6=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + .../Assets/Script/AppMain/Event/EEvent.cs | 5 + .../Assets/Script/AppMain/Manager/AppEmu.cs | 39 ++- .../Assets/Script/AppMain/Manager/AppRoom.cs | 20 ++ .../AppMain/NesEmulator/CoreSupporter.cs | 5 + .../NesEmulator/NesControllerMapper.cs | 266 +++++++++++------- .../Script/AppMain/NesEmulator/NesEmulator.cs | 191 ++++++------- .../Script/AppMain/UI/GamesUI/RomItem.cs | 6 +- .../Script/AppMain/UI/InGameUI/InGameUI.cs | 85 +++--- .../AppMain/UI/InGameUI/StepPerformer.cs | 2 +- .../Script/AppMain/UI/RoomUI/RoomItem.cs | 42 ++- .../Supporter/ControllerState.cs | 69 +++-- .../VirtualNes.Core/Supporter/Supporter.cs | 25 +- 13 files changed, 455 insertions(+), 302 deletions(-) diff --git a/.gitignore b/.gitignore index 7cc5f27..f3ddef2 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ /AxibugEmuOnline.Server/config.cfg /AxibugEmuOnline.Server/bin/ /AxibugEmuOnline.Client/.editorconfig +/AxibugEmuOnline.Client/*.user +/AxibugEmuOnline.Client/.idea diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Event/EEvent.cs b/AxibugEmuOnline.Client/Assets/Script/AppMain/Event/EEvent.cs index 6f8fdf1..a16f100 100644 --- a/AxibugEmuOnline.Client/Assets/Script/AppMain/Event/EEvent.cs +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Event/EEvent.cs @@ -63,5 +63,10 @@ /// 服务器等待Step更新 /// OnRoomWaitStepChange, + + /// + /// 当房间中手柄位信息发生任何变化时触发,进入房间后也应该触发 + /// + OnRoomSlotDataChanged, //todo : 实现这个事件 } } diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Manager/AppEmu.cs b/AxibugEmuOnline.Client/Assets/Script/AppMain/Manager/AppEmu.cs index 4896f54..f995068 100644 --- a/AxibugEmuOnline.Client/Assets/Script/AppMain/Manager/AppEmu.cs +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Manager/AppEmu.cs @@ -1,6 +1,7 @@ using AxibugEmuOnline.Client.ClientCore; using AxibugEmuOnline.Client.Event; using UnityEngine; +using VirtualNes.Core; namespace AxibugEmuOnline.Client.Manager { @@ -11,6 +12,9 @@ namespace AxibugEmuOnline.Client.Manager /// 但是Equals方法可以,所以,这个接口判断为空请使用Equals /// private IEmuCore m_emuCore; + + private IControllerSetuper m_controllerSetuper; + /// /// unity的c#实现有bug,以接口类型保存的monobehaviour引用,!=和==运算符没有调用到monobehaviour重写过的运算符 /// 但是Equals方法可以,所以,这个接口判断为空请使用Equals @@ -28,7 +32,7 @@ namespace AxibugEmuOnline.Client.Manager if (!m_emuCore.IsNull()) StopGame(); var roomInfo = App.roomMgr.mineRoomMiniInfo; - roomInfo.FetchRomFileInRoomInfo(EnumPlatform.NES, (room, romFile) => + roomInfo.FetchRomFileInRoomInfo(EnumPlatform.NES, (_, romFile) => { if (!romFile.RomReady) //这个rom并没有下载,所以取消进入房间 { @@ -58,6 +62,35 @@ namespace AxibugEmuOnline.Client.Manager InGameUI.Instance.Show(romFile, m_emuCore); m_emuCore.SetupScheme(); + + m_controllerSetuper = Supporter.GetControllerSetuper(); + + SetupController(); + + Eventer.Instance.RegisterEvent(EEvent.OnRoomSlotDataChanged,OnSlotDataChanged); + } + + private void OnSlotDataChanged() + { + SetupController(); + } + + private void SetupController() + { + if (!App.roomMgr.InRoom) //不在房间中,自动分配0号手柄到0号手柄位 + { + m_controllerSetuper.SetConnect(con0ToSlot: 0); + } + else //在房间中则使用服务器下发的手柄槽位信息分配本地手柄 + { + long selfUID = App.user.userdata.UID; + App.roomMgr.mineRoomMiniInfo.GetPlayerSlotIdxByUid(selfUID, 0, out var con0Slot); + App.roomMgr.mineRoomMiniInfo.GetPlayerSlotIdxByUid(selfUID, 1, out var con1Slot); + App.roomMgr.mineRoomMiniInfo.GetPlayerSlotIdxByUid(selfUID, 2, out var con2Slot); + App.roomMgr.mineRoomMiniInfo.GetPlayerSlotIdxByUid(selfUID, 3, out var con3Slot); + + m_controllerSetuper.SetConnect(con0Slot, con1Slot, con2Slot, con3Slot); + } } public void StopGame() @@ -68,6 +101,8 @@ namespace AxibugEmuOnline.Client.Manager InGameUI.Instance.Hide(); LaunchUI.Instance.ShowMainMenu(); + m_controllerSetuper = null; + Eventer.Instance.UnregisterEvent(EEvent.OnRoomSlotDataChanged,OnSlotDataChanged); } public void ResetGame() @@ -77,4 +112,4 @@ namespace AxibugEmuOnline.Client.Manager m_emuCore.DoReset(); } } -} +} \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Manager/AppRoom.cs b/AxibugEmuOnline.Client/Assets/Script/AppMain/Manager/AppRoom.cs index 5f14db9..3085daa 100644 --- a/AxibugEmuOnline.Client/Assets/Script/AppMain/Manager/AppRoom.cs +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Manager/AppRoom.cs @@ -492,6 +492,26 @@ namespace AxibugEmuOnline.Client.Manager return freeSlots.Length > 0; } + /// + /// 指定uid和该uid的本地手柄序号,获取占用的手柄位 + /// + public static bool GetPlayerSlotIdxByUid(this Protobuf_Room_MiniInfo roomMiniInfo, long uid ,int controllerIndex, out uint? slotID) + { + slotID = null; + + //controllerIndex取值返回[0,3],这个序号代表玩家本地的手柄编号 + //todo : 根据uid和controllerIndex 返回占用的位置 + + //目前未实现,所有非0号位置的手柄,都返回false + if (controllerIndex != 0) return false; + + if (roomMiniInfo.Player1UID == uid) slotID = 0; + if (roomMiniInfo.Player2UID == uid) slotID = 1; + if (roomMiniInfo.Player3UID == uid) slotID = 2; + if (roomMiniInfo.Player4UID == uid) slotID = 3; + return true; + } + /// /// 按照房间玩家下标获取昵称 /// diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/NesEmulator/CoreSupporter.cs b/AxibugEmuOnline.Client/Assets/Script/AppMain/NesEmulator/CoreSupporter.cs index 437b14f..19c381d 100644 --- a/AxibugEmuOnline.Client/Assets/Script/AppMain/NesEmulator/CoreSupporter.cs +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/NesEmulator/CoreSupporter.cs @@ -129,6 +129,11 @@ namespace AxibugEmuOnline.Client } } + public IControllerSetuper GetControllerSetuper() + { + return ControllerMapper; + } + public ControllerState FromNet(AxiReplay.ReplayStep step) { var temp = new ServerInputSnapShot(); diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/NesEmulator/NesControllerMapper.cs b/AxibugEmuOnline.Client/Assets/Script/AppMain/NesEmulator/NesControllerMapper.cs index 5a42fe7..742ac01 100644 --- a/AxibugEmuOnline.Client/Assets/Script/AppMain/NesEmulator/NesControllerMapper.cs +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/NesEmulator/NesControllerMapper.cs @@ -3,77 +3,69 @@ using VirtualNes.Core; namespace AxibugEmuOnline.Client { - public class NesControllerMapper + public class NesControllerMapper : IControllerSetuper { - public MapperSetter Player1 = new MapperSetter(1); - public MapperSetter Player2 = new MapperSetter(2); - public MapperSetter Player3 = new MapperSetter(3); - public MapperSetter Player4 = new MapperSetter(4); + public Controller Controller0 { get; } = new(0); + public Controller Controller1 { get; } = new(1); + public Controller Controller2 { get; } = new(2); + public Controller Controller3 { get; } = new(3); + + private readonly EnumButtonType[] m_states = new EnumButtonType[4]; public ControllerState CreateState() { - var state1 = Player1.GetButtons(); - var state2 = Player2.GetButtons(); - var state3 = Player3.GetButtons(); - var state4 = Player4.GetButtons(); + m_states[0] = m_states[1] = m_states[2] = m_states[3] = 0; - var result = new ControllerState(state1, state2, state3, state4); + if (Controller0.ConnectSlot.HasValue) m_states[Controller0.ConnectSlot.Value] = Controller0.GetButtons(); + if (Controller1.ConnectSlot.HasValue) m_states[Controller1.ConnectSlot.Value] = Controller1.GetButtons(); + if (Controller2.ConnectSlot.HasValue) m_states[Controller2.ConnectSlot.Value] = Controller2.GetButtons(); + if (Controller3.ConnectSlot.HasValue) m_states[Controller3.ConnectSlot.Value] = Controller3.GetButtons(); + + var result = new ControllerState(m_states); return result; } - public class Mapper + /// + /// Nes控制器 + /// + public class Controller { - MapperSetter m_setter; - EnumButtonType m_buttonType; - IKeyListener m_keyListener; - int m_controllerIndex => m_setter.ControllerIndex; - - public Mapper(MapperSetter setter, EnumButtonType buttonType) - { - m_setter = setter; - m_buttonType = buttonType; - - loadConfig(); - } - - private void loadConfig() - { - m_keyListener = MapperSetter.GetKey(m_controllerIndex, m_buttonType); - - } - - public EnumButtonType SampleKey() - { - return m_keyListener.IsPressing() ? m_buttonType : 0; - } - } - - public class MapperSetter - { - /// 控制器序号(手柄1,2,3,4) + /// + /// 控制器编号 + /// 此编号并非对应游戏中的player1,player2,player3,player4,仅仅作为本地4个手柄的实例 + /// [0,3] + /// public int ControllerIndex { get; } - public Mapper UP { get; private set; } - public Mapper DOWN { get; private set; } - public Mapper LEFT { get; private set; } - public Mapper RIGHT { get; private set; } - public Mapper A { get; private set; } - public Mapper B { get; private set; } - public Mapper SELECT { get; private set; } - public Mapper START { get; private set; } - public Mapper MIC { get; private set; } - public MapperSetter(int controllerIndex) + /// + /// 指示该手柄连接的手柄插槽 + /// 这个值代表了该手柄在实际游戏中控制的Player + /// [0,3] 例外:为空代表未连接 + /// + public uint? ConnectSlot { get; set; } + + public Button UP { get; } + public Button DOWN { get; } + public Button LEFT { get; } + public Button RIGHT { get; } + public Button A { get; } + public Button B { get; } + public Button SELECT { get; } + public Button START { get; } + public Button MIC { get; } + + public Controller(int controllerIndex) { ControllerIndex = controllerIndex; - UP = new Mapper(this, EnumButtonType.UP); - DOWN = new Mapper(this, EnumButtonType.DOWN); - LEFT = new Mapper(this, EnumButtonType.LEFT); - RIGHT = new Mapper(this, EnumButtonType.RIGHT); - A = new Mapper(this, EnumButtonType.A); - B = new Mapper(this, EnumButtonType.B); - SELECT = new Mapper(this, EnumButtonType.SELECT); - START = new Mapper(this, EnumButtonType.START); - MIC = new Mapper(this, EnumButtonType.MIC); + UP = new Button(this, EnumButtonType.UP); + DOWN = new Button(this, EnumButtonType.DOWN); + LEFT = new Button(this, EnumButtonType.LEFT); + RIGHT = new Button(this, EnumButtonType.RIGHT); + A = new Button(this, EnumButtonType.A); + B = new Button(this, EnumButtonType.B); + SELECT = new Button(this, EnumButtonType.SELECT); + START = new Button(this, EnumButtonType.START); + MIC = new Button(this, EnumButtonType.MIC); } public EnumButtonType GetButtons() @@ -93,7 +85,7 @@ namespace AxibugEmuOnline.Client return res; } - public static IKeyListener GetKey(int controllerInput, EnumButtonType nesConBtnType) + public static KeyListener GetKey(int controllerInput, EnumButtonType nesConBtnType) { string configKey = $"NES_{controllerInput}_{nesConBtnType}"; if (PlayerPrefs.HasKey(configKey)) @@ -102,62 +94,60 @@ namespace AxibugEmuOnline.Client } else { - var defaultKeyCode = GetDefaultKey(); + var defaultKeyCode = KeyListener.GetDefaultKey(controllerInput, nesConBtnType); PlayerPrefs.SetString(configKey, defaultKeyCode.ToString()); return defaultKeyCode; } - - KeyListener GetDefaultKey() - { - switch (controllerInput) - { - case 1: - if (nesConBtnType == EnumButtonType.LEFT) return new KeyListener(KeyCode.A); - if (nesConBtnType == EnumButtonType.RIGHT) return new KeyListener(KeyCode.D); - if (nesConBtnType == EnumButtonType.UP) return new KeyListener(KeyCode.W); - if (nesConBtnType == EnumButtonType.DOWN) return new KeyListener(KeyCode.S); - if (nesConBtnType == EnumButtonType.START) return new KeyListener(KeyCode.B); - if (nesConBtnType == EnumButtonType.SELECT) return new KeyListener(KeyCode.V); - if (nesConBtnType == EnumButtonType.A) return new KeyListener(KeyCode.K); - if (nesConBtnType == EnumButtonType.B) return new KeyListener(KeyCode.J); - if (nesConBtnType == EnumButtonType.MIC) return new KeyListener(KeyCode.M); - break; - case 2: - if (nesConBtnType == EnumButtonType.LEFT) return new KeyListener(KeyCode.Delete); - if (nesConBtnType == EnumButtonType.RIGHT) return new KeyListener(KeyCode.PageDown); - if (nesConBtnType == EnumButtonType.UP) return new KeyListener(KeyCode.Home); - if (nesConBtnType == EnumButtonType.DOWN) return new KeyListener(KeyCode.End); - if (nesConBtnType == EnumButtonType.START) return new KeyListener(KeyCode.PageUp); - if (nesConBtnType == EnumButtonType.SELECT) return new KeyListener(KeyCode.Insert); - if (nesConBtnType == EnumButtonType.A) return new KeyListener(KeyCode.Keypad5); - if (nesConBtnType == EnumButtonType.B) return new KeyListener(KeyCode.Keypad4); - if (nesConBtnType == EnumButtonType.MIC) return new KeyListener(KeyCode.KeypadPeriod); - break; - } - - return default; - } - } } - public interface IKeyListener + /// + /// NES控制器按键类 + /// + public class Button { - bool IsPressing(); + /// 所属控制器 + readonly Controller m_hostController; + + /// 按键 + readonly EnumButtonType m_buttonType; + + /// 按键监听器 + KeyListener m_keyListener; + + public Button(Controller controller, EnumButtonType buttonType) + { + m_hostController = controller; + m_buttonType = buttonType; + + CreateListener(); + } + + /// + /// 采集按钮按下状态 + /// + /// + public EnumButtonType SampleKey() + { + return m_keyListener.IsPressing() ? m_buttonType : 0; + } + + private void CreateListener() + { + m_keyListener = Controller.GetKey(m_hostController.ControllerIndex, m_buttonType); + } } - public struct KeyListener : IKeyListener + public readonly struct KeyListener { - private KeyCode m_key; + private readonly KeyCode m_key; public KeyListener(KeyCode key) { m_key = key; } - /// - /// 从配置表字符串构建 - /// + /// 从配置字符串构建 public KeyListener(string confStr) { m_key = KeyCode.None; @@ -166,19 +156,83 @@ namespace AxibugEmuOnline.Client m_key = (KeyCode)result; } - public readonly bool IsPressing() + public bool IsPressing() { - if (Input.GetKey(m_key)) return true; - - return false; + return Input.GetKey(m_key); } public override string ToString() { return ((int)(m_key)).ToString(); } + + public static KeyListener GetDefaultKey(int controllerIndex, EnumButtonType nesConBtnType) + { + switch (controllerIndex) + { + case 1: + switch (nesConBtnType) + { + case EnumButtonType.LEFT: + return new KeyListener(KeyCode.A); + case EnumButtonType.RIGHT: + return new KeyListener(KeyCode.D); + case EnumButtonType.UP: + return new KeyListener(KeyCode.W); + case EnumButtonType.DOWN: + return new KeyListener(KeyCode.S); + case EnumButtonType.START: + return new KeyListener(KeyCode.B); + case EnumButtonType.SELECT: + return new KeyListener(KeyCode.V); + case EnumButtonType.A: + return new KeyListener(KeyCode.K); + case EnumButtonType.B: + return new KeyListener(KeyCode.J); + case EnumButtonType.MIC: + return new KeyListener(KeyCode.M); + } + + break; + case 2: + switch (nesConBtnType) + { + case EnumButtonType.LEFT: + return new KeyListener(KeyCode.Delete); + case EnumButtonType.RIGHT: + return new KeyListener(KeyCode.PageDown); + case EnumButtonType.UP: + return new KeyListener(KeyCode.Home); + case EnumButtonType.DOWN: + return new KeyListener(KeyCode.End); + case EnumButtonType.START: + return new KeyListener(KeyCode.PageUp); + case EnumButtonType.SELECT: + return new KeyListener(KeyCode.Insert); + case EnumButtonType.A: + return new KeyListener(KeyCode.Keypad5); + case EnumButtonType.B: + return new KeyListener(KeyCode.Keypad4); + case EnumButtonType.MIC: + return new KeyListener(KeyCode.KeypadPeriod); + } + + break; + } + + return default; + } + } + + public void SetConnect(uint? con0ToSlot = null, + uint? con1ToSlot = null, + uint? con2ToSlot = null, + uint? con3ToSlot = null) + { + Controller0.ConnectSlot = con0ToSlot; + Controller1.ConnectSlot = con1ToSlot; + Controller2.ConnectSlot = con2ToSlot; + Controller3.ConnectSlot = con3ToSlot; } } - - -} +} \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/NesEmulator/NesEmulator.cs b/AxibugEmuOnline.Client/Assets/Script/AppMain/NesEmulator/NesEmulator.cs index ace0654..085be65 100644 --- a/AxibugEmuOnline.Client/Assets/Script/AppMain/NesEmulator/NesEmulator.cs +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/NesEmulator/NesEmulator.cs @@ -1,44 +1,61 @@ -using AxibugEmuOnline.Client.ClientCore; -using System; +using System; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Xml.Linq; +using AxibugEmuOnline.Client.ClientCore; +using UnityEditor; using UnityEngine; using VirtualNes.Core; using VirtualNes.Core.Debug; +using Debug = System.Diagnostics.Debug; namespace AxibugEmuOnline.Client { public class NesEmulator : MonoBehaviour, IEmuCore { - public EnumPlatform Platform => EnumPlatform.NES; - - //ģʵ + public VideoProvider VideoProvider; + public AudioProvider AudioProvider; + + //模拟器核心实例化对象 public NES NesCore { get; private set; } - //ƵUnityģݵIJȾʵ֣ - public VideoProvider VideoProvider; - //ƵUnityģƵݵIJųʵ֣ - public AudioProvider AudioProvider; - //Ƿͣ - private bool m_bPause; - /// Ƿͣ - public bool IsPause => m_bPause; + /// 是否暂停 + public bool IsPause { get; private set; } private void Start() { - //رմֱͬ + //关闭垂直同步 QualitySettings.vSyncCount = 0; - //Ϊ60֡ + //设为60帧 Application.targetFrameRate = 60; VideoProvider.NesEmu = this; AudioProvider.NesEmu = this; } /// - /// ָROMʼϷ + /// Unity的逐帧驱动 + /// + private unsafe void Update() + { + if (IsPause) return; + + if (NesCore != null) + { + PushEmulatorFrame(); + if (InGameUI.Instance.IsNetPlay) + FixEmulatorFrame(); + + var screenBuffer = NesCore.ppu.GetScreenPtr(); + VideoProvider.SetDrawData(screenBuffer); + } + } + + public EnumPlatform Platform => EnumPlatform.NES; + + /// + /// 指定ROM开始游戏 /// - /// public void StartGame(RomFile rom) { StopGame(); @@ -59,78 +76,14 @@ namespace AxibugEmuOnline.Client } } - /// - /// ֹͣϷ - /// - public void StopGame() - { - NesCore?.Dispose(); - NesCore = null; - } - - /// - /// Unity֡ - /// - private unsafe void Update() - { - if (m_bPause) return; - - if (NesCore != null) - { - PushEmulatorFrame(); - if (InGameUI.Instance.IsNetPlay) - FixEmulatorFrame(); - - var screenBuffer = NesCore.ppu.GetScreenPtr(); - VideoProvider.SetDrawData(screenBuffer); - } - } - - //Ƿ֡Ч - private void FixEmulatorFrame() - { - var skipFrameCount = App.roomMgr.netReplay.GetSkipFrameCount(); - - if (skipFrameCount > 0) App.log.Debug($"SKIP FRAME : {skipFrameCount}"); - for (int i = 0; i < skipFrameCount; i++) - { - if (!PushEmulatorFrame()) break; - } - } - - ControllerState lastState; - //ƽ֡ - private bool PushEmulatorFrame() - { - Supporter.SampleInput(NesCore.FrameCount); - var controlState = Supporter.GetControllerState(); - - //δյInput,֡ƽ - if (!controlState.valid) return false; - -#if UNITY_EDITOR - if (controlState != lastState) - { - App.log.Info($"[LOCALDEBUG]{NesCore.FrameCount}-->{controlState}"); - } -#endif - - NesCore.pad.Sync(controlState); - NesCore.EmulateFrame(true); - - lastState = controlState; - - return true; - } - public void Pause() { - m_bPause = true; + IsPause = true; } public void Resume() { - m_bPause = false; + IsPause = false; } @@ -155,7 +108,7 @@ namespace AxibugEmuOnline.Client } /// - /// ȡʱ浵 + /// 获取即时存档 /// /// public byte[] GetStateBytes() @@ -164,21 +117,68 @@ namespace AxibugEmuOnline.Client } /// - /// ؼʱ浵 + /// 加载即时存档 /// - /// + /// + /// public void LoadStateFromBytes(byte[] data) { - State st = new State(); + var st = new State(); st.FromByte(data); NesCore.LoadState(st); } public uint Frame => NesCore.FrameCount; + /// + /// 停止游戏 + /// + public void StopGame() + { + NesCore?.Dispose(); + NesCore = null; + } + + +#if UNITY_EDITOR + private ControllerState m_lastState; +#endif + //是否跳帧,单机无效 + private void FixEmulatorFrame() + { + var skipFrameCount = App.roomMgr.netReplay.GetSkipFrameCount(); + + if (skipFrameCount > 0) App.log.Debug($"SKIP FRAME : {skipFrameCount}"); + for (var i = 0; i < skipFrameCount; i++) + if (!PushEmulatorFrame()) + break; + } + + //推进帧 + private bool PushEmulatorFrame() + { + Supporter.SampleInput(NesCore.FrameCount); + var controlState = Supporter.GetControllerState(); + + //如果未收到Input数据,核心帧不推进 + if (!controlState.valid) return false; + +#if UNITY_EDITOR + if (controlState != m_lastState) App.log.Info($"[LOCALDEBUG]{NesCore.FrameCount}-->{controlState}"); + m_lastState = controlState; +#endif + + NesCore.pad.Sync(controlState); + NesCore.EmulateFrame(true); + + + return true; + } + #if UNITY_EDITOR /// - /// ༭ + /// 编辑器用 /// [Conditional("UNITY_EDITOR")] [ContextMenu("ImportNesDB")] @@ -189,22 +189,23 @@ namespace AxibugEmuOnline.Client var xmlStr = File.ReadAllText("nes20db.xml"); var xml = XDocument.Parse(xmlStr); - var games = xml.Element("nes20db").Elements("game"); + var games = xml.Element("nes20db")?.Elements("game"); + Debug.Assert(games != null, nameof(games) + " != null"); foreach (var game in games) { - var crcStr = game.Element("rom").Attribute("crc32").Value; - var crc = uint.Parse($"{crcStr}", System.Globalization.NumberStyles.HexNumber); + var crcStr = game.Element("rom")?.Attribute("crc32")?.Value; + var crc = uint.Parse($"{crcStr}", NumberStyles.HexNumber); - var mapper = int.Parse($"{game.Element("pcb").Attribute("mapper").Value}"); + var mapper = int.Parse($"{game.Element("pcb")?.Attribute("mapper")?.Value}"); if (mapper > 255) continue; db.AddInfo(new RomDB.RomInfo { CRC = crc, Mapper = mapper }); } - UnityEditor.EditorUtility.SetDirty(db); - UnityEditor.AssetDatabase.SaveAssets(); + EditorUtility.SetDirty(db); + AssetDatabase.SaveAssets(); } #endif } -} +} \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/UI/GamesUI/RomItem.cs b/AxibugEmuOnline.Client/Assets/Script/AppMain/UI/GamesUI/RomItem.cs index 65a339d..e047221 100644 --- a/AxibugEmuOnline.Client/Assets/Script/AppMain/UI/GamesUI/RomItem.cs +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/UI/GamesUI/RomItem.cs @@ -1,4 +1,4 @@ -using AxibugEmuOnline.Client.ClientCore; +using AxibugEmuOnline.Client.ClientCore; using AxibugEmuOnline.Client.UI; using UnityEngine; using UnityEngine.UI; @@ -59,7 +59,7 @@ namespace AxibugEmuOnline.Client { if (!m_romfile.InfoReady) { - SetBaseInfo("ȡ", "---", "---"); + SetBaseInfo("正在拉取", "---", "---"); } else { @@ -82,8 +82,6 @@ namespace AxibugEmuOnline.Client } else { - //һûԼṩRomʱ,ʹ - //App.emu.BeginGame(App.nesRomLib.GetExistRom("bad_apple_2_5.nes")); App.emu.BeginGame(m_romfile); return false; diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/UI/InGameUI/InGameUI.cs b/AxibugEmuOnline.Client/Assets/Script/AppMain/UI/InGameUI/InGameUI.cs index 743ee71..697f4c3 100644 --- a/AxibugEmuOnline.Client/Assets/Script/AppMain/UI/InGameUI/InGameUI.cs +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/UI/InGameUI/InGameUI.cs @@ -1,15 +1,21 @@ -using AxibugEmuOnline.Client.ClientCore; +using System.Collections.Generic; +using AxibugEmuOnline.Client.ClientCore; using AxibugEmuOnline.Client.Event; -using System; -using System.Collections.Generic; +using AxibugProtobuf; namespace AxibugEmuOnline.Client { public class InGameUI : CommandExecuter { + private bool m_delayCreateRoom; + private object m_state; + private StepPerformer m_stepPerformer; + + private readonly List menus = new(); public static InGameUI Instance { get; private set; } - public RomFile RomFile => m_rom; + public RomFile RomFile { get; private set; } + public override bool Enable => gameObject.activeInHierarchy; /// 指示该游戏实例是否处于联机模式 @@ -19,18 +25,13 @@ namespace AxibugEmuOnline.Client { if (!App.user.IsLoggedIn) return false; if (App.roomMgr.mineRoomMiniInfo == null) return false; - if (App.roomMgr.RoomState <= AxibugProtobuf.RoomGameState.OnlyHost) return false; + if (App.roomMgr.RoomState <= RoomGameState.OnlyHost) return false; return true; } } - private RomFile m_rom; public IEmuCore Core { get; private set; } - private object m_state; - - private List menus = new List(); - private StepPerformer m_stepPerformer; protected override void Awake() { @@ -59,36 +60,36 @@ namespace AxibugEmuOnline.Client { m_state = state; } + /// - /// 读取快速快照 + /// 读取快速快照 /// - /// - /// public object GetQuickState() { return m_state; } - private bool m_delayCreateRoom; public void Show(RomFile currentRom, IEmuCore core) { m_delayCreateRoom = false; - m_state = null;//清空游戏快照 + m_state = null; //清空游戏快照 CommandDispatcher.Instance.RegistController(this); - m_rom = currentRom; + RomFile = currentRom; Core = core; m_stepPerformer.Reset(); - if (!App.roomMgr.InRoom) - { - if (App.user.IsLoggedIn) - App.roomMgr.SendCreateRoom(m_rom.ID, 0, m_rom.Hash); - else - { - m_delayCreateRoom = true; - OverlayManager.PopTip("稍后将会建立房间"); - } + if (!App.roomMgr.InRoom) + { + if (App.user.IsLoggedIn) + { + App.roomMgr.SendCreateRoom(RomFile.ID, 0, RomFile.Hash); + } + else + { + m_delayCreateRoom = true; + OverlayManager.PopTip("稍后将会建立房间"); + } } Eventer.Instance.RegisterEvent(EEvent.OnLoginSucceed, OnLoggedIn); @@ -106,21 +107,18 @@ namespace AxibugEmuOnline.Client filter.ApplyPreset(preset); App.filter.EnableFilter(filter); } - } - - private void OnRoomCreated() - { - m_delayCreateRoom = false; - } - - private void OnLoggedIn() - { - if (m_delayCreateRoom) - { - App.roomMgr.SendCreateRoom(m_rom.ID, 0, m_rom.Hash); - } - } - + } + + private void OnRoomCreated() + { + m_delayCreateRoom = false; + } + + private void OnLoggedIn() + { + if (m_delayCreateRoom) App.roomMgr.SendCreateRoom(RomFile.ID, 0, RomFile.Hash); + } + private void OnServerStepUpdate(int step) { m_stepPerformer.Perform(step); @@ -138,16 +136,17 @@ namespace AxibugEmuOnline.Client { OverlayManager.PopSideBar(menus, 0, PopMenu_OnHide); - if (!IsNetPlay)//单人模式暂停模拟器 + if (!IsNetPlay) //单人模式暂停模拟器 Core.Pause(); } //菜单关闭时候 private void PopMenu_OnHide() { - if (!IsNetPlay)//单人模式恢复模拟器的暂停 + if (!IsNetPlay) //单人模式恢复模拟器的暂停 Core.Resume(); } + public void QuitGame() { Eventer.Instance.UnregisterEvent(EEvent.OnRoomWaitStepChange, OnServerStepUpdate); @@ -157,4 +156,4 @@ namespace AxibugEmuOnline.Client App.emu.StopGame(); } } -} +} \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/UI/InGameUI/StepPerformer.cs b/AxibugEmuOnline.Client/Assets/Script/AppMain/UI/InGameUI/StepPerformer.cs index 5a5372e..039790b 100644 --- a/AxibugEmuOnline.Client/Assets/Script/AppMain/UI/InGameUI/StepPerformer.cs +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/UI/InGameUI/StepPerformer.cs @@ -6,7 +6,7 @@ namespace AxibugEmuOnline.Client { public class StepPerformer { - private InGameUI m_inGameUI; + private readonly InGameUI m_inGameUI; private int m_step = -1; public StepPerformer(InGameUI inGameUI) diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/UI/RoomUI/RoomItem.cs b/AxibugEmuOnline.Client/Assets/Script/AppMain/UI/RoomUI/RoomItem.cs index 63e312d..2766162 100644 --- a/AxibugEmuOnline.Client/Assets/Script/AppMain/UI/RoomUI/RoomItem.cs +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/UI/RoomUI/RoomItem.cs @@ -1,28 +1,25 @@ -using AxibugEmuOnline.Client.ClientCore; +using AxibugEmuOnline.Client.ClientCore; using AxibugEmuOnline.Client.Event; using AxibugEmuOnline.Client.Manager; using AxibugEmuOnline.Client.UI; using AxibugProtobuf; using UnityEngine; using UnityEngine.UI; +using Debug = System.Diagnostics.Debug; namespace AxibugEmuOnline.Client { public class RoomItem : MenuItem, IVirtualItem { - [SerializeField] - Image m_roomPreview; - [SerializeField] - Slider m_downloadProgress; - [SerializeField] - GameObject m_downloadingFlag; - [SerializeField] - GameObject m_romReadyFlag; + [SerializeField] Image m_roomPreview; + [SerializeField] Slider m_downloadProgress; + [SerializeField] GameObject m_downloadingFlag; + [SerializeField] GameObject m_romReadyFlag; private RomFile m_romFile; public int Index { get; set; } - public int roomID { get; private set; } + public int RoomID { get; private set; } protected override void Awake() { @@ -33,7 +30,7 @@ namespace AxibugEmuOnline.Client private void OnRoomSignelUpdate(int roomID) { - if (this.roomID != roomID) return; + if (this.RoomID != roomID) return; if (App.roomMgr.GetRoomListMiniInfo(roomID, out var roomInfo)) UpdateUI(roomInfo); @@ -41,8 +38,10 @@ namespace AxibugEmuOnline.Client public void SetData(object data) { - var roomInfo = data as Protobuf_Room_MiniInfo; - roomID = roomInfo.RoomID; + Debug.Assert(data is Protobuf_Room_MiniInfo); + + var roomInfo = (Protobuf_Room_MiniInfo)data; + RoomID = roomInfo.RoomID; UpdateUI(roomInfo); } @@ -58,20 +57,19 @@ namespace AxibugEmuOnline.Client } else { - if (!App.roomMgr.GetRoomListMiniInfo(roomID, out Protobuf_Room_MiniInfo MiniInfo)) + if (!App.roomMgr.GetRoomListMiniInfo(RoomID, out Protobuf_Room_MiniInfo MiniInfo)) { - OverlayManager.PopTip("䲻"); + OverlayManager.PopTip("房间不存在"); return false; } - int[] freeSlots = null; - if (!MiniInfo.GetFreeSlot(out freeSlots)) + if (!MiniInfo.GetFreeSlot(out var freeSlots)) { - OverlayManager.PopTip("޿λ"); + OverlayManager.PopTip("无空闲位置"); return false; } - App.roomMgr.SendJoinRoom(roomID, freeSlots[0]); + App.roomMgr.SendJoinRoom(RoomID, freeSlots[0]); return true; } } @@ -80,12 +78,12 @@ namespace AxibugEmuOnline.Client { var hostNick = roomInfo.GetHostNickName(); roomInfo.GetRoomPlayers(out var cur, out var max); - SetBaseInfo("--", $"{hostNick}ķ", $"{cur}/{max}"); + SetBaseInfo("--", $"{hostNick}的房间", $"{cur}/{max}"); SetIcon(null); roomInfo.FetchRomFileInRoomInfo(EnumPlatform.NES, (room, romFile) => { - if (room.RoomID != roomID) return; + if (room.RoomID != RoomID) return; m_romFile = romFile; Txt.text = romFile.Alias; @@ -139,4 +137,4 @@ namespace AxibugEmuOnline.Client Reset(); } } -} +} \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Supporter/ControllerState.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Supporter/ControllerState.cs index d5e04fa..22b7bcc 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Supporter/ControllerState.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Supporter/ControllerState.cs @@ -2,7 +2,7 @@ namespace VirtualNes.Core { - public struct ControllerState + public struct ControllerState : IEquatable { public uint raw0; public uint raw1; @@ -11,19 +11,49 @@ namespace VirtualNes.Core public bool valid; - public ControllerState( - EnumButtonType player0_buttons, - EnumButtonType player1_buttons, - EnumButtonType player2_buttons, - EnumButtonType player3_buttons) + public ControllerState(EnumButtonType[] states) { - raw0 = (uint)player0_buttons; - raw1 = (uint)player1_buttons; - raw2 = (uint)player2_buttons; - raw3 = (uint)player3_buttons; + raw0 = (uint)states[0]; + raw1 = (uint)states[1]; + raw2 = (uint)states[2]; + raw3 = (uint)states[3]; valid = true; } + + public bool HasButton(int player, EnumButtonType button) + { + uint raw = player switch + { + 0 => raw0, + 1 => raw1, + 2 => raw2, + 3 => raw3, + _ => 0 + }; + return (raw & (uint)button) == (uint)button; + } + public override string ToString() + { + return $"{raw0}|{raw1}|{raw2}|{raw3}"; + } + + #region Impl_Equals + public bool Equals(ControllerState other) + { + return raw0 == other.raw0 && raw1 == other.raw1 && raw2 == other.raw2 && raw3 == other.raw3 && valid == other.valid; + } + + public override bool Equals(object obj) + { + return obj is ControllerState other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(raw0, raw1, raw2, raw3, valid); + } + public static bool operator ==(ControllerState left, ControllerState right) { return @@ -37,24 +67,7 @@ namespace VirtualNes.Core { return !(left == right); } - - public override string ToString() - { - return $"{raw0}|{raw1}|{raw2}|{raw3}"; - } - - public bool HasButton(int player, EnumButtonType button) - { - uint raw = 0; - switch (player) - { - case 0: raw = raw0; break; - case 1: raw = raw1; break; - case 2: raw = raw2; break; - case 3: raw = raw3; break; - } - return (raw & (uint)button) == (uint)button; - } + #endregion } [Flags] diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Supporter/Supporter.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Supporter/Supporter.cs index 393210d..3701159 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Supporter/Supporter.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Supporter/Supporter.cs @@ -5,6 +5,7 @@ namespace VirtualNes.Core public static class Supporter { private static ISupporterImpl s_support; + public static void Setup(ISupporterImpl supporter) { s_support = supporter; @@ -44,6 +45,7 @@ namespace VirtualNes.Core { s_support.SaveFile(fileData, directPath, fileName); } + public static Stream OpenFile(string directPath, string fileName) { return s_support.OpenFile(directPath, fileName); @@ -64,6 +66,11 @@ namespace VirtualNes.Core s_support.SampleInput(frameCount); } + public static IControllerSetuper GetControllerSetuper() + { + return s_support.GetControllerSetuper(); + } + public static EmulatorConfig Config => s_support.Config; } @@ -82,5 +89,21 @@ namespace VirtualNes.Core bool TryGetMapperNo(ROM rom, out int mapperNo); ControllerState GetControllerState(); void SampleInput(uint frameCount); + IControllerSetuper GetControllerSetuper(); } -} + + /// + /// 负责管理本地控制器与具体游戏之间的槽位分配 + /// + public interface IControllerSetuper + { + /// + /// 设置本地手柄与游戏手柄槽位的映射 + /// + void SetConnect( + uint? con0ToSlot = null, + uint? con1ToSlot = null, + uint? con2ToSlot = null, + uint? con3ToSlot = null); + } +} \ No newline at end of file