引入PETimer之后,就可以整合进服务器和客户端,为它们添加计时服务:

PlaneZhong/PETimer: A C# Timer Tool. (github.com)

服务器TimerSvc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
using System;
using System.Collections.Generic;

public class TimerSvc:SingletonPattern<TimerSvc>
{
class TaskPack
{
public int tid;
public Action<int> callback;
public TaskPack(int tid, Action<int> callback)
{
this.tid = tid;
this.callback = callback;
}
}
PETimer pt = null;
Queue<TaskPack> tpQue = new Queue<TaskPack>();
static readonly string tpQueLock = "tpQueLock";
public void Init()
{
pt = new PETimer(100);//添加间隔,PETimer就会使用多线程计时器
tpQue.Clear();
pt.SetLog(info =>
{
PECommon.Log(info);
});
pt.SetHandle((callback,tid) =>//使用多线程计时器,就应该使用SetHandle来添加回调,这是为了具体方法的执行从主线程操作
{
if(callback != null)
{
lock(tpQueLock)
{
tpQue.Enqueue(new TaskPack(tid, callback));
}
}
});
PECommon.Log("TimerSvc Init Done");
}
public void Update()
{
while(tpQue.Count > 0)
{
TaskPack tp = null;
lock (tpQueLock)
{
tp = tpQue.Dequeue();
}
tp?.callback(tp.tid);
}
}
public int AddTimeTask(Action<int> callback, double delay, PETimeUnit timeUnit = PETimeUnit.Millisecond, int count = 1)
{
return pt.AddTimeTask(callback, delay, timeUnit, count);
}
}

定制体力增长的GameMsg

1
2
3
4
5
[System.Serializable]
public class PshPower
{
public int power;
}

服务器PowerSys

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
using PENet;
using PEProtocol;
using System.Collections.Generic;

public class PowerSys:SingletonPattern<PowerSys>
{
CacheSvc _cacheSvc;
public void Init()
{
_cacheSvc = CacheSvc.Instance;

TimerSvc.Instance.AddTimeTask(CalcPowerAdd, PECommon.PowerAddInterval, PETimeUnit.Minute, 0);
PECommon.Log("PowerSys Inited");
}
private void CalcPowerAdd(int tid)
{
//计算体力增长
GameMsg msg = new GameMsg()
{
cmd = (int)CMD.PshPower
};
msg.pshPower = new PshPower();//提前new,这是一个群发的消息,这个消息只需要读取同一个引用即可
//获取所有在线玩家获得实时的体力增长推送数据
Dictionary<ServerSession,PlayerData> onlineDic = _cacheSvc.GetOnlineCache();
foreach (var item in onlineDic)
{
PlayerData pd = item.Value;
ServerSession session = item.Key;

int powerMax = PECommon.GetPowerLimit(pd.lv);
if(pd.power>=powerMax)
{
continue;
}
else
{
pd.power += PECommon.PowerAddNum;
if(pd.power >= powerMax)
{
pd.power = powerMax;
}
}

if(_cacheSvc.UpdatePlayerData(pd.id, pd))
{
msg.err = (int)ErrorCode.UpdateDBError;
}
else
{
msg.pshPower.power = pd.power;
session.SendMsg(msg);
}
}
}
}

服务器一开始启动,就执行PoweSys,每5分钟向全服在线玩家发送体力增长消息

离线玩家体力计算

离线玩家离线时记下时间戳,当玩家再次上线时通过与服务器时间对比计算出体力的增加值

GameMsg定义时间戳

修改GameMsgPalyerData部分

1
2
3
4
5
6
7
public class PlayerData
{
//...
public long time;
//To Add

}

数据库添加time

DBMgr——QueryPlayerData中:

1
time = mySqlDataReader.GetInt64("time")//需要64位

在创建新的玩家账号时:

1
time = TimeSvc.Instance.GetNowTime();

TimeSvc.Instance.GetNowTime();这个API调用的是PETimer的GetMillisecondsTime()

1
2
3
4
public long GetNowTime()
{
return (long)pt.GetMillisecondsTime();//这里转化为long
}

GetMillisecondsTime()返回的是从当前UTC时间到计算机元年(1970.1.1)的双精度浮点数

1
2
3
4
5
private double GetUTCMilliseconds()
{
TimeSpan ts = DateTime.UtcNow - startDateTime;
return ts.TotalMilliseconds;
}

PETimer是使用这个时间来作为时间计时的比较的

数据库设计表:

类型 长度 小数点 不是null
time bigint 11 0

修改服务器LoginSys

每次登录服务器,服务器需要根据时间戳计算玩家的体力增量并刷新玩家数据库,然后再发送给玩家登录成功的消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//计算离线体力增长
int power = _playerData.power;
long nowtime = _timerSvc.GetNowTime();
long playerOfflineTime = _playerData.time;
long playerOfflineDuration = nowtime - playerOfflineTime;
int addPower = (int)(playerOfflineDuration / (PECommon.PowerAddInterval * 60 * 1000))*PECommon.PowerAddNum;
if (addPower > 0)
{
int powerMax = PECommon.GetPowerLimit(_playerData.lv);
if (_playerData.power < powerMax)
{
_playerData.power += addPower;
if (_playerData.power > powerMax)
{
_playerData.power = powerMax;
}
}
}
if (power != _playerData.power)
{
_cacheSvc.UpdatePlayerData(_playerData.id, _playerData);
}

msg.rspLogin = new RspLogin
{
playerData = _playerData
};
//缓存账号数据
_cacheSvc.AcctOnline(data.acct, msgPack.session, _playerData);

玩家下线时更新时间戳

修改服务器LoginSysClearOfflieData

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void ClearOfflieData(ServerSession session)
{
//写入下线时间
PlayerData pd = _cacheSvc.GetPalyerDataBySession(session);
if (pd != null)
{
pd.time = _timerSvc.GetNowTime();
if(!_cacheSvc.UpdatePlayerData(pd.id, pd))
{
PECommon.Log("Update Offline time error",LogType.Error);
}
_cacheSvc.AcctOffLine(session);
}
}

在线玩家更新时间戳

为了防止玩家意外下线,每次在线玩家体力增长时更新玩家的时间数据,修改PowerSys

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private void CalcPowerAdd(int tid)
{
//...
Dictionary<ServerSession,PlayerData> onlineDic = _cacheSvc.GetOnlineCache();
foreach (var item in onlineDic)
{
PlayerData pd = item.Value;
ServerSession session = item.Key;

int powerMax = PECommon.GetPowerLimit(pd.lv);
if(pd.power>=powerMax)
{
continue;//体力最大时不用刷新时间戳
}
else
{
pd.power += PECommon.PowerAddNum;
pd.time = _timerSvc.GetNowTime();//更新时间戳
if(pd.power >= powerMax)
{
pd.power = powerMax;
}
}

if(!_cacheSvc.UpdatePlayerData(pd.id, pd))
{
msg.err = (int)ErrorCode.UpdateDBError;
}
else
{
msg.pshPower.power = pd.power;
session.SendMsg(msg);
}
}
}