C#服务端的微信小游戏——多人在线角色扮演(七)
物以类聚,人以群分。游戏世界更是如此,合理的规划类的关系,对开发质量有很大的帮助。
——茂叔
小猫也来了,但是……
上一篇,我们实现了用ObjectFactory
来生成地图的内容——一只小狗。
那如果我们要生成一只小猫呢?是不是代码应该是这样的:
……
case ObjectClassID.DOG:
{
ret = new GameObject(LOGFUN)
{
Name = "小狗旺财",
Description = "一只小狗,就是你",
CanPass = false,
ClassID = ClassID,
Category = ObjectCategory.ANIMAL,
ImgFileID = 0,
ImgID = 0,
Status = ObjectStatus.IDEL,
StatusStep = 0,
OwnerEID = null,
Inventory = new GameObject[8]//可以带8个物品
};
}
break;
case ObjectClassID.CAT:
{
ret = new GameObject(LOGFUN)
{
Name = "小猫咪咪",
Description = "一只小猫,好可爱",
CanPass = false,
ClassID = ClassID,
Category = ObjectCategory.ANIMAL,
ImgFileID = 0,
ImgID = 2,
Status = ObjectStatus.IDEL,
StatusStep = 0,
OwnerEID = null,
Inventory = new GameObject[8]//可以带8个物品
};
}
break;
……
拷贝粘贴,稍作修改,半分钟不到,一只小猫就实现了。然后再到地图内容清单那里加入一下就可以了。
继续添加草地、岩石也很容易实现。
小猫小狗心跳HeartBeat
应该也区别不大,跑跑跳跳见人就靠而已,但是小猫小狗和草地岩石,它们的心跳HeartBeat
会是差不多的么?
我们是不是也要在GameObject
的HeartBeat
里面去搞个又粗又长的switch
语句来分别实现他们的心跳呢?
这显然一点也不硬核。
地图内容分类
所以,我们需要从GameObject
派生出不同的类,来分别处理不同类型Category
的心跳。
我们先梳理一下游戏的需求,大致需要以下几类:
- 地形(Terrain),基本地图特征的元素,不可破坏和移动,这样可以使地图保持一个相对固定的环境,不至于三天不上线就找不到方向了。
- 角色(Character),可以移动、交互的元素,例如小猫小狗,包括玩家、NPC、动物等。
- 资源(Resource),可以进行采集的元素,比如树木、岩石,通过采集可以获得资源。
- 材料(Material),可以拾取使用的元素,如木材、石材等。
- 武器(Weapon),呵呵
- 防具(Armor),嘿嘿
- 药品(Herb),嘻嘻
- 货币(Currency),第五套人民币,除了不能提现,其他功能都一样
好吧,先这么多,其他以后想到再陆续添加。
我们注意到,材料、武器、防具、药品、货币这些,又可以归类于可携带(Carriable)元素,也就是可以被拾取、交易、使用或属于装备(Equip)类型的元素。
所以,我们计划建立以下类架构:
依据上图,我们修改ObjectCategory
的定义为:
public enum ObjectCategory : ushort
{
NONE = 0,
TERRAIN = 1,
CHARACTER = 2,
PLAYER = 3,
NPC = 4,
ANIMAL = 5,
CARRIABLE = 6,
MATERIAL = 7,
EQUIP = 8,
WEAPON = 9,
ARMOR = 10,
HERB = 11,
CURRENCY = 12,
RESOURCE = 13,
PLANT = 14,
ROCK = 15
}
然后,分别新建类,其继承关系严格按照图片所示,具体类的成员可以暂时不管,等今后再来添加。只是在构造函数里面把Catagory
设置为相应的值。
为了防止命名冲突,我们统一使用Game作为类名的前缀。
class GameObject : Existence
{
public string Description;
public ExistenceID MapEID;
public sbyte X;
public sbyte Y;
public ObjectCategory Category;
public ObjectClassID ClassID;
public ObjectStatus Status;
public byte StatusStep;
public byte ImgFileID;
public byte ImgID;
public bool CanPass;
public GameObject[] Inventory;
public ExistenceID OwnerEID;
public GameObject(Action<string> LOGFUN = null) : base(LOGFUN)
{
LOG("GameObject(" + EID + ")创建成功");
}
public override void HeartBeat()
{
LOG("GameObject[" + Name + "](" + EID + ")正在心跳 " + G.GlobeTime);
}
}
class GameTerrain : GameObject
{
public GameTerrain(Action<string> LOGFUN = null) : base(LOGFUN)
{
Category = ObjectCategory.TERRAIN;
}
}
class GameCharacter : GameObject
{
public GameCharacter(Action<string> LOGFUN = null) : base(LOGFUN)
{
Category = ObjectCategory.CHARACTER;
}
}
class GamePlayer : GameCharacter
{
public GamePlayer(Action<string> LOGFUN = null) : base(LOGFUN)
{
Category = ObjectCategory.PLAYER;
}
}
class GameNPC : GameCharacter
{
public GameNPC(Action<string> LOGFUN = null) : base(LOGFUN)
{
Category = ObjectCategory.NPC;
}
}
class GameAnimal : GameCharacter
{
public GameAnimal(Action<string> LOGFUN = null) : base(LOGFUN)
{
Category = ObjectCategory.ANIMAL;
}
}
class GameCarriable : GameObject
{
public GameCarriable(Action<string> LOGFUN = null) : base(LOGFUN)
{
Category = ObjectCategory.CARRIABLE;
}
}
class GameMaterial : GameCarriable
{
public GameMaterial(Action<string> LOGFUN = null) : base(LOGFUN)
{
Category = ObjectCategory.MATERIAL;
}
}
class GameEquip : GameCarriable
{
public GameEquip(Action<string> LOGFUN = null) : base(LOGFUN)
{
Category = ObjectCategory.EQUIP;
}
}
class GameWeapon : GameEquip
{
public GameWeapon(Action<string> LOGFUN = null) : base(LOGFUN)
{
Category = ObjectCategory.WEAPON;
}
}
class GameArmor : GameEquip
{
public GameArmor(Action<string> LOGFUN = null) : base(LOGFUN)
{
Category = ObjectCategory.ARMOR;
}
}
class GameHerb : GameCarriable
{
public GameHerb(Action<string> LOGFUN = null) : base(LOGFUN)
{
Category = ObjectCategory.HERB;
}
}
class GameCurrency : GameCarriable
{
public GameCurrency(Action<string> LOGFUN = null) : base(LOGFUN)
{
Category = ObjectCategory.CURRENCY;
}
}
class GameResource : GameObject
{
public GameResource(Action<string> LOGFUN = null) : base(LOGFUN)
{
Category = ObjectCategory.RESOURCE;
}
}
class GamePlant : GameResource
{
public GamePlant(Action<string> LOGFUN = null) : base(LOGFUN)
{
Category = ObjectCategory.PLANT;
}
}
class GameRock : GameResource
{
public GameRock(Action<string> LOGFUN = null) : base(LOGFUN)
{
Category = ObjectCategory.ROCK;
}
}
好,建好以后,都在一个文件GameObject.cs
里面,这样一目了然。将来需要细化的时候,再来移动到单独的文件里面。
走两步试试?
现在,我们先把GameCharacter
拿出来,选中类名,按alt+enter调出菜单,选择移动到新文件。然后把它的子类GamePlayer
GameNPC
GameAnimal
也移到同一个文件里。
然后给GameCharacter
加上一个方法Move(Direction dir)
。
其中Direction
定义如下:
public enum Direction : byte
{
NONE = 0,
EAST = 1,
WEST = 2,
NORTH = 3,
SOUTH = 4
}
记住要写在Type.cs
里面哦。
Move(Direction dir)
的代码也很简单:
public void Move(Direction dir)
{
switch (dir)
{
case Direction.NONE:
break;
case Direction.EAST:
X++;
break;
case Direction.WEST:
X--;
break;
case Direction.NORTH:
Y--;
break;
case Direction.SOUTH:
Y++;
break;
default:
break;
}
}
接下来,我们把GameAnimal
类也移到单独的文件里,并且覆盖(Override
)GameAnimal
类的HeartBeat
让它随机移动:
public override void HeartBeat()
{
base.HeartBeat();
Direction dir = (Direction)G.RND((int)Direction.EAST, (int)Direction.SOUTH);
Move(dir);
LOG(Name + " 向 " + dir.ToString() + " 走去。当前坐标(" + X + ":" + Y + ")");
}
把ObjectFactory
创建小狗的相关代码修改一下,直接创建一个GameAnimal
类:
……
case ObjectClassID.DOG:
{
ret = new GameAnimal(LOGFUN)//这里原来是GameObject
{
Name = "小狗旺财",
……
调试一下,看,我们的小狗可以移动了。
不过仔细一看,一秒钟跑了10次……太快了吧~!
如果我们注释掉其他无关的心跳日志,你会看到它一秒钟起码跑40几次,如果跑不到40次以上,你的电脑该换一台了……
更要命的是,不可能所有的角色都是一个速度,乌龟不可能比兔子慢啊!
速度,必须可控~!
控制速度的算法
给GameCharacter
加上一些内容:
……
public byte Speed = 128;
private byte SpeedBuff = 0;
public bool IsHeartReady
{
get
{
if (Speed + SpeedBuff == 255)
return true;
else
return false;
}
}
public override void HeartBeat()
{
if (Speed + SpeedBuff == 255)
{
SpeedBuff = 0;
base.HeartBeat();
}
SpeedBuff++;
}
……
这是通过SpeedBuff
不断增加,直到与Speed
加起来等于255,这样的算法,使得Speed
可以取值0-255以控制速度,数值越大越快(达到255)。
再修改GameAnimal
的HeartBeat
:
public override void HeartBeat()
{
base.HeartBeat();
if (IsHeartReady)
{
Direction dir = (Direction)G.RND((int)Direction.EAST, (int)Direction.SOUTH);
Move(dir);
LOG(Name + " 向 " + dir.ToString() + " 走去。当前坐标(" + X + ":" + Y + ")");
}
}
注意,凡是GameCharacter
的派生类,都需要判断’IsHeartReady’才能进行心跳。
这样,通过调整GameCharacter
的Speed
,就可以控制每个角色的HeartBeat
速度了。
好了,把小猫也加到地图清单里面去,这个应该人人都能自己搞定吧……修改GameWorld
的构造函数:
public GameWorld(Action<string> LOGFUN = null) : base(LOGFUN)
{
gMaps = new List<GameMap>();
LOG("GameWorld(" + EID + ")创建成功");
GameMap Map = new GameMap(LOG);
Map.MapItems.Add(new MapItem()
{
ClassID = ObjectClassID.DOG,
X = 1,
Y = 1
}
);
Map.MapItems.Add(new MapItem()
{
ClassID = ObjectClassID.CAT,
X = 1,
Y = 2
}
);
Map.Refresh();
gMaps.Add(Map);
}
把ObjectFactory
关于小猫小狗的部分修改成下面这样,注意小猫速度不同。
case ObjectClassID.DOG:
{
ret = new GameAnimal(LOGFUN)
{
Name = "小狗旺财",
Description = "一只小狗,就是你",
CanPass = false,
ClassID = ClassID,
ImgFileID = 0,
ImgID = 0,
Status = ObjectStatus.IDEL,
StatusStep = 0,
OwnerEID = null,
Inventory = new GameObject[8]//可以带8个物品
};
}
break;
case ObjectClassID.CAT:
{
ret = new GameAnimal(LOGFUN)
{
Name = "小猫咪咪",
Speed = 60,
Description = "一只小猫,好可爱",
CanPass = false,
ClassID = ClassID,
ImgFileID = 0,
ImgID = 2,
Status = ObjectStatus.IDEL,
StatusStep = 0,
OwnerEID = null,
Inventory = new GameObject[8]//可以带8个物品
};
}
break;
(很简单吧,下次添加类似代码大家自己搞吧,贴代码太占篇幅了)
然后调试一个看看:
效果好极了,这一篇我们把地图内容的实现梳理了一个框架出来,在这个框架内添加和实现更多的功能就比较容易了,下一篇,我们要讨论一下移动与地图的关系,让走位更加风骚~!