商店系统学习
商店系统几乎是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
中定义了商品的购买、发送商品信息的方法。
总结
- 商店商品应该被抽象到很通用。
- 由于商店物品可能被限购,处于跨服环境时更为复杂,所以限购逻辑的设计很重要。