版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/littleschemer/article/details/78012630
实现背景
该系列打了这么久的地基,还没写过业务代码。实际上我是这么考虑的,因为不同游戏项目的业务代码差异性非常大,业务逻辑只有你想不到,没有策划大大想不到的。要想做一个拓展性强的框架,按理说业务代码越少越好。但再三考虑,还是要尽量构造出一些游戏公共的业务,也算是这套框架的示例代码吧。
玩过手机游戏的人应该知道,不管是什么游戏,都会经常出现每日重置的逻辑。比如,玩家打副本每日有一个上限次数,玩家购买某种商品也会有每日购买最大次数……这些业务有个特点,就是每天有上限,隔天次数会被重置。至于隔天是0点重置,还是5点重置,就要看项目的运营更新时间了。
每日重置业务的注意事项
每日重置的逻辑看似简单,要完美实现还是要花点功夫的。因为每日重置有这样的特点:
1. 在家玩家和离线玩家的处理方式是不同的;
2. 每日重置的时候要考虑线程安全,避免出现并发问题。
针对第一个问题,我们的处理方式是,处理在家玩家,我们通过遍历在线玩家列表,依次执行重置业务。而离线玩家就比较麻烦,我们不可能把所有不在线的玩家从数据库捞出来对其处理。实际上,我们可以在玩家登录的时候处理,玩家每天登录的时候,我们将玩家身上的重置标识与系统公共的重置时间进行比较,若不相同,则可以执行重置业务。
针对第二个问题,考虑线程并发问题,就会跟系统所使用的异步线程模型相结合。由于我们的线程模型是根据玩家的分发id作负载均衡的。所以,每日重置也应该与其相适应。
quartz在游戏中的应用
quartz是一个非常优秀的作业调度框架。在游戏开发中,我们经常用quartz来实现定点任务和频率任务。
使用quartz只需要引入相关的jar包,然后使用配置文件对任务触发进行配置。
每日重置业务实现
1. 首先,我们在当前线程模型的基础上,定义一种timer事件。该事件需要满足,对玩家业务的执行来说是线程安全的。同时,为了拓展性,该事件可以设置timer的执行次数。要达到线程安全,只要让timer事件继承自AbstractDistributeTask就可以了。具体原因,可参考该系列关于线程模型的文章手游服务端框架之消息线程模型
/**
* timer任务
* @author kingston
*/
public abstract class TimerTask extends AbstractDistributeTask {
private int currLoop;
/** 小于0表示无限任务 */
private int maxLoop;
public TimerTask(int distributeKey) {
this(distributeKey, 1);
}
public TimerTask(int distributeKey, int maxLoop) {
this.distributeKey = distributeKey;
this.maxLoop = maxLoop;
}
public void updateLoopTimes() {
this.currLoop += 1;
}
public boolean canRunAgain() {
if (this.maxLoop <= 0) {
return true;
}
return this.currLoop < this.maxLoop;
}
}
public class DailyResetTask extends TimerTask {
private Player player;
public DailyResetTask(int distributeKey, Player player) {
super(distributeKey);
this.player = player;
}
@Override
public void action() {
System.err.println("玩家"+player.getName()+"进行每日重置");
PlayerManager.getInstance().checkDailyReset(player);
}
}
<!-- 每日重置 -->
<job>
<name>DailyResetJob</name>
<group>DEFAULT</group>
<job-class>com.kingston.game.cronjob.DailyResetJob</job-class>
</job>
<trigger>
<cron>
<name>DailyResetJobTrigger</name>
<group>DEFAULT</group>
<job-name>DailyResetJob</job-name>
<job-group>DEFAULT</job-group>
<cron-expression>0 0 5 * * ?</cron-expression>
<!-- 每天05:00运行 -->
</cron>
</trigger>
/**
* 每日5点定时job
* @author kingston
*/
@DisallowConcurrentExecution
public class DailyResetJob implements Job {
private Logger logger = LoggerSystem.CRON_JOB.getLogger();
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
logger.info("每日5点定时任务开始");
long now = System.currentTimeMillis();
SystemParameters.update("dailyResetTimestamp", now);
Collection<Player> onlines = PlayerManager.getInstance().getOnlinePlayers().values();
for (Player player:onlines) {
int distributeKey = player.distributeKey();
//将事件封装成timer任务,丢回业务线程处理
TaskHandlerContext.INSTANCE.acceptTask(new DailyResetTask(distributeKey, player));
}
}
}
5. 对于离线玩家的处理,我们需要将当次重置的时刻记录在一张公共的系统表(systemrecord)。在DailyResetJob里的
SystemParameters.update("dailyResetTimestamp", now);
玩家登录的时候检查一下,在LoginManager.handlSelectPlayer()方法。登录逻辑发生在玩家发出登录消息,本来就在架构里的线程模型,本身就是线程安全的了。
/**
* 选角登录
* @param session
* @param playerId
*/
public void handleSelectPlayer(IoSession session, long playerId) {
Player player = PlayerManager.getInstance().get(playerId);
if (player != null) {
//绑定session与玩家id
session.setAttribute(SessionProperties.PLAYER_ID, playerId);
//加入在线列表
PlayerManager.getInstance().add2Online(player);
SessionManager.INSTANCE.registerNewPlayer(playerId, session);
//推送进入场景
ResPlayerEnterSceneMessage response = new ResPlayerEnterSceneMessage();
response.setMapId(1001);
MessagePusher.pushMessage(session, response);
//检查日重置
PlayerManager.getInstance().checkDailyReset(player);
}
}
至此,游戏的每日重置业务的介绍到这里就结束啦。
手游服务端开源框架系列完整的代码请移步github ->> jforgame