[开发者心得] 分享一套轻松易用的上下文开发框架(含示例工程)

[复制链接]
62 |3

本帖最后由 不会游泳的鱼 于 2024-11-9 22:28 编辑

前言

实现目标

👀️ 众所周知,当前口袋方舟编辑器的代码开发方式是基于模块的,存档是基于DataCenter,对于刚刚接触编程的新手来说,理解起来成本很大,服务器和客户端常常搞的很混乱,各个模块的功能划分也很困难。


为了解决这个问题,设计了一套基于角色的服务端客户端编程套件,新手用户不用关心模块,数据存储,数据的读取和同步等大部分理解成本过高的问题。该套件会自动的完成以上功能,用户只需要关心代码需要在服务端还是客户端运行就好了🚀️

设计思路

设计分为世界上下文和玩家上下文两个部分

整体结构

UML类图.jpg

世界上下文

世界上下文负责世界(整个房间)的事件,客户端/服务端通讯,效果表演以及消息通知。设想一个场景,你需要驱动整个世界的光照在所有玩家的屏幕里按照时间来运行,你可以实现一个世界光照上下文,在这个系统里更改所有用户的光照


@SystemType.WorldSystem()
export class WorldLighterSystem extends BaseWorldSubSystem {

    private currentClientTime: number = 0;
    public onStart() {
        this.bTicked = SystemUtil.isClient();
    }

    onUpdate(dt: number) {
        if (this.currentClientTime != this.system.Attribute_WorldtimeMinute) {
            this.currentClientTime = this.system.Attribute_WorldtimeMinute;
            this.changeLighter();
        }
    }

    private changeLighter() {
        const minuteOffset = Math.abs(this.currentClientTime - 720);//720-0-720
        mw.Lighting.directionalLightIntensity = 6 - minuteOffset * 0.007;
        mw.Lighting.skyLightIntensity = 1 - minuteOffset * 0.001;
    }

}

以上代码就是实现光照系统所需要关注的所有代码,通过“SystemType.WorldSystem”装饰器,系统会自动运行起来。

玩家上下文

玩家上下文属性的使用场景在于管理玩家的状态和持久化数据、玩家的服务端和客户端通讯。甚至玩家之间的通讯也可使用。时刻记住,每个玩家都有一套单独的玩家上下文系统,并且这个系统包括玩家的所有子系统

一个常见的例子,我们在游戏中需要管理玩家的外观,可以设计一个外观子系统来实现这个目的。

/**
 * 玩家装扮系统
 */
@SystemType.CharacterSystem()
export class AppearanceSubSystem extends BaseCharacterSubSystem {
    public onStart() {
        this.system.addAttributeChangeEvent("Appearace_RoleId", this.onAppearaceRoleIdChanged, this);
    }

    private onAppearaceRoleIdChanged(roldId: string) {
        this.system.character.setDescription([roldId]);
    }
    @SystemType.Client()
    public OpenAppearaceUI() {
        UIService.show(AppearaceMainUI);
    }
    @SystemType.Server()
    changeDress(id: string) {
        this.system.Appearace_RoleId = id;
    }
}

以上就是一个外观系统的基本实现代码,包括打开UI,更换玩家的外观以及同步给所有其他人该玩家外观,其中有几个@SystemType开头的装饰器,后文用户手册部分会详细说明。

玩家存档

😄 玩家存档已经融于玩家上下文系统,下面一段话为运行原理,一般使用只需参考用户手册中的保存玩家属性部分。

目前存档只存在于玩家身上,通过在玩家上下文(CharacterContext)中使用@SystemType.Save装饰器来保存字段,你可以随意定义装饰器来保存你需要保存的属性,这些属性在玩家下线的时候会自动保存,再次上线的时候也会自动读取,如果有属性同步标识,也将会自动在所有玩家的客户端自动触发属性同步,这些都是完全自动的。😕 需要注意的是存档数据不能保存类对象和Map类型。

属性系统

在游戏中,常常需要知道别的玩家身上或者房间内的一些属性。比如敌对玩家的血量,世界BOSS的血量等等。上面例子中的世界时间,玩家身上的装扮都是通过这套系统来实现的,结合玩家属性系统,可以很方便的做出持久化的表现数据。

上下文系统中玩家属性定义为3种

  1. 保存且需要同步的属性
  2. 保存但不需要同步的属性
  3. 不保存但需要同步的属性

用户手册部分会一一介绍这几种属性。需要牢记,更改属性的函数必须运行在服务端才能真实生效。🚀️

用户手册

通用函数

@SystemType.Server()这个装饰器表示该函数是在服务端运行

   @SystemType.Server()
    changeDress(id: string) {
        this.system.Appearace_RoleId = id;
    }

@SystemType.Multicast()这个装饰器表示该函数在所有客户端运行

  //给所有客户端发个消息 
  @SystemType.Multicast()
    sendMessage(msg: string) {
        console.log(msg);
    }

玩家上下文函数

@SystemType.CharacterSystem()将子系统注册到玩家上下文,同时激活该系统

@SystemType.CharacterSystem()
export class AppearanceSubSystem extends BaseCharacterSubSystem {
}

@SystemType.Client()这个装饰器表示该函数在当前玩家的客户端运行

  @SystemType.Client()
    public OpenAppearaceUI() {
        UIService.show(AppearaceMainUI);
    }

@SystemType.ToClient()这个装饰器表示该函数需要在指定玩家的客户端运行,第一个参数必须为mw.Player,表示运行这个函数的客户端,最后一个可选参数为发起者

    //给指定玩家发个消息
    @SystemType.ToClient()
    sendMessageTo(player:mw.Player,msg: string,from?:mw.Player) {
        console.log(`${from.userId}对${player.userId}说`,msg);
    }

世界上下文函数

@SystemType.WorldSystem()将子系统注册到世界上下文,同时激活该系统

@SystemType.WorldSystem()
export class LighterSystem extends BaseWorldSubSystem {
    public onStart() {
    }

    onUpdate(dt: number) {

    }

}

属性系统

玩家属性

玩家属性仅能在CharacterContext类中定义,针对上文提到的3种不同类型的属性,使用如下装饰器可以很容易的进行定义。

保存且需要同步的属性

  //经验值
    @SystemType.Save("exp")
    public Attribute_Exp: number = 0;

保存但不需要同步的属性

  @SystemType.Save("role",false)
    public Appearace_RoleId: string = "";

不保存但需要同步的属性

  @mw.Property({replicated:true})
    public Appearace_face: string = "";

世界属性

目前世界属性没有设计保存接口,故只有一种世界属性,即不保存但需要同步的属性,使用@mw.Property({ replicated: true })即可,示例如下

   /**
     * 当前世界时间-以秒计算
     */
    @mw.Property({ replicated: true })
    Attribute_WorldtimeSecond: number = 0;

存档函数

存档的读取和写入都是自动进行,系统会自动管理存档,不需要进行任何操作,Enjoy!

性能优化

对于单帧的多次调用,玩家上下文系统会合并成一次调用来减少RPC消耗,防止RPC溢出导致的断线。有经验的使用者可以结合项目实际做更多的优化。

RPC优化在Demo项目的CharacterContext类中实现,可以看到是将远程调用做了合批处理。

DEMO

由于帖子无法插入附件,DEMO见第一条回帖

此DEMO包含上文提到的所有用法,不懂的地方可以运行DEMO,随意更改代码看看效果即可明白

后言

该方案为临时兴趣所致,后续不保证对方案的扩展性维护,同时方案中可能的一些BUG也需要一定代码理解能力,遇到问题可以尝试自己先解决,同时可以在帖子后留言,有时间我会解答,但不保证时效性。你可以参照玩家上下文和世界上下文的实现,设计自己的上下文,做出更多易用效果。同时,也欢迎改进DEMO中上下文系统本体的性能和易用性,帮助更多用户👍

回复

使用道具 举报

不会游泳的鱼楼主 发表于 4 天前 | 显示全部楼层
ProjectContext.zip (3.04 MB, 下载次数: 2)
回复

使用道具 举报

帅翁 发表于 前天 10:31 | 显示全部楼层
研读框架中!
回复

使用道具 举报

复读机读复读机 发表于 前天 10:40 | 显示全部楼层
太强了,我赶紧试试Demo
回复

使用道具 举报

热门版块
快速回复 返回顶部 返回列表