master #67
2
.gitignore
vendored
2
.gitignore
vendored
@ -18,3 +18,5 @@
|
||||
/AxibugEmuOnline.Server/config.cfg
|
||||
/AxibugEmuOnline.Server/bin/
|
||||
/AxibugEmuOnline.Client/.editorconfig
|
||||
/AxibugEmuOnline.Client/*.user
|
||||
/AxibugEmuOnline.Client/.idea
|
||||
|
@ -63,5 +63,10 @@
|
||||
/// 服务器等待Step更新
|
||||
/// </summary>
|
||||
OnRoomWaitStepChange,
|
||||
|
||||
/// <summary>
|
||||
/// 当房间中手柄位信息发生任何变化时触发,进入房间后也应该触发
|
||||
/// </summary>
|
||||
OnRoomSlotDataChanged, //todo : 实现这个事件
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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>
|
||||
|
@ -129,6 +129,11 @@ namespace AxibugEmuOnline.Client
|
||||
}
|
||||
}
|
||||
|
||||
public IControllerSetuper GetControllerSetuper()
|
||||
{
|
||||
return ControllerMapper;
|
||||
}
|
||||
|
||||
public ControllerState FromNet(AxiReplay.ReplayStep step)
|
||||
{
|
||||
var temp = new ServerInputSnapShot();
|
||||
|
@ -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()
|
||||
{
|
||||
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
|
||||
/// <summary>
|
||||
/// NES控制器按键类
|
||||
/// </summary>
|
||||
public class Button
|
||||
{
|
||||
bool IsPressing();
|
||||
/// <summary> 所属控制器 </summary>
|
||||
readonly Controller m_hostController;
|
||||
|
||||
/// <summary> 按键 </summary>
|
||||
readonly EnumButtonType m_buttonType;
|
||||
|
||||
/// <summary> 按键监听器 </summary>
|
||||
KeyListener m_keyListener;
|
||||
|
||||
public Button(Controller controller, EnumButtonType buttonType)
|
||||
{
|
||||
m_hostController = controller;
|
||||
m_buttonType = buttonType;
|
||||
|
||||
CreateListener();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 采集按钮按下状态
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;//清空游戏快照
|
||||
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)
|
||||
@ -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<int>(EEvent.OnRoomWaitStepChange, OnServerStepUpdate);
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user