Merge pull request 'master' (#67) from Alienjack/AxibugEmuOnline:master into master

Reviewed-on: sin365/AxibugEmuOnline#67
This commit is contained in:
sin365 2024-12-19 20:46:38 +08:00
commit 417390227c
13 changed files with 455 additions and 302 deletions

2
.gitignore vendored
View File

@ -18,3 +18,5 @@
/AxibugEmuOnline.Server/config.cfg
/AxibugEmuOnline.Server/bin/
/AxibugEmuOnline.Client/.editorconfig
/AxibugEmuOnline.Client/*.user
/AxibugEmuOnline.Client/.idea

View File

@ -63,5 +63,10 @@
/// 服务器等待Step更新
/// </summary>
OnRoomWaitStepChange,
/// <summary>
/// 当房间中手柄位信息发生任何变化时触发,进入房间后也应该触发
/// </summary>
OnRoomSlotDataChanged, //todo : 实现这个事件
}
}

View File

@ -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
/// </summary>
private IEmuCore m_emuCore;
private IControllerSetuper m_controllerSetuper;
/// <summary>
/// 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()

View File

@ -492,6 +492,26 @@ namespace AxibugEmuOnline.Client.Manager
return freeSlots.Length > 0;
}
/// <summary>
/// 指定uid和该uid的本地手柄序号,获取占用的手柄位
/// </summary>
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;
}
/// <summary>
/// 按照房间玩家下标获取昵称
/// </summary>

View File

@ -129,6 +129,11 @@ namespace AxibugEmuOnline.Client
}
}
public IControllerSetuper GetControllerSetuper()
{
return ControllerMapper;
}
public ControllerState FromNet(AxiReplay.ReplayStep step)
{
var temp = new ServerInputSnapShot();

View File

@ -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
/// <summary>
/// Nes控制器
/// </summary>
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
{
/// <summary> 控制器序号(手柄1,2,3,4) </summary>
/// <summary>
/// 控制器编号
/// <para><c>此编号并非对应游戏中的player1,player2,player3,player4,仅仅作为本地4个手柄的实例</c></para>
/// <value>[0,3]</value>
/// </summary>
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)
/// <summary>
/// 指示该手柄连接的手柄插槽
/// <para><c>这个值代表了该手柄在实际游戏中控制的Player</c></para>
/// <value>[0,3] 例外:为空代表未连接</value>
/// </summary>
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()
/// <summary>
/// NES控制器按键类
/// </summary>
public class Button
{
switch (controllerInput)
/// <summary> 所属控制器 </summary>
readonly Controller m_hostController;
/// <summary> 按键 </summary>
readonly EnumButtonType m_buttonType;
/// <summary> 按键监听器 </summary>
KeyListener m_keyListener;
public Button(Controller controller, EnumButtonType buttonType)
{
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;
m_hostController = controller;
m_buttonType = buttonType;
CreateListener();
}
return default;
}
}
}
public interface IKeyListener
/// <summary>
/// 采集按钮按下状态
/// </summary>
/// <returns></returns>
public EnumButtonType SampleKey()
{
bool IsPressing();
return m_keyListener.IsPressing() ? m_buttonType : 0;
}
public struct KeyListener : IKeyListener
private void CreateListener()
{
private KeyCode m_key;
m_keyListener = Controller.GetKey(m_hostController.ControllerIndex, m_buttonType);
}
}
public readonly struct KeyListener
{
private readonly KeyCode m_key;
public KeyListener(KeyCode key)
{
m_key = key;
}
/// <summary>
/// 从配置表字符串构建
/// </summary>
/// <summary> 从配置字符串构建 </summary>
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;
}
}
}

View File

@ -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接收模拟器画面数据的并渲染出来的实现
public VideoProvider VideoProvider;
//音频驱动这里是Unity接收模拟器音频数据的并播放出来的实现
public AudioProvider AudioProvider;
//是否暂停
private bool m_bPause;
/// <summary> 是否暂停 </summary>
public bool IsPause => m_bPause;
/// <summary> 是否暂停 </summary>
public bool IsPause { get; private set; }
private void Start()
{
//关闭垂直同步
//关闭垂直同步
QualitySettings.vSyncCount = 0;
//设为60帧
//设为60帧
Application.targetFrameRate = 60;
VideoProvider.NesEmu = this;
AudioProvider.NesEmu = this;
}
/// <summary>
/// 指定ROM开始游戏
/// Unity的逐帧驱动
/// </summary>
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;
/// <summary>
/// 指定ROM开始游戏
/// </summary>
/// <param name="rom"></param>
public void StartGame(RomFile rom)
{
StopGame();
@ -59,78 +76,14 @@ namespace AxibugEmuOnline.Client
}
}
/// <summary>
/// 停止游戏
/// </summary>
public void StopGame()
{
NesCore?.Dispose();
NesCore = null;
}
/// <summary>
/// Unity的逐帧驱动
/// </summary>
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
}
/// <summary>
/// 获取即时存档
/// 获取即时存档
/// </summary>
/// <returns></returns>
public byte[] GetStateBytes()
@ -164,21 +117,68 @@ namespace AxibugEmuOnline.Client
}
/// <summary>
/// 加载即时存档
/// 加载即时存档
/// </summary>
/// <param name="data"></param>
/// <param
/// name="data">
/// </param>
public void LoadStateFromBytes(byte[] data)
{
State st = new State();
var st = new State();
st.FromByte(data);
NesCore.LoadState(st);
}
public uint Frame => NesCore.FrameCount;
/// <summary>
/// 停止游戏
/// </summary>
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
/// <summary>
/// 编辑器用
/// 编辑器用
/// </summary>
[Conditional("UNITY_EDITOR")]
[ContextMenu("ImportNesDB")]
@ -189,20 +189,21 @@ 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

View File

@ -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;

View File

@ -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<OptionMenu> 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;
/// <summary> 指示该游戏实例是否处于联机模式 </summary>
@ -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<OptionMenu> menus = new List<OptionMenu>();
private StepPerformer m_stepPerformer;
protected override void Awake()
{
@ -59,31 +60,31 @@ namespace AxibugEmuOnline.Client
{
m_state = state;
}
/// <summary>
/// 读取快速快照
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public object GetQuickState()
{
return m_state;
}
private bool m_delayCreateRoom;
public void Show(RomFile currentRom, IEmuCore core)
{
m_delayCreateRoom = false;
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);
{
App.roomMgr.SendCreateRoom(RomFile.ID, 0, RomFile.Hash);
}
else
{
m_delayCreateRoom = true;
@ -115,10 +116,7 @@ namespace AxibugEmuOnline.Client
private void OnLoggedIn()
{
if (m_delayCreateRoom)
{
App.roomMgr.SendCreateRoom(m_rom.ID, 0, m_rom.Hash);
}
if (m_delayCreateRoom) App.roomMgr.SendCreateRoom(RomFile.ID, 0, RomFile.Hash);
}
private void OnServerStepUpdate(int step)
@ -148,6 +146,7 @@ namespace AxibugEmuOnline.Client
if (!IsNetPlay) //单人模式恢复模拟器的暂停
Core.Resume();
}
public void QuitGame()
{
Eventer.Instance.UnregisterEvent<int>(EEvent.OnRoomWaitStepChange, OnServerStepUpdate);

View File

@ -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)

View File

@ -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("--", $"<b>{hostNick}</b>的房间", $"{cur}/{max}");
SetBaseInfo("--", $"<b>{hostNick}</b>的房间", $"{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;

View File

@ -2,7 +2,7 @@
namespace VirtualNes.Core
{
public struct ControllerState
public struct ControllerState : IEquatable<ControllerState>
{
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]

View File

@ -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();
}
/// <summary>
/// 负责管理本地控制器与具体游戏之间的槽位分配
/// </summary>
public interface IControllerSetuper
{
/// <summary>
/// 设置本地手柄与游戏手柄槽位的映射
/// </summary>
void SetConnect(
uint? con0ToSlot = null,
uint? con1ToSlot = null,
uint? con2ToSlot = null,
uint? con3ToSlot = null);
}
}