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

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会是差不多的么?

我们是不是也要在GameObjectHeartBeat里面去搞个又粗又长的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)。

再修改GameAnimalHeartBeat

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’才能进行心跳。
这样,通过调整GameCharacterSpeed,就可以控制每个角色的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#服务端的微信小游戏——多人在线角色扮演(八)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值