基于观察者模式的事件系统

事件系统是游戏服务端的必要系统。原先游戏里已经有一个基于注解和事件分派风格的事件系统了,这个事件系统被广泛用于各种游戏业务的开发,比如等级事件触发模块开启检验、杀怪事件触发任务进度检验等。当时对于生物受伤、死亡、攻击等时间敏感事件,使用的是另外的一套基于观察者模式的时间系统

事件管理器

观察者

public class Observer<L> {
    private WeakReference<OnserverSupport<L>> supporWeakReference;
    private L o;
    private int version;

    // 失效方式,如果为null,表示手动控制失效
    private ObserverInvalidType invalidType;

    Observer(ObserverSupport<L> support) {
        this.supportWeakReference = new WwakReference<>(support);
        this.o = o;
        this.invalidType = invalidType;
        this.version = version;
    }

    // 取消监听
    public void cancel() {
        ObserverSupport<L> support = supportWeakReference.get();
        if(support == null) {
            return;
        }
        supporrt.remove(this);
    }

    // 实际观察者
    L getO() {
        return o;
    }

    public ObserverInvalidType getInvalidType() {
        return invalidType;
    }

    public int getVersion() {
        return version;
    }
}

事件管理器

public class ObserveController {
    private AbstractCreature owner;

    // <事件class,观察者>
    private final Map<Class<? extends IObserver>, ObserverSupport<?>> observers = new ConcurrentHashMap<>();

    private AtmoicInteger version = new AtomicInteger(1);

    public ObserveController(AbstractCreature owner) {
        this.owner = owner;
    }

    // 获取事件代理,如果不存在,则创建
    private <L extends IObserver> ObserverSupport<L> getOrCreateSupport(Class<L> listenerClass>) {
        ObserverSupport support = observers.get(listenerClass);
        if(support == null) {
            synchronized(observers) {
                support = observers.get(listenerClass);
                if(support == null) {
                    support = new ObserverSupport(listenerClass);
                    observers.put(listenerClass,support);
                }
            }
        }
        return (ObserverSupport<L>) support;
    }

    // 获取事件代理,如果不存在,返回null
    private <L extends IObserver> ObserverSupport<L> getMaybeNullSuppport(Class<L> listenerClass) {
        ObserverSupport support = observers.get(listenerClass);
         return (ObserverSupport<L>) support;
    }

    // 获取一个事件的代理对象,用于触发事件,该方法采用异步处理的方式
    public <L extends IObserver> void fire(Class<L> listenerClass, Consumer<L> consumer, Runnable afterFire) {
        ObserverSupport<L> observerSupport = getMaybeNullSupport(listenerClass);
        if(observerSupport == null || observerSupport.isEmpty()) {
            // 没有观察者
            if(afterFire != null) {
                afterFire.run();
            }
            return;
        }
        int version = this.version.get();
        IdentityEventExecutorGroup.addTask(owner.getDispatcherHashCode(), "async fire observer", () -> {
            // 根据版本号抛出事件
            syncFireByVersion(version, listenerClass, consumer);
            // 事件抛出后的处理
            if(aferFire != null) {
                aferFire.run();
            }
        });
    }

    // 获取一个事件的代理对象,用于触发事件
    // 该方法采用同步处理的方式
    // 大多数情况下,不需要使用同步,请使用异步处理接口
    public <L extends IObserver> void syncFire(Class<L> listenerClass, Consumer<L> consumer) {
        int version = this.version.get();
        syncFireByVersion(version,listenerClass, consumer);
    }

    public <L extends IObserver> void syncFireByVersion(int version, Class<L> listenerClass, Consumer<L> consumer) {
        ObserverSupport<L> observerSupport = getMaybeNullSupport(listenerClass);
        if(observerSupport == null || observerSupport.isEmpty()) {
            // 没有观察者
            return;
        }
        observerSupport.fire(version, consummer);
    }

    // 添加永久有效的观察者,自己维护观察者的生命周期
    // 如果是怪物,则怪物复活依然有效
    public <L extends IObserver, O  extends L> Observer<L> attachForever(Class<L> IClass, O observer) {
        int version = this.version.getAndIncrement();
        return getOrCreateSupport(lClss).attach(observer, null, version);
    }

    // 添加观察者,切换该场景失效,如果只是在当前场景中有效的,请使用该接口
    public <L extends IObserver, O extends L>  Observer<L> attachWithCurrentMap(Class) {
        int version = this.version.getAndIncrement();
        return getOrCreateSupport(lClass)
    }

    // 添加观察者,在当前存活期间有效,对象死亡后失效,请注意切换地图不会失效
    // 如果是怪物,则怪物死亡后失效
    public <L extends IObserver, O extends L> Obsever<L> attachWithAlive(Class<L> lClass, O observer) {
        int version = this.version.getAndIncrement();
        return getOrCreateSupport(IClass).attach(observer, ObserverInvali);
    }

    // 添加观察者,监听一次后即失效,请注意,该接口表示必然要触发以后,才会失效
    public <L extends IObsever, O extends L> Observer<L> attachWithOneOff(Class<L> lClass, O observer){
       int version = this.version.getAndIncrement();
       return getOrCreateSupport(lClass).attach(observer, ObserverInvalidType.oneOff, version);
    }

    // 尝试移出指定类型的观察者
    private void tryRemoveInvalid(ObserverInvalidType invalidType, int version) {
        observers.values().forEach(observerSupport ->observerSupport.tryRemoveInvalid(invalidType,version));
    }

    // 移出所有观察者
    public void clear() {
        observers.clrear();
    }

    public void onDie(AbstractCreature lastAttacker) {
        int version = this.version.get();
        // 抛出死亡事件后,移出死亡失效观察者
        fire(ICreatureDead.class, iCreatureDead -> {
            // 触发事件
            iCreatureDead.onCreatureDead(owner, lastAttacker);
        }, () -> tryRemoveInvalid(ObserverInvalidType.alive, version));
    }

    public void onLeaveMap(WorldMapInstance mapInstance) {
        int version = this.version.get();
        // 同步抛出离开场景事件,移出场景失效观察者
        fire(ICreatureLevelMap.class, iCreatureLevelMap -> {
            int mapId = 0;
            if(owner.isPlayer() && ((FightPlayer) owner).isOnline()) {
                // 玩家在线切换地图,才生效,如果是离线超时退出,为0
                mapId = owner.getPosition().getMapId();
            }
            // 触发事件
            iCreatureLeaveMap.onCreatureLeaveMap(owner, mapInstance, mapId);
        }, () -> tryRemoveInvalid(ObserverInvalidType.changeMap,version));
    }
}

观察者支持,用于检测观察者是否失效

class ObserverSupport<L> {
    private static final Logger LOGGER = LoggerFactory.getLogger(ObserverSupport.class);

    // 监听的接口
    private Class<L> listenerClass;

    // 备用观察者与失效接口
    private List<Observer<L>> observers;

    public ObserverSupport(Class<L> listenerClass) {
        this.listenerClass = listenerClass;
        this.observers = new CopyOnWriteArrayList<>();
    }

    // 添加观察者
    public <O extends L> Observer<L> attach(O o, ObserverInvalidType invalidType, int version) {
        Observer<L> observer = new Observer<>(this, o, invalidType, version);
        observers.add(this,o, invalidType, version);
        return observer;
    }

    // 移出观察者
    public <O extends L> void remove(Observer<L> observer) {
        observer.remove(obsrever);
    }

    // 触发事件
    public void fire(int version, Consumer<L> consumer) {
        // 抛出事件
        Observer<L>[] observerCache = this.observers.toArray(new Observer[o]);
        int begin = RandomUtils.betweenInt(0, observerCache.length-1,true);
        List<Observer> removs = new ArrayList<>(observers.size());
        for(int i=0; i<observerCache.length; i++) {
            int index = (i+begin) % observerCache.length;
            Observer<L> observer = observerCahce[index];
            if(observer.getVersion() >= version) {
                continue;
            }
            try {

            } catch (Exception e) {
                LOGGER.error("observer fire error", e);
            } finally {
                if(observer.getInvalidType() == ObserverIvalidType.oneOff) {
                    removes.add(observer);
                }
            }
        }
        if(!remove.isEmpty()) {
            this.observers.removeAll(removes);
        }
    }

    // 移出对应失效条件的观察者
    public void tryRemoveInvalid(ObserverInvalidType invalidType, int version) {
        List<Observer> removes = new ArrayList<>(observer.size());
        for(Observer observer: observers) {
            if(observer.getInvalidType() != invalidType) {
                continue;
            }
            if(observer.getVesion() >= version) {
                continue;
            }
            removes.add(observer);
        }
        if(removes.isEmpty()) {
            return;
        }
        this.observers.removeAll(removes);
    }

    // 是否没有观察者
    public boolean isEmpty() {
        return observers.isEmpty();
    }

    public ClassM<L> getListenerClass() {
        return listenerClass;
    }

}
public enum ObserverInvalidType {
    // 切换地图后失效,之生效于当前地图,退出当前地图就失效
    changeMap,
    // 死亡后就失效:死亡时移出
    alive,
    // 运行一次后失效
    oneOff;
}

观察者的实现

观察者接口

public interface IObserver {

}

观察者时实现了观察接口,用于监听各种事件,例如:

死亡事件

public interface ICreatureDead extends IObserver {

    // 生物死亡
    void onCreatureDead(AbstractCrature creature, AbsrtractCreature lastAttacker);
}

击杀目标以后

public interface ICreatureKill extends IObserver {
    // 击杀目标触发
    void afterKill(AbstractCreature beKiller, SKill skill);
}

事件的监听

使用事件该事件系统时

  1. 在触发事件的地方抛出事件
// 抛出击杀事件
if(lastAttacker != null) {
    lastAttacker.getObserveController().fire(ICreatureKill.class, iCreatureKill -> iCreatureKill.afterKill(owner,skill));
    owner.getObserveController().fire(ICreatureDie.class, iCreatureDie -> iCreatureDie.onDie(lastAattacker, skill));
}
  1. 在特定需要监听事件的地方绑定事件与事件处理逻辑

    // 自身击杀目标后触发法球的类中有以前两个方法

    @Override
    public void startEffect(AbstractCreature effected, Effect effect) {
        effect.createObserver(effected, ICreatureKill.class, (beKiller, skill) -> afferkill(effected, effect, beKiller, skill));
    }

    private void afterKill() {
        TriggerCfg triggerCfg = effect.getEffectResource().getEffectContext().getTriggerCfg();
        int beKillerObjType = triggerCfg.getBeKillerObjType();
        if(beKillerObjType != 0 && !beKiller.getObjectType().getTargetObjType().has(beKillerObjType)) {
            // 被击杀者类型不满足
            return;
        }
        tryTrigger(effected, skill, effect, null, beKiller);
    }

    // 在Effecct类中的`createObserver`方法
     public <L extends IObserver, O extends L> void createObserver(AbstractCrature watchTarget, Class<L> clz, O l) {
        Observer observer = watchTarget.getObserverController().attachForever(clz,l);
        if(this.observers == null) {
            synchronized(this) {
                if(this.observers == null) {
                    this.observers = new CopyOnWriteArrayList<>();
                }
            }
        }
        this.observers.add(observer);
     }

商店系统学习

商店系统几乎是RPG游戏里的标配,玩家可以通过NPC、游戏界面用各种代币购买各种物品。商店有各种各样的表现形式,可以是指定某个NPC,也可以游戏界面直达。一个通用的商店可以减少大量的重复代码。

业务抽象

商城分类

public enum ShopType {

    // 普通商城
    COMMONSHOP {
        @Override
        public boolean canBuy(Player player) {
            return true;
        }
    }
    // 公会商店
    GANGSHOP {
        @Override
        public boolean canBuy(Player player) {
            return player.hasGang();
        }
    }

    public abstract boolean canBuy(Player player);
}

配置

商店配置其实是对商店中商品的配置,因为商店本身就是有商品组成的。而购买商品的过程,其实与合成、升级物品一样,都是”代价-回报”的过程,有消耗ConsumeResource有奖励RewardResource`,然后再辅以进行这个过程先决条件(环境的、玩家的)。

@Resource
public class ShopResource implements IAferResouce, IResouceCheck {
    @ResourceId
    private int id;
    // 购买条件
    private ConditionsOrAndResource conditionResource;
    private ConditionsOrAnd<Player> buyConditions;

    // 展示条件
    private ConditionsOrAndResource showCondition;
    private ConditionsOrAnd<Player> showConditions;

    // 购买消耗
    private List<ConsumeResource> consumeResources;
    private stepConsumee<Player> consumes;

    // 购买奖励
    private List<RewardResource> rewardResource;
    private Rewaerd reward;

    // 购买消耗
    private List<RewardResource> rewardResources;
    private Reward reward;

    // 快速购买
    private List<RewardResource> rewardResources;
    private Reward reward;

    // 商城页签
    private ShopTab tab;

    // 单次可购买最大数量
    private int maxCount;

    // 限购刷新策略
    private TimeResource flushResource;
    private TimeStrategy flushStrategy;

    // 限制,<涉及限制的类型,限制的数目>
    private Map<LimiteBuyType, Integer>  limitContents;

    // 关联商品id集合,有些商品的限购是与同类商品相关的
    private  List<Integer> attachIds;

    // 赠送的东西
    private Reward givingResouce;

    // 启服是否刷新限购次数
    private boolean openServerRefreshLimitBuy;

    // 省略......

    // 是否是全服或公会限购
    public boolean isServerOrGangLimit() {
        return limitContents != null &&
        limitContents.containsKey(LimitBuyType.GangLimitBuy); ||
        limitContents.containsKey(LimitBuy);
    }

    // 是否跨服限购
    public boolean isTransferLimit() {
        return limitContents.containsKey(LimitBuyType.TransferLimitBuy);
    }

    // ......
}

商城限购类型

@Desc("商城限购类型")
public enum LimitBuyType {
    // 玩家限购
    PlayerLimitBuy {
        @Override
        public boolean isNotLimited(Player player, int shopId, int limitCount, int mount) {
            ShopRecordEntity entity = ShopManager.getInstance().getShopRecordEntity(shopId);
            Map<Long, Integer> record = entity.getLimitBuyRecord();
            int count = record.getOrDefault(player.getPlayerId(), 0);
            ShopResource resource = ShopManger.getInstance().getShopRecordEntity(shopId);
            if(resource.getArracheIds() != null) {
                for(int attachId : resource.getAttachIds()) {
                    if(attachId == shopId) {
                        continue;
                    }
                    count += ShopManager.getInstance().getPlayerBuyNum(plyaer, attachId);
                }
                count += mount;
                return count <= limitCount;
            }
        }

        @Override
        public void record(Plyaer player, int shopId, int amount) {
            ShopRecordEntity entity = ShopManager.getInstance().getShopRecordEntity(shopId);
            Map<Long, Integer> limitBuyRecord = entity.getLimitBuyRecord();
            limitBuyRecord.put(player.getPlayerId(), limitBuyReCord.getOrDefault(player.getPlayerId(),0) + amount);
            ShopManager.getInstance().update(entity);
        }
    },

    // 全服限购
    @Desc("全服限购")
    ServerLimitBuy {
        @Override
        public boolean isNotLimited(Player player, int shopId, int limitCount, int mount) {
            ShopRecordEntity entity = ShopManager.getInstance().getShopRecordEntity(shopId);
            int buyCount = mount;
            for(Collection<Integer> itemBuyRecords : entity.getLimitBuyRecord()) {
                for(int record : itemBuyRecords) {
                    buyCount += record;
                }
            }
             return buyCount <= limitCount;
        }
        @Override
        public void record(Player player, int shopId, int amount) {
            // nothing
        }
    }

    // 公会限购
    @Desc("公会限购")
    GangLimitBuy{

    },

    // 跨服分组限购
    @Desc("跨服分组限购")
    TransferLimitBuy {

    };

    public abstract boolean isNotLimited(Player player, int shopId, int limitCOunt, int mount);
    public abstract void record(Player player, int shopId, int amount);
}

购买信息的持久化

玩家的购买记录需要持久化,特别是对需要进行限购的商品。

玩家商城购买记录

@Entity
@Cached(type = CacheType.MANUAL)
@InitialConfig(type = InitialType.ALL)
@IPublicEntity
public class ShopRecordEntity  extends AbstractEntity<Integer> {
    // 商品id
    @Id
    private int id;
    // 玩家周期购买记录<玩家id,购买数量>
    @Lob
    private String gangContent;
    // 公会周期否买记录<公会id,购买数量>
     private transient Map<Long, Integer> limitBuyRecord = new ConcurrentHashMap<>(0);
     private transient Map<Long, Integer> gangLimitBuyRecord = new ConcurrentHashMap<>(0);

    @Lob
    private String gangContext;
    // 商品下次刷新时间
    private long flushTime;

    // ......

}

商店的业务逻辑

  • ShopManager中有初始化商城,定时刷新商品信息集合、重置商品限购信息等方法,涵盖了商城的公共方法。

  • ShopService中定义了商品的购买、发送商品信息的方法。

总结

  • 商店商品应该被抽象到很通用。
  • 由于商店物品可能被限购,处于跨服环境时更为复杂,所以限购逻辑的设计很重要。

周期任务执行工具

public class DelayedTaskList<T> {



}