mirror of
https://gitee.com/jisol/jisol-game/
synced 2025-09-27 02:36:14 +00:00
提交
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c36e10ef0cfc4de3bf506429ff687cd4
|
||||
timeCreated: 1706782844
|
@@ -0,0 +1,52 @@
|
||||
|
||||
namespace Plugins.JNGame.Sync.Frame.Entity
|
||||
{
|
||||
public class JNTime
|
||||
{
|
||||
|
||||
private JNSyncFrame _sync;
|
||||
|
||||
public static JNTime Time;
|
||||
|
||||
/// <summary>
|
||||
/// 在Unity中,Time.time是一个非常有用的属性,它表示从游戏开始运行到现在的时间,单位是秒。它常常被用来作为游戏内时间或者帧时间的参考。
|
||||
/// </summary>
|
||||
public float time => (this.deltaTime * _sync.NLocalRunFrame);
|
||||
|
||||
/// <summary>
|
||||
/// 在Unity中,Time.deltaTime是一个表示上一帧所花费时间的属性,单位是秒。它用于在游戏运行时提供稳定的时间测量,不受帧率变化的影响
|
||||
/// </summary>
|
||||
public float deltaTime => ((float)_sync.NSyncTime / _sync.NDivideFrame) / 1000;
|
||||
|
||||
/// <summary>
|
||||
/// Time.fixedDeltaTime是一个在Unity中表示以秒为单位的固定时间间隔的属性,通常用于物理计算和其他需要固定帧率更新的场景。它与Time.deltaTime不同,因为Time.fixedDeltaTime可以自行赋值,并且用于固定帧率更新(如FixedUpdate()函数)。
|
||||
/// </summary>
|
||||
public float fixedDeltaTime => ((float)_sync.NSyncTime / _sync.NDivideFrame) / 1000;
|
||||
|
||||
/// <summary>
|
||||
/// Time.frameCount 表示已经渲染的总帧数。这个属性通常用于性能测试,例如,你可以使用它来检查游戏是否在每一帧都进行了必要的渲染工作,或者是否在某些帧上出现了渲染瓶颈。
|
||||
/// </summary>
|
||||
public int frameCount => _sync.NLocalRunFrame;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Time.realtimeSinceStartup是Unity中表示自游戏启动以来经过的实时时间(以秒为单位)的属性。这个时间不受Time.timeScale的影响,即使游戏暂停或慢动作,它的值也会持续增加,因为它是独立于游戏时间的。
|
||||
/// </summary>
|
||||
public float realtimeSinceStartup => (this.time);
|
||||
|
||||
/// <summary>
|
||||
/// Time.unscaledDeltaTime 是 Unity 中的一个属性,表示上一帧所花费的时间,单位是秒。与 Time.deltaTime 不同,Time.unscaledDeltaTime 是未经过时间缩放的,也就是说它不受 Time.timeScale 的影响。
|
||||
/// </summary>
|
||||
public float unscaledDeltaTime => ((float)_sync.NSyncTime / _sync.NDivideFrame) / 1000;
|
||||
|
||||
public float smoothDeltaTime => ((float)_sync.NSyncTime / _sync.NDivideFrame) / 1000;
|
||||
|
||||
|
||||
public JNTime(JNSyncFrame sync)
|
||||
{
|
||||
Time = this;
|
||||
this._sync = sync;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf53109fbe7b4d33856ebd1a7904c078
|
||||
timeCreated: 1706782853
|
8
JNFrame/Assets/Game/Plugins/JNGame/Sync/Frame/Game.meta
Normal file
8
JNFrame/Assets/Game/Plugins/JNGame/Sync/Frame/Game.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 240dc802e5754972a9f7590feea56628
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,17 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Plugins.JNGame.Sync.Frame.game
|
||||
{
|
||||
public abstract class IJNSyncFrameComponent : MonoBehaviour
|
||||
{
|
||||
//标识
|
||||
[HideInInspector]
|
||||
public int _nId;
|
||||
[HideInInspector]
|
||||
public int NID => _nId;
|
||||
|
||||
//获取同步类
|
||||
protected abstract JNSyncFrame GetSync();
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fdc20d5f68c44f11ba0f08c304cb5fae
|
||||
timeCreated: 1706518761
|
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Plugins.JNGame.Sync.Frame.game
|
||||
{
|
||||
|
||||
//所有帧同步组件的基类
|
||||
public abstract class JNSyncFrameComponent<T> : IJNSyncFrameComponent
|
||||
{
|
||||
|
||||
//当前输入
|
||||
private T _input;
|
||||
|
||||
//是否有输入
|
||||
public Boolean isInput => this._input == null;
|
||||
|
||||
//是否初始化完成
|
||||
[HideInInspector]
|
||||
public Boolean isSyncInitSuccess = false;
|
||||
|
||||
//清空输入
|
||||
public void ClearInput(){
|
||||
this._input = default(T);
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
//向帧同步获取Id
|
||||
this._nId = this.GetSync().nSyncID();
|
||||
GetSync().AddSyncActor(this);
|
||||
this.OnSyncLoad();
|
||||
}
|
||||
|
||||
//初始化完成
|
||||
public void OnSyncInitSuccess(){}
|
||||
|
||||
//加载
|
||||
public abstract void OnSyncLoad();
|
||||
|
||||
//解析
|
||||
public T Decoder(byte[] bytes)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(Encoding.UTF8.GetString(bytes));
|
||||
}
|
||||
|
||||
//编码
|
||||
public byte[] Encoder(T input){
|
||||
return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(input));
|
||||
}
|
||||
|
||||
//帧同步
|
||||
public abstract void OnSyncUpdate(int dt,JNFrameInfo frame,T input);
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a802748c1744ea886c648699b51245f
|
||||
timeCreated: 1706163852
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98ca6a76430a4a27b0d8d609453a6db2
|
||||
timeCreated: 1706782794
|
319
JNFrame/Assets/Game/Plugins/JNGame/Sync/Frame/JNSyncFrame.cs
Normal file
319
JNFrame/Assets/Game/Plugins/JNGame/Sync/Frame/JNSyncFrame.cs
Normal file
@@ -0,0 +1,319 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Plugins.JNGame.Sync.Frame.Entity;
|
||||
using Plugins.JNGame.Sync.Frame.game;
|
||||
using Plugins.JNGame.System;
|
||||
using Plugins.JNGame.Util;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Plugins.JNGame.Sync.Frame
|
||||
{
|
||||
|
||||
//帧同步 通知
|
||||
public interface JNSyncFrameEvent
|
||||
{
|
||||
static string CLEAR = "JNSyncFrameEvent_CLEAR";
|
||||
static string CREATE = "JNSyncFrameEvent_CREATE";
|
||||
static string FRAME = "JNSyncFrameEvent_FRAME";
|
||||
}
|
||||
|
||||
public abstract class JNSyncFrame : SystemBase
|
||||
{
|
||||
|
||||
//同步时间 (和服务器保持一致)
|
||||
private int _nSyncTime = 67;
|
||||
//大于多少帧进行追帧
|
||||
private int _nMaxFrameBan = 4;
|
||||
//大于多少帧进行快速追帧
|
||||
private int _nMaxFrameLoopBan = 18;
|
||||
//将服务器帧数进行平分
|
||||
private int _nDivideFrame = 3;
|
||||
|
||||
public int NSyncTime => _nSyncTime;
|
||||
public int NMaxFrameBan => _nMaxFrameBan;
|
||||
public int NMaxFrameLoopBan => _nMaxFrameLoopBan;
|
||||
public int NDivideFrame => _nDivideFrame;
|
||||
|
||||
|
||||
//帧队列
|
||||
private Queue<JNFrameInfo> _nFrameQueue = new();
|
||||
|
||||
//本地已执行帧数
|
||||
private int _nLocalRunFrame = 0;
|
||||
//本地帧数
|
||||
private int _nLocalFrame = 0;
|
||||
//服务器的帧
|
||||
private int _nServerFrame = 0;
|
||||
|
||||
public int NLocalRunFrame => _nLocalRunFrame;
|
||||
public int NLocalFrame => _nLocalFrame;
|
||||
public int NServerFrame => _nServerFrame;
|
||||
|
||||
//暂存帧列表
|
||||
private Dictionary<int,JNFrameInfo> _nFrameTempQueue = new();
|
||||
|
||||
//需要同步的Actor
|
||||
private List<IJNSyncFrameComponent> _nSyncActors = new();
|
||||
|
||||
//ID 每添加 JNSyncFrameComponent + 1
|
||||
public Func<int> nSyncID = RandomUtil.Next(0);
|
||||
|
||||
//随机数
|
||||
public Func<float> nRandom = RandomUtil.SyncRandom();
|
||||
|
||||
//随机数double
|
||||
public Func<float,float,float> nRandomFloat = RandomUtil.SyncRandomFloat();
|
||||
|
||||
//随机数整数
|
||||
public Func<int, int, int> nRandomInt = RandomUtil.SyncRandomInt();
|
||||
|
||||
//是否开始同步
|
||||
Boolean _isStart = false;
|
||||
|
||||
//是否请求后台数据
|
||||
private Boolean _isRequestServerData = false;
|
||||
|
||||
public bool IsRequestServerData => _isRequestServerData;
|
||||
|
||||
//帧更新
|
||||
int dtTotal = 0;
|
||||
//输入更新
|
||||
int dtInputTotal = 0;
|
||||
|
||||
public JNTime Time => (new JNTime(this));
|
||||
|
||||
public override Task OnInit()
|
||||
{
|
||||
Physics.simulationMode = SimulationMode.Script;
|
||||
Physics.autoSyncTransforms = false;
|
||||
this.OnReset();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
//开始
|
||||
public void OnStart(){
|
||||
this._isStart = true;
|
||||
}
|
||||
|
||||
//重置数据
|
||||
public void OnReset()
|
||||
{
|
||||
|
||||
EventDispatcher.Event.Dispatch(JNSyncFrameEvent.CLEAR);
|
||||
|
||||
this._isStart = false;
|
||||
this.nSyncID = RandomUtil.Next(0);
|
||||
this.nRandom = RandomUtil.SyncRandom();
|
||||
this.nRandomFloat = RandomUtil.SyncRandomFloat();
|
||||
this.nRandomInt = RandomUtil.SyncRandomInt();
|
||||
this._nSyncActors = new();
|
||||
this._nFrameQueue = new();
|
||||
this._nFrameTempQueue = new();
|
||||
this._nLocalFrame = 0;
|
||||
this._nServerFrame = 0;
|
||||
this._nLocalRunFrame = 0;
|
||||
this.dtTotal = 0;
|
||||
this.dtInputTotal = 0;
|
||||
this._isRequestServerData = false;
|
||||
|
||||
Physics.SyncTransforms();
|
||||
EventDispatcher.Event.Dispatch(JNSyncFrameEvent.CREATE);
|
||||
|
||||
}
|
||||
|
||||
//更新同步
|
||||
public void Update(int dt)
|
||||
{
|
||||
|
||||
if(!_isStart) return;
|
||||
|
||||
dtTotal += dt;
|
||||
dtInputTotal += dt;
|
||||
|
||||
int nSyncTime = this.DyTime();
|
||||
|
||||
if(nSyncTime > 0){
|
||||
while(nSyncTime != 0 && this.dtTotal > nSyncTime){
|
||||
this.onUpdate();
|
||||
this.dtTotal -= nSyncTime;
|
||||
nSyncTime = this.DyTime();
|
||||
}
|
||||
}else{
|
||||
//追帧运行 保持前端 15 帧 刷新
|
||||
long endTime = (new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds()) + 66;
|
||||
while(this.DyTime() == 0 && (new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds()) < endTime){
|
||||
this.onUpdate();
|
||||
}
|
||||
dtTotal = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//运行帧
|
||||
public void onUpdate()
|
||||
{
|
||||
if(!(_nFrameQueue.TryDequeue(out var frame))) return;
|
||||
|
||||
if(frame.Index != 0)
|
||||
this._nLocalRunFrame = frame.Index;
|
||||
|
||||
int dt = this._nSyncTime / this._nDivideFrame;
|
||||
|
||||
//拆出输入
|
||||
Dictionary<int, JNFrameInput> inputs = new();
|
||||
foreach (var message in frame.Messages)
|
||||
{
|
||||
inputs.Add(message.NId,message);
|
||||
}
|
||||
|
||||
//更新帧
|
||||
this._nSyncActors.ForEach(child =>
|
||||
{
|
||||
MethodInfo OnSyncUpdate = child.GetType().GetMethod("OnSyncUpdate");
|
||||
MethodInfo Decoder = child.GetType().GetMethod("Decoder");
|
||||
if (inputs.ContainsKey(child.NID))
|
||||
{
|
||||
OnSyncUpdate.Invoke(child,new []{dt,frame,Decoder.Invoke(child,new object[]{ inputs[child.NID].Input.ToByteArray() })});
|
||||
}
|
||||
else
|
||||
{
|
||||
OnSyncUpdate.Invoke(child,new []{ dt,frame,(object)null });
|
||||
}
|
||||
});
|
||||
|
||||
// //执行下一帧物理
|
||||
// Physics.Simulate((float)dt / 1000);
|
||||
// Physics.SyncTransforms();
|
||||
|
||||
|
||||
}
|
||||
|
||||
//自适应间隔时间
|
||||
public int DyTime(){
|
||||
int dt = this._nSyncTime / this._nDivideFrame;
|
||||
int loop = dt;
|
||||
|
||||
//大于nMaxFrameBan 进行 追帧
|
||||
if(this._nFrameQueue.Count > this._nMaxFrameBan) {
|
||||
|
||||
//计算超过的帧数
|
||||
int exceed = this._nFrameQueue.Count - this._nMaxFrameBan;
|
||||
int most = this._nMaxFrameLoopBan - this._nMaxFrameBan;
|
||||
int ldt = ((most - exceed) / most) * dt;
|
||||
|
||||
//自适应追帧算法
|
||||
if(exceed <= this._nMaxFrameLoopBan){
|
||||
loop = ldt;
|
||||
}else{
|
||||
loop = 0;
|
||||
}
|
||||
}else{
|
||||
loop = dt;
|
||||
}
|
||||
|
||||
return loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收帧数据
|
||||
* @param frame 帧数据
|
||||
* @param isLatestData 是否最新的帧数据
|
||||
*/
|
||||
public void AddInput(JNFrameInfo frame,Boolean isLatestData = true)
|
||||
{
|
||||
|
||||
if (!_isStart) return;
|
||||
|
||||
if(isLatestData)
|
||||
{
|
||||
this._nServerFrame = frame.Index;
|
||||
// //如果推的帧小于本地帧 则 重开
|
||||
// if(frame.Index < _nLocalFrame){
|
||||
// OnReset();
|
||||
// OnStart();
|
||||
// return;
|
||||
// }
|
||||
}
|
||||
|
||||
//我需要的下一帧
|
||||
int index = _nLocalFrame + 1;
|
||||
|
||||
//判断接受的帧是否下一帧 如果不是则加入未列入
|
||||
if (frame.Index != index){
|
||||
|
||||
_nFrameTempQueue.TryAdd(frame.Index,frame);
|
||||
|
||||
//在未列入中拿到需要的帧
|
||||
JNFrameInfo tamp = null;
|
||||
|
||||
if ((tamp = _nFrameTempQueue.GetValueOrDefault(index,null)) == null)
|
||||
{
|
||||
|
||||
var that = this;
|
||||
|
||||
//如果没有则向服务器请求我需要的帧数
|
||||
if (!that._isRequestServerData)
|
||||
{
|
||||
|
||||
that._isRequestServerData = true;
|
||||
|
||||
//请求
|
||||
that.OnServerData(this._nLocalFrame, 0).ContinueWith(infos =>
|
||||
{
|
||||
foreach (var frameInfo in infos.Frames)
|
||||
{
|
||||
if(frameInfo.Index >= this._nLocalFrame)
|
||||
that.AddInput(frameInfo,false);
|
||||
}
|
||||
that._isRequestServerData = false;
|
||||
}).Forget();
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//如果有则覆盖
|
||||
frame = tamp;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//删除临时帧
|
||||
_nFrameTempQueue.Remove(frame.Index);
|
||||
|
||||
_nLocalFrame = index;
|
||||
|
||||
//分帧插入
|
||||
_nFrameQueue.Enqueue(frame);
|
||||
for (var i = 0; i < this._nDivideFrame - 1; i++) {
|
||||
_nFrameQueue.Enqueue(new JNFrameInfo());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//添加同步组件
|
||||
public void AddSyncActor(IJNSyncFrameComponent sync){
|
||||
if(!this._nSyncActors.Contains(sync)){
|
||||
this._nSyncActors.Add(sync);
|
||||
}
|
||||
}
|
||||
|
||||
//销毁同步组件
|
||||
public void DelSyncActor(IJNSyncFrameComponent sync){
|
||||
this._nSyncActors.Remove(sync);
|
||||
}
|
||||
|
||||
//发送帧数据
|
||||
protected abstract void OnSendInput(JNFrameInputs inputs);
|
||||
//获取帧数据
|
||||
protected abstract UniTask<JNFrameInfos> OnServerData(int start,int end);
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44e6e0cb968b4bc0a21ed772b81d1c39
|
||||
timeCreated: 1706003302
|
Reference in New Issue
Block a user