Skip to content

Avatar时装、装备如何制作 ?

ilvxna edited this page Aug 7, 2018 · 2 revisions

: 现在需要实现时装、装备的功能,思路是应该将Avatar进行拓展,目前我的想法是做了一个Cloth类,里面实现的对服务器做的请求,然后将Avatar里面加入一个Cloth类(图1)的成员变量,服务器回调的函数都全部写在Avatar(图2)中,现在出现一个问题,在其他角色进入游戏世界的时候,底层回调用Client_onEntityEnterWorld(MemoryStream stream)这个函数(图3),在这个函数里面进行Entity的组装,我现在想知道的是,我如何从这个stream去获取服务器传给我的Cloth的信息,我需要这个信息去对entity进行组装。我看这个里面基本都是stream.readInt32()这一类的方法,难不成我要以后往Avatar中加入装备、技能、属性等信息的时候还要去拓展这个stream? Alt text Alt text Alt text

: 写游戏逻辑不要尝试去修改插件层,你就当做那里是只读禁区好了,除非你要改引擎特性。 你只需要在kbe_scripts里实现就行了。

我没看懂你的Cloth是一个道具还是只玩家的装扮表现。

如果是我来做的话: 1:给玩家添加一个表现类结构,就叫Ornament好了, 玩家包含这个属性(ALL_CLIENTS),并且属性是自动广播同步客户端的。

Ornament{
手套物品编号
护腿物品编号
头盔物品编号
铠甲:。。。

}

2:时装、装备、药品都从物品基础类继承而出, 不同的是时装和装备被装备到身上和卸下时会修改玩家的Ornament

例如:

class 头盔装备def 装备到身上Avatar):
          。。。
          。。。
          Avatar.Ornament. 头盔 = self.modelID

    def 卸载Avatar):
          。。。
          。。。
          Avatar.Ornament. 头盔 = 0
    

客户端应该在如下接口中获得玩家的Ornament并且表现到3D世界中去。

Avatar.cs:
public override void onEnterWorld()

具体你可以参考角色列表如何同步和在客户端是如何获取并且给到3D渲染层表现的。

你想服务器不广播由客户端主动获取也行, 看看Avatar的onEnterWorld是如何获取技能列表的

:这个我和服务器进行讨论了一些,他决定在服务器端将cloth(时装)、equip(装备)做成和friends一样的模式 ,统一从Avatar中传过来,我现在在客户端需要做的就是怎么样获取到传过来的这些数据封道我在客户端Avatar中定义的相应的Cloth/Equip中 ,我发现在KBEngine.cs的onUpdatePropertys_这个方法中可以获取到我想要的数据,然后在函数的后面我看到调用到了setmethod.Invoke(entity, new object[] { oldval });(问题一:这里在上文通过object val = propertydata.utype.createFromStream(stream);和object oldval = entity.getDefinedProptertyByUType(utype);获取了两个值,为什么,为什么是用的Invoke中用的是oldVal而不是val?) ,接上面的那个Invoke函数,这个应该是和set_XX相对应的,那么就是数据是从这个方法中调用Avatar中的set_XX,然后我需要在set_XX中对获取到的数据进行解析,并且封装成我定义的Cloth/Equip这些类放在Avatar中,现在我想看到在Entity中,作者定义了两个字典defpropertys_和iddefpropertys_,这两个字典在Entity的构造函数中有做Add操作,所以我想知道什么时候构造了这个Entity,我往引擎内部找,找到了三个有关的函数,Client_onEntityEnterWorld、Client_onCreatedProxies、Client_onUpdatePropertys, 这个就是问题二:这三个函数调用顺序是怎样的,他和Entity的构造有什么关系,我把运行后的打印截图贴出来(图1)?不要和我说这是引擎内部的东西不需要管之类的,我这个必须要搞清楚的!!

: set_XX就是旧值, 这是你唯一能获得旧值的机会。 新值你在set_XX自己获得, 多参考demo。

                public virtual void set_HP(object old)
                {
                        object v = getDefinedPropterty("HP");
                        Dbg.DEBUG_MSG(className + "::set_HP: " + old + " => " + v); 
                        Event.fireOut("set_HP", new object[]{this, v});
                }

kbe_scripts层的entity可能会在2个地方创建(

Client_onEntityEnterWorld
Client_onCreatedProxies

一个用来创建其他实体,一个用来创建角色)。

Client_onEntityEnterWorldClient_onCreatedProxies、Client_onUpdatePropertys

这3个消息接口都由服务端调用, 分别表示 其他实体进入世界了(可理解为其他实体进入了服务器角色实体的AOI,在你的客户端要创建这个实体了或者你的角色实体创建了cell部分进入了场景)、proxy实体创建了(通常不是账号实体就是角色实体)、实体属性更新了(如你所说如果实现了set_**表示你想监听某个属性的改变,你的代码只需要关注这里) 如果是角色, 一定会是

Client_onCreatedProxies

先触发,角色第一次进入cellapp场景时才会触发

Client_onEntityEnterWorld

, 之后如果不销毁cell切换场景时只会触发

Client_onEntityEnterSpace

而怪进入你的客户端只会触发

Client_onEntityEnterWorld。

关于Client_onUpdatePropertys: 你可以理解为Client_onUpdatePropertys会在他们之前,

Client_onEntityEnterWorldClient_onCreatedProxies

一旦触发表示实体相关的数据都已经准备好了, 创建出来就可用了。

你在你的逻辑层应该做的事情就是遇到什么事件触发就去做逻辑该做的, 例如:有实体进入世界了你就在3D世界创建出来,进入了场景就应该切换场景。

: 这两个里面来主动调用的你在world中实现的类似这样的方法:

    /// <summary>
    /// 服务器回调设置HP
    /// </summary>
    public void set_HP(KBEngine.Entity entity, object v)
    {
        if (entity.renderObj != null)
        {
            //((GameObject)entity.renderObj).GetComponent<GameEntity>().hp = "" + (Int32)v + "/" + (Int32)entity.getDefinedPropterty("HP_Max");
        }
    }

,这些个方法也会收到fireOut的调用,而用到set_XX这些的地方是你在游戏中再次调用的onUpdatePropertys_()时候,这时候isWorld已经是true了,这时set_XX才会起作用,你是不是这个用意?

: 首先要明确的是set_*仅针对于数据发生改变之后触发,Client_onEntityEnterWorld的同时传过来的是属性在实体进入世界之前的初始化数据,并没有发生改变所以不会触发set方法 。

服务端同步给客户端的属性分2种, 一种是cell同步过来的一种是base同步过来的, 对应的同步标记大概是 BASE_AND_CLIENT或者CELL_PUBLIC_AND_OWN之类的。

base同步过来实体既可用了, 不需要任何的等待, 为了方便属性一同步到客户端就可以立即触发set_,其实正确的流程是应该也加一个pending期,初始化不触发(这个以后再考虑)。
cell上的实体传输到客户端分为几个阶段, 第一个阶段传输了他的属性数据, 此时实体处于pending期并不可用, 直到实体进入世界Client_onEntityEnterWorld 才变得可用, 但实体可用之时所有的属性应该都已经同步好,可以直接访问了。 所以在这个过程中实体的属性之前就传输过来了, 客户端一直缓存着,等获得实体真正进入世界的消息的时候会先将实体的属性数据填充好,而这个填充期不应该触发set_
(因为是初始值并未发生改变), 填充好了之后才进入世界。 在进入世界之后的属性发生任何赋值行为才会触发set_*。

对于cell属性, 你应该在表现层onEnterWorld时主动获取一次数据做表现,进入世界之后就可以依赖set_*触发的事件了。

world.cs
        public void onEnterWorld(KBEngine.Entity entity)
        {
                if(entity.isPlayer())
                        return;
                
                float y = entity.position.y;
                if(entity.isOnGround)
                        y = 1.3f;
                
                entity.renderObj = Instantiate(entityPerfab, new Vector3(entity.position.x, y, entity.position.z), 
                        Quaternion.Euler(new Vector3(entity.direction.y, entity.direction.z, entity.direction.x))) as UnityEngine.GameObject;

                ((UnityEngine.GameObject)entity.renderObj).name = entity.className + "_" + entity.id;

                set_position(entity);
                set_direction(entity);
                                                
                object speed = entity.getDefinedPropterty("moveSpeed");
                if(speed != null)
                        set_moveSpeed(entity, speed);
                
                object state = entity.getDefinedPropterty("state");
                if(state != null)
                        set_state(entity, state);
                
                object modelScale = entity.getDefinedPropterty("modelScale");
                if(modelScale != null)
                        set_modelScale(entity, modelScale);
                
                object name = entity.getDefinedPropterty("name");
                if(name != null)
                        set_entityName(entity, (string)name);
                
                object hp = entity.getDefinedPropterty("HP");
                if(hp != null)
                        set_HP(entity, hp);
        }
Clone this wiki locally