您的位置:首页 > 移动开发 > 微信开发

C#服务端的微信小游戏——多人在线角色扮演(七)

2019-06-15 13:45 351 查看
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/foomow/article/details/92081648

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
的心跳。
我们先梳理一下游戏的需求,大致需要以下几类:

  1. 地形(Terrain),基本地图特征的元素,不可破坏和移动,这样可以使地图保持一个相对固定的环境,不至于三天不上线就找不到方向了。
  2. 角色(Character),可以移动、交互的元素,例如小猫小狗,包括玩家、NPC、动物等。
  3. 资源(Resource),可以进行采集的元素,比如树木、岩石,通过采集可以获得资源。
  4. 材料(Material),可以拾取使用的元素,如木材、石材等。
  5. 武器(Weapon),呵呵
  6. 防具(Armor),嘿嘿
  7. 药品(Herb),嘻嘻
  8. 货币(Currency),第五套人民币,除了不能提现,其他功能都一样

好吧,先这么多,其他以后想到再陆续添加。
我们注意到,材料、武器、防具、药品、货币这些,又可以归类于可携带(Carriable)元素,也就是可以被拾取、交易、使用或属于装备(Equip)类型的元素。

所以,我们计划建立以下类架构:

GameObject地形角色可携带资源NPC玩家动物材料装备武器防具药品货币树木岩石

依据上图,我们修改

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;

(很简单吧,下次添加类似代码大家自己搞吧,贴代码太占篇幅了)

然后调试一个看看:

效果好极了,这一篇我们把地图内容的实现梳理了一个框架出来,在这个框架内添加和实现更多的功能就比较容易了,下一篇,我们要讨论一下移动与地图的关系,让走位更加风骚~!

上一篇:C#服务端的微信小游戏——多人在线角色扮演(六)
下一篇:C#服务端的微信小游戏——多人在线角色扮演(八)

请用微信扫描查看游戏效果演示

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: