目录
游戏中的商城系统是游戏厂商获取利润的主要来源之一,那商城是怎么实现的呢,其实很简单,我将以我的开发经验来记录其中的一种实现方式。
准备工作
拿王者荣耀的商城界面来举例可以看到商品有很多分类。
比如:热门推荐、限时皮肤、英雄、皮肤、星元、夺宝、宝箱、限购宝箱、特惠商品,玩家挑选自己喜欢的商品。
为了更贴近实际一点我们提前配置好物品表ItemConfig。
对应上面的物品表,在游戏服务端中2001这串数字就表示金币以此类推。
那玩家的数据我们就可以用字典来存储
Dictionary<int,long> GetItem = new Dictionary<int,long>();
比如 GetItem[2001] 的值就表示了玩家在游戏中金币的数量,(小编在王者中金币攒了四十多万了,都花不出去。。。)
如果玩家用5000钻石抽奖获得了钻石夺宝的水晶
那么玩家的物品信息在服务端就会:
GetItem[2006] -= 5000 //消耗5000钻石
GetItem[2003] += 1 //获得一个水晶
以此为准备条件来实现商城功能。
购买流程
购买完成后发放方式有的直接发放,有的商品是通过邮件发放的;直接发放前端就可以在接收到服务端购买成功的信息后弹出领取商品的界面和按钮,邮件发放就可以联系到邮件系统来实现物品的发放。
现在来模拟实现一个商城系统。
配置商品表
1、基本属性
熟悉好流程后来配置商品表 —— 商品Id(ID)、商品价格(Price)、商品名字(Name)、货币类型(CurrencyType —— 金币、钻石、点券、荣耀水晶...)这是最基本的商品属性,然后我们再来细分商品类型。
2、商品类型属性
根据左侧侧边栏进行商品类型的划分(GoodsType)—— 这一目的是实现玩家通过点击左侧侧边栏来检索这一类型的商品,比如玩家点击英雄检索出GoodsType都是英雄的商品。
3、近一步划分
此外商品还有其他属性,比如说限时商品(限时皮肤)、限购(英雄不能重复购买、皮肤不能重复购买、宝箱限购50个......)、抽奖商品(宝箱、夺宝等本质是通过概率获得商品)、商品发放方式(直接发放、邮件发放)
4、编辑配置表
商品类型(1:英雄,2:皮肤,3:夺宝,4:宝箱)
货币类型(1:金币,2:点券,3:荣耀水晶,4:钻石)
限购时间存储时间戳,0表示不是限时商品 —— 同理限购数量、抽奖概率ID为0表示不是该类型的商品
PayConfig
编辑概率配置表
ProbabilityConfig
PS:计算概率的实现方式之一
这个方法的原理是确定一个总权重值比如100,然后取随机数和权重区间做比较
比如看积分夺宝概率配置表,
获得1001物品的概率为(75%= 75-0)(0到100取随机数抽到75以下都对应1001物品),
获得1002物品的概率为(15%= 90-75)(0到100取随机数抽到75—90区间的数都对应1002物品),
以此类推获得2002物品也就是荣耀水晶的概率就是(1% = 100-99)
计算出抽到随机数对应的是哪一个物品的ID然后返回
//start 为0 end为总权重值
//数组a为权重区间 数组b为对应概率的物品ID
public static int GetProbabilityPower(int[] a,int[] b,int start,int end)
{
int multi = 1;
//取随机数
int random = RandomGenerator.RandomNumber(start, end);
for(int num = 0;num <a.Length;num++)
{
//遍历权重区间与随机数做比较
if (random < a[num])
{
multi = b[num];
break;
}
}
return multi;
}
购买商品
按照ESC式编程,我们就需要创建一个支付组件(PayComponent)用于购买商品,物品组件(ItemComponent)用于记录玩家的物品信息(和文章开头部分相对应)
物品组件
namespace ET.Server
{
[ComponentOf(typeof(Unit))]
public class ItemComponent:Entity,IAwake,IDestroy,IDeserialize,ITransfer,IUnitCache
{
//用于记录玩家身上物品的数量
[BsonDictionaryOptions(DictionaryRepresentation.ArrayOfArrays)]
public Dictionary<int, long> ItemNum = new Dictionary<int, long>();
}
}
物品组件所做出的行为就是物品的加减对ItemNum进行操作,这里先不说了。
支付组件
namespace ET.Server
{
[ComponentOf(typeof(Unit))]
public class PayComponent:Entity,IAwake,IDestroy,IDeserialize,ITransfer,IUnitCache
{
//用于记录限购商品已购买的数量
[BsonDictionaryOptions(DictionaryRepresentation.ArrayOfArrays)]
public Dictionary<int, int> BuyCount = new Dictionary<int, int>();
public int DayFlag = 20240521;
}
}
using System.Collections.Generic;
namespace ET.Server
{
public class PayComponentAwakeSystem: AwakeSystem<PayComponent>
{
protected override void Awake(PayComponent self)
{
}
}
public class PayComponentDestorySystem: DestroySystem<PayComponent>
{
protected override void Destroy(PayComponent self)
{
}
}
[FriendOf(typeof (PayComponent))]
public static class PayComponentSystem
{
//用于商品列表的展示 —— 分为三种方式获取商品信息 —— 单独写一个获取商品信息的接口调用该方法
public static Dictionary<int,GoodsListProto> GetGoodList(this PayComponent self, int type ,int key)
{
Dictionary<int, GoodsListProto> items = new Dictionary<int, GoodsListProto>();
//读取商品配置表
var payConfig = PayConfigCategory.Instance.GetAll();
//获取所有商品信息
if (type == 0)
{
foreach (var info in payConfig)
{
self.GetGoodListHelp(info, items);
}
}
//根据商品类型获取商品信息
if (type == 1)
{
foreach (var info in payConfig)
{
if (info.Value.GoodsType == key)
{
self.GetGoodListHelp(info, items);
}
}
}
//根据商品ID获取商品信息
if (type == 2)
{
foreach (var info in payConfig)
{
if (info.Key == key)
{
self.GetGoodListHelp(info, items);
}
}
}
return items;
}
public static Dictionary<int,GoodsListProto> GetGoodListHelp(this PayComponent self, KeyValuePair<int,PayConfig> info,Dictionary<int,GoodsListProto> items)
{
//做各种符合商品信息的判断。。。这里就不说了
items.Add(info.Key, new GoodsListProto()
{
ImgId = info.Value.ImgId, //商品图片
Price = info.Value.Price, //商品价格
AllBuyCount = self.BuyCount[info.Key], //已经购买数量
EndTime = endTime, //限时商品结束时间
GoodsType = info.Value.GoodsType, //商品类型
GoodsId = info.Key, //商品ID
});
}
//检查限购物品购买次数是否用完
public static bool CheckBuy(this PayComponent self,int key,int NowBuyCount)
{
//2表示根据商品ID搜寻商品
Dictionary<int, GoodsListProto> goods = self.GetGoodList(2, key);
GoodsListProto _goods = goods[key];
if (_goods.AllBuyCount == -1 || _goods.AllBuyCount > NowBuyCount)
{
return false;
}
return true;
}
//根据商品ID购买商品
public static (Dictionary<int,long>,long) Buy(this PayComponent self,int key,int NowBuyCount)
{
Dictionary<int, long> getItems = new Dictionary<int, long>();
//根据商品ID在配置表查找商品信息
var payConfig = PayConfigCategory.Instance.Get(key);
//限时、限购等判断逻辑
getItems.Add(payConfig.key,NowBuyCount)
//记录商品的购买数量用于判断限购商品
self.BuyCount[key] += NowBuyCount;
//如果是抽奖型商品读取概率表调用GetProbabilityPower方法
if(payConfig.LotteryBox != 0)
{
var probabilityConfig = ProbabilityConfigCategory.Instance.Get(payConfig.LotteryBox);
int GoodsKey = GetProbabilityPower(probabilityConfig.IntervalWeight,probabilityConfig.Probability,0,probabilityConfig.TotalWeightValue);
getItems.Clear();
getItems.Add(GoodsKey,NowBuyCount);
}
long totalPrice = payConfig.Price * NowBuyCount;
return (getItems,totalPrice);
}
}
}
支付Proto消息
//ResponseType M2C_Pay
message C2M_Pay // IActorLocationRequest
{
int32 RpcId = 1;
int32 GoodsId = 2; //商品ID
int32 NowBuyCount = 3; //购买商品数量
int32 CurrencyType = 4; //货币类型
}
message M2C_Pay // IActorLocationResponse
{
int32 RpcId = 1;
int32 Error = 2;
string Message = 3;
map<int32,int64> GetItems = 4; //获得的物品
}
购买商品接口
[ActorMessageHandler(SceneType.Map)]
public class C2M_PayHandler : AMActorLocationRpcHandler<Unit,C2M_Pay,M2C_Pay>
{
protected override async ETTask Run(Unit unit, C2M_Pay request, M2C_Pay response)
{
PayComponent payComponent = unit.GetComponent<PayComponent>();
//判断物品是否限购
if (payComponent.CheckBuy(request.GoodsId))
{
response.Error = ErrorCode.ERR_All;
response.Message = "玩家买过该商品了不能再买了";
return;
}
//执行购买方法
(Dictionary<int, long> getItems,long totalPrice) = payComponent.Buy(request.GoodsId,request.NowBuyCount)
//获取玩家的物品组件
ItemComponent itemComponent = unit.GetComponent<ItemComponent>();
//判断玩家货币是否充足
if(itemComponent.ItemNum[request.CurrencyType] < totalPrice)
{
response.Error = ErrorCode.ERR_All;
response.Message = "玩家好穷,买不起该商品,要多充钱";
return;
}
//玩家得到商品
itemComponent.GetItem(getItems);
//扣除玩家购买商品所需货币数量
itemComponent.ItemNum[request.CurrencyType] -= totalPrice;
//如果是邮件发放将物品传递给邮件组件来给玩家发放奖励
//通知前端购买的物品和数量
response.GetItems = getItems;
}
}
总结
在实现商城系统过程中,配置表的编辑是重中之重,要充分考虑商品的各个属性来编辑配置表,然后再去实现具体功能。