Skip to content
V0.29.0.0 Release Note

V0.29.0.0 Release Note

注意事项

  1. 高级轮式载具API停用:

继v0.26停用旧载具逻辑对象后,对应WheeledVehicle4W不再提供技术支持,请替换为AdvancedVehicle;v0.29将停用WheeledVehicle4W,请及时替换新的类名。

  1. 升级后,原工程存放会多一个层级目录,原有工程文件会变为该目录子文件,可能会造成 git 无法定位的问题,解决方案详见:028升级后git无法定位修改指南

  2. 原旧工程目录(...\MetaWorldSaved\Saved\MetaWorld\Projects),029版将不再兼容维护,请将原旧目录下的工程文件移到新工程目录(...\MetaWorldSaved\Saved\MetaWorld\Project\Edit)

  3. UI控件-容器的布局功能中的边缘间距属性中的右边距和下边距此前没有生效,在029上修复了这个问题,若如果有项目中的容器内容由于这个调整导致了变化,只需要重新调整一下容器大小即可

img

  1. 在之前某个版本中,项目保存时有可能错误的存入了一个本不应该暴露的的颜色属性【TintColor】,此前UI没有应用到这个属性,而在029上这个属性会生效,所以如果此前修改过这个属性会导致029上 UI 颜色看起来跟之前不一样,如果出现这类问题,请把UI文件里面的这个数据清空即可解决

  2. 特效的遮罩颜色功能将废弃:目前暴露了特效粒子的颜色可以用于修改特效的颜色效果,遮罩颜色后续我们将进行隐藏和废弃处理,后续将不再生效。

  3. 029版本Animation对象新增属性结构有所改动,导致某些项目中自己定义的Animation对象出现重定向报错提示。

    • 由于在027版本API自动升级过程中兼容脚本存在自己定义的Animation对象,因此为避免经历027自动升级的项目在029版本打开工程报错,我们在打开工程时会自动对老的兼容脚本进行刷脚本修改,以解决此类报错问题。

    • 由于部分项目仍存在将兼容脚本的对象进行导出使用的情况,029的报错修复判定逻辑无法兼容去执行批刷。因此对于那些用29编辑器打开依然还有Animation相关的编译报错需要根据当时的使用方法自行修改,修改方法:

      将工程中 ModifiedPlayer 中 RpcAnimation 的检测到 ts 报错处根据下图,添加相关对象及方法,不出现报错提示即可。

img

新增功能

一、编辑器交互及发版策略

[优化]DS版本聚合

将同一引擎大版本号内所有引擎小版本,统一匹配对应大版本号的最新DS版本,降低小版本(Hotfix)造成的玩家分流

[优化]画质分级模拟中,匹配用户占比更加准确

将GPU性能分级和CPU性能分级中,每个画质级别的匹配用户占比更改为动态请求C端埋点数据的用户占比,便于开发者根据C端的情况设置游戏画质分级。

每个等级的匹配用户比例之后会根据我们的埋点数据动态调整,可以通过这个数据了解当前平台用户的性能分级占比,从而更精准的调节自身游戏的画质等级来满足更大范围的用户。

img

[优化]网格数值支持自定义输入

旧版本中,启用网格功能后,仅能从预设值中选择网格属性;

新版本中,启用网格功能后,可以点击下拉按钮,从预设值中选择网格数值,也可以直接输入数值(移动工具支持1-1000的整数,旋转工具支持1-360的整数,缩放工具支持0.0625-10.0的小数)。

img

[优化]Transform工具优化

  • 移动工具:优化了主视口中Move Gizmo的设计

    img

  • 旋转工具:优化主视口中Rotate Gizmo的功能与设计

    img

    • 拖动红色/绿色/蓝色枢轴,选中对象沿着本地/世界坐标系中的对应轴向(X/Y/Z轴)进行旋转。

      img

    • 拖动外围的白色枢轴,选中对象沿着当前视图进行平面旋转。

      img

    • 拖动球状区域,选中对象进行轨迹球式旋转。

      img

  • 缩放工具:新增缩放模式选择(“轴式/盒式”),优化主视口中Scale Gizmo的功能与设计

    img

    img

    • 盒式缩放:

      • 拖动红色/绿色/蓝色控制点,选中对象沿其本地坐标系中对应轴向(X/Y/Z轴)与控制点方向进行缩放。

        img

        • 按住Shift键,选中对象沿控制点方向进行三轴等比缩放。

          img

        • 多选时,选中对象沿控制点方向进行三轴等比缩放。

          img

    • 轴式缩放:

      • 拖动红色/绿色/蓝色枢轴,选中对象沿其本地坐标系中对应轴向(X/Y/Z轴)进行缩放。

        img

      • 拖动红色/绿色/蓝色三角面,选中对象沿其着本地系中对应平面(YZ/XZ/XY平面)进行缩放。

        img

      • 拖动轴式Scale Gizmo中心点,选中对象进行三轴等比缩放。

        img

[新增]“中心/锚点”模式

使用Transform工具时,可以切换“中心/锚点”模式,以改变主视口中Transform Gizmo的位置,以及选中对象旋转/缩放的方式

img

  • 选择“中心”模式时,Transform Gizmo位于选中对象的几何中心处;选择“锚点”模式时,Transform Gizmo位于选中对象的锚点处(多选时,位于最后选中对象的锚点处)。

    img

  • 选择“中心”模式时,选中对象将相对于其几何中心进行旋转/缩放(多选时,相对于全部选中对象的几何中心进行旋转/缩放);选择“锚点”模式时,选中对象将相对于其锚点进行旋转/缩放(多选时,相对于各自锚点进行旋转/缩放)。

    img

    img

    TIP

    当前使用代码或者在属性面板上修改物体的Transform,都是以锚点模式进行修改。

[新增]Tab键召唤Gizmo功能

使用Transform工具时,如果模型过大,Gizmo不在主视口镜头之内,可以按住Tab键,在光标位置召唤Gizmo(松开Tab键,Gizmo恢复到默认位置)

img

[新增]预制体支持脚本动态生成的UI资源

如果预制体中的脚本,带有动态生成UI逻辑时,预制体会在导出时扫描脚本,将import的UI资源文件一起导出

imgimg

[优化]角色属性面板调整

  • 我们重新调整了角色属性面板和逻辑对象的属性的归类和排序,确保看起来更加整洁,方便使用查找。

二、性能相关优化

[优化]对象getter动静态转化

目前模型Model动静态转化条件再次放开:

  • GameObject中除包围盒get接口(3个),其余get接口(getChildren & parent)均不触发动静态转化。
  • Model中物理模拟相关属性(除开physicsEnabled)的getter均不触发动静态转化。
  • 由于getter不转化,之前访问parent,或者涉及访问parent对象属性的操作例如可见性造成的批量转化卡顿,后续将不再出现。

目前仍会导致转化的getter接口:

GameObjectModel
说明接口说明接口
获取对象边界getBounds(onlyCollidingComponents: boolean, originOuter: Vector, boxExtentOuter: Vector, includeFromChildActors?: boolean): void启用物理模拟physicsEnabled
获取对象包围盒大小getBoundingBoxExtent(nonColliding?: boolean, includeFromChildActors?: boolean, outer?: Vector): Vector;获取当前拥有的材质实例getMaterialInstance(): Arraymw.MaterialInstance
获取所有子对象包围盒中心点getChildrenBoundingBoxCenter(outer?: Vector): Vector

[优化]ISM支持切换LOD和裁剪阴影

  • 此前,场景中的完全相同的模型会合并为ISM(Instanced Static Mesh),而这些ISM都会合在一起采用相同的LOD级别,LOD级别偏低,性能比较差;并且也都会统一渲染阴影或者统一不渲染阴影,当自定义裁剪距离小于Lighting-阴影距离时,会有模型被裁剪掉但阴影仍然保留的现象。

  • 本次更新后,ISM支持拆分单独切换LOD和裁剪阴影:

    • 例如下方视频中,随着距离越来越远,较远处的草的细节发生变化,切换了较高级LOD,而较近处的草的细节保持不变;

    • 而下方动图中,如果自定义裁剪距离小于阴影距离,ISM模型被裁剪,其阴影能被一起裁剪掉;

      img

    • 这项改动能优化渲染耗时,尤其是场景中使用大量的相同模型,并且这些模型散落在场景的各个位置的情况优化幅度最大,能解决此前发现的华为设备上部分项目使用大量模型导致严重卡顿的问题

[优化]对象动态使用物理模拟相关参数的效果优化

  • 优化了动态开启模型物理模拟时,碰撞表现异常的问题;
  • 优化了动态设置模型物理模拟相关参数后,表现异常的问题;
  • model.physicsEnabled在服务端调用时,仅在服务端开启物理模拟;在客户端调用时,仅在客户端启用物理模拟,不进行同步;双端同时调用,开启物理模拟同步;
  • 优化了底层model物理模拟的流程,减少了一些内部函数的调用次数,提升物理模拟使用性能;
  • 可以对单端物体开启物理模拟,需要同时将单端物体的physticEnabled和collision开启。也可以为单端物体施加模拟力。

三、UI编辑器新增功能及API更新

[优化]按键绑定相关接口补充及调整

  • 除了UI编辑器中的按键绑定菜单可以绑定摇杆与键鼠按键,为了实现切换多套载具/角色运动状态的摇杆UI时能动态调整按键绑定,029新增提供了摇杆绑定/解绑按键的接口;

Joystick类新增接口

函数名称用法说明使用域
添加绑定按键addKey(key: mw.JoystickBindKeyType): void;同一按键同时只能操控一个UI控件,最新绑定的UI控件会覆盖之前的绑定;脚本中添加的绑定无法覆盖编辑器按键绑定菜单中绑定相同按键的UI控件,但当两个UI控件分别通过代码和菜单绑定到同一按键时,使用代码绑定的优先级更高Client only
移除绑定按键removeKey(key: mw.JoystickBindKeyType): void;此操作只会解绑动态绑定的按键,无法解除编辑器按键绑定菜单中绑定的按键Client only
获取所有绑定按键getKeys(): mw.JoystickBindKeyType[];获取当前UI控件绑定的所有键盘按键,包括编辑器按键绑定菜单和用代码绑定的按键Client only

新增JoystickBindKeyType类作为结构体

TypeScript
    class JoystickBindKeyType {
        /**
         * @description 向上的按键
         */
        up: mw.Keys;
        /**
         * @description 向下的按键
         */
        down: mw.Keys;
        /**
         * @description 向左的按键
         */
        left: mw.Keys;
        /**
         * @description 向右的按键
         */
        right: mw.Keys;
        /**
         * @description 不进行赋值等待后续为每个按键单独绑定
         * @effect 只在客户端调用生效
         */
        constructor();
        /**
         * @description 构造一个数据结构传递摇杆需要绑定的按键
         * @effect 只在客户端调用生效
         * @param up usage:上方向的按键 default:undefined
         * @param down usage:下方向的按键 default:undefined
         * @param left usage:左方向的按键 default:undefined
         * @param right usage:右方向的按键 default:undefined
         */
        constructor(up: mw.Keys, down: mw.Keys, left: mw.Keys, right: mw.Keys);
    }
    class JoystickBindKeyType {
        /**
         * @description 向上的按键
         */
        up: mw.Keys;
        /**
         * @description 向下的按键
         */
        down: mw.Keys;
        /**
         * @description 向左的按键
         */
        left: mw.Keys;
        /**
         * @description 向右的按键
         */
        right: mw.Keys;
        /**
         * @description 不进行赋值等待后续为每个按键单独绑定
         * @effect 只在客户端调用生效
         */
        constructor();
        /**
         * @description 构造一个数据结构传递摇杆需要绑定的按键
         * @effect 只在客户端调用生效
         * @param up usage:上方向的按键 default:undefined
         * @param down usage:下方向的按键 default:undefined
         * @param left usage:左方向的按键 default:undefined
         * @param right usage:右方向的按键 default:undefined
         */
        constructor(up: mw.Keys, down: mw.Keys, left: mw.Keys, right: mw.Keys);
    }
typescript
	joystick.addKey(new JoystickBindKeyType(Keys.I, Keys.K, Keys.J, Keys.L));
	const keys = joystick.getKeys();
	console.log("joystick keys", keys.map((key) => { return JSON.stringify(key) }));
	//  joystick keys,{"up":"I","down":"K","left":"J","right":"L"},{"up":"W","down":"S","left":"A","right":"D"},						{"up":"Up","down":"Down","left":"Left","right":"Right"} 
	joystick.removeKey(keys[0]);
	joystick.addKey(new JoystickBindKeyType(Keys.I, Keys.K, Keys.J, Keys.L));
	const keys = joystick.getKeys();
	console.log("joystick keys", keys.map((key) => { return JSON.stringify(key) }));
	//  joystick keys,{"up":"I","down":"K","left":"J","right":"L"},{"up":"W","down":"S","left":"A","right":"D"},						{"up":"Up","down":"Down","left":"Left","right":"Right"} 
	joystick.removeKey(keys[0]);
  • 按钮类控件的按键绑定接口更新:废弃原有的InputUtil类中的bindButton和unbindButton接口,各UI控件类内部新增接口用于新增/移除/获取按键的key;

<Button/StaleButton/MaskButton>类新增接口

函数名称用法说明使用域
添加绑定按键addKey(key: mw.Keys): void;同一按键同时只能操控一个UI控件,最新绑定的UI控件会覆盖之前的绑定;脚本中添加的绑定无法覆盖编辑器按键绑定菜单中绑定相同按键的UI控件,但当两个UI控件分别通过代码和菜单绑定到同一按键时,使用代码绑定的优先级更高Client only
移除绑定按键removeKey(key: mw.Keys): void;此操作只会解绑动态绑定的按键,无法解除编辑器按键绑定菜单中绑定的按键Client only
获取所有绑定按键getKeys(): mw.Keys[];获取当前UI控件绑定的所有键盘按键,包括编辑器按键绑定菜单和用代码绑定的按键Client only

允许玩家使用shift切换鼠标锁定状态默认关闭

  • 此前编辑器在运行游戏时默认使用shift切换鼠标锁定状态,从029起,这一设置将改为默认关闭。如果不使用接口进行设置,点击shift时,将不会锁定鼠标。旧版本中,点击shift会切换鼠标锁定状态,如下所示:

  • 原接口setMouseLockable废弃,更新为mouseLockOptionEnabled;
  • 暴露直接控制鼠标锁定的接口isLockMouse;
    • 也就是说,在代码中,既可以使用isLockMouse直接控制玩家当前的鼠标锁定状态,也可以使用mouseLockOptionEnabled控制是否要允许玩家自行用shift键切换鼠标锁定状态(相当于把isLockMouse的值交给玩家来控制);

InputUtil新增属性

属性名称英文名称类型默认值取值范围说明读写编辑器分组
鼠标锁定isLockMouseBooleanfalse-决定玩家的鼠标是否可以自由移动或是被锁定如果为True,玩家进入鼠标锁定状态,鼠标隐藏并且转动鼠标将操控镜头旋转和人物朝向;无论当前是否允许玩家用快捷键切换鼠标锁定(mouseLockOptionEnabled),开发者都可以使用此接口直接控制玩家的鼠标锁定状态默认值:falseWrite/ReadHiden-
玩家是否可以切换鼠标锁定mouseLockOptionEnabledBooleanfalse-如果为True,玩家在游戏中按下shift键后,玩家可以自行切换鼠标锁定状态;默认值:falseWrite/ReadHiden-

[优化]UI控件自动大小拆分为水平和垂直两个方向

  • UI控件的自动大小属性(autoSizeEnable)废弃,并按方向拆分为两个属性——水平自动大小(autoSizeHorizontalEnable)和垂直自动大小(autoSizeVerticalEnable);

    • 这样可以更灵活的分别控制两个方向的效果,例如,可以实现文本框在垂直方向开启自动大小,按文本内容设置自适应高度,而水平方向仍可以开启自动换行(则此时水平自动大小无法开启),或者水平方向的对齐方式可以设置为自适应/左右对齐,按照父级宽度设置文本框宽度。

      img

Widget类新增属性

属性名称英文名称类型默认值取值范围说明读写编辑器分组
水平自动大小autoSizeHorizontalEnableBooleanfalse-开启自动大小后,UI控件的大小将按照编辑器的默认规则调整为合适大小;例如文本控件开启自动大小将还原至文字内容的大小,图片控件开启自动大小将还原至图片-图片大小;但是自动大小不能与对齐-自适应/上下对齐/左右对齐同时使用;水平自动大小不能与文本自动换行同时使用Write/ReadVisible变换-自动大小
垂直自动大小autoSizeVerticalEnableBooleanfalse-开启自动大小后,UI控件的大小将按照编辑器的默认规则调整为合适大小;例如文本控件开启自动大小将还原至文字内容的大小,图片控件开启自动大小将还原至图片-图片大小;但是自动大小不能与对齐-自适应/上下对齐/左右对齐同时使用Write/ReadVisible变换-自动大小

TypeScript
ui.mTextBtn.onClicked.add(() => {
    // 开启文本水平自动大小
    ui.mTextBlock.autoSizeHorizontalEnable = true;
    setTimeout(() => {
        ui.mTextSize.text = ui.mTextBlock.size.x + "x" + ui.mTextBlock.size.y;
    }, 1);
})
ui.mTextBtn1.onClicked.add(() => {
    // 开启图片水平自动大小
    ui.mImage.autoSizeHorizontalEnable = true;
    setTimeout(() => {
        ui.mImageSize.text = ui.mImage.size.x + "x" + ui.mImage.size.y;
    }, 1);
})
ui.mTextBtn2.onClicked.add(() => {
    // 开启图片垂直自动大小
    ui.mImage.autoSizeVerticalEnable = true;
    setTimeout(() => {
        ui.mImageSize.text = ui.mImage.size.x + "x" + ui.mImage.size.y;
    }, 1);
})
ui.mTextBtn.onClicked.add(() => {
    // 开启文本水平自动大小
    ui.mTextBlock.autoSizeHorizontalEnable = true;
    setTimeout(() => {
        ui.mTextSize.text = ui.mTextBlock.size.x + "x" + ui.mTextBlock.size.y;
    }, 1);
})
ui.mTextBtn1.onClicked.add(() => {
    // 开启图片水平自动大小
    ui.mImage.autoSizeHorizontalEnable = true;
    setTimeout(() => {
        ui.mImageSize.text = ui.mImage.size.x + "x" + ui.mImage.size.y;
    }, 1);
})
ui.mTextBtn2.onClicked.add(() => {
    // 开启图片垂直自动大小
    ui.mImage.autoSizeVerticalEnable = true;
    setTimeout(() => {
        ui.mImageSize.text = ui.mImage.size.x + "x" + ui.mImage.size.y;
    }, 1);
})

TIP

  • 设置自动大小后,需要延迟一帧再获取控件的size,不然会存在不准确的情况。
  • 文本控件只有在水平显示模式为"裁剪"时才能够开启水平自动大小的选项。
    • 应用:自适应姓名框,可以使用此功能根据自动大小后的长度设置姓名框长度。
  • 同时,由于自动大小功能拆开后,与容器的自动布局-自适应规则(UILayout.InHugType)的效果重复,将后者废弃,并从属性面板上隐藏。

更新前:

img

更新后:

img

[优化]任意UI控件支持挂载其他UI控件作为子级

  • 自029起,为了提高UI搭建的自由度,所有UI控件都拥有挂载/查找/移除子级控件的能力,原有的PanelWidget类废弃,PanelWidget类的接口都合并到Widget类里,按钮/容器/滚动框控件改为直接继承Widget类,各种用法都保持不变。
    • UI编辑器设计器中拖拽创建并挂为子级的规则保持不变,如果拖拽到容器/滚动框上方释放创建新控件,新建的UI控件会自动挂载为子级;而如果拖拽其他类型控件上方释放创建新控件,新建的UI控件会创建到同级的最下方(也就是最上层);
  • 但是请注意容器/滚动框所拥有的以下其他两种能力,暂不赋予其他类型UI控件:
    • 作为父级时,容器和滚动框开启自动大小后能根据子级大小/位置来计算自身大小;而其他类型UI控件的自动大小不会有这一功能,这是因为其他类型控件的自动大小有各自原本的功能,比如文本框开启自动大小之后会根据文本内容来调整大小,例如图片开启自动大小后会根据图片-图片大小来调整大小;
    • 容器的自动布局功能比较复杂,为了避免冗余以及和子级对齐方式产生过多冲突,也不赋予其他类型UI控件;

img

注意

避免将多个UI控件同时放入开启了自动大小的容器和滚动框中,可能会出现自动大小和对齐方式冲突的问题。

[新增]富文本中支持以富图片形式使用233娘表情图

  • 新增支持在文本框/输入框控件中使用标记格式插入富图片,首批富图片功能支持使用233娘表情图共93张;

    • 使用方法:将文本框/输入框控件的富文本属性开启,在文字输入富图片的对应标记格式,标记格式会自动转成富图片,富图片的尺寸由文本框的字号大小决定;

    • 例如:We are not amused.[开心]

      imgimg

  • 如果使用了029新增的【社交功能-聊天服务新增私聊功能、聊天气泡】功能中的聊天气泡接口,能自动支持将房间内聊天使用到的233表情同步显示在聊天气泡中(自030起生效);

  • 富图片标记格式对应表及更多信息请查看手册:富文本 | 产品手册

四、编辑器新增功能及API更新

[新增]物体新增启动阴影属性,控制单个物体的阴影显示

img
  • 功能说明:物体属性中可以启用或关闭阴影显示,方便用户自定义物体的阴影显示效果。举例说明,玻璃材质的物体,可能不太需要阴影效果,我们就可以关闭该物体的阴影功能。
  • 除了属性面板,还可以在脚本中控制该属性:
属性名称英文名称类型默认值取值范围说明读写
开启阴影castShadowBooleantrue-是否开启阴影Write/Read
  • 示例代码:
TypeScript
//找到相应的物体
let cub = await GameObject.asyncFindGameObjectById("2F2D8B2E") as Model
//将物体的阴影进行关闭
cub.castShadow = false;
//找到相应的物体
let cub = await GameObject.asyncFindGameObjectById("2F2D8B2E") as Model
//将物体的阴影进行关闭
cub.castShadow = false;

[新增]物理模拟力接口,可对开启物理模拟的对象施加物理力,产生物理模拟运动效果

TypeScript
//给一个方块的Z轴添加 500冲量,同时忽视方块自身的质量影响
modelCube.addImpulse(new mw.Vector(0,0,500),true);

//给一个方块的Z轴添加 500力,同时忽视方块自身的质量影响
modelCube.addForce(new mw.Vector(0,0,500),true);

//给一个方块的Z轴添加 10扭力,同时忽视方块自身的质量影响
modelCube.addTorque(new mw.Vector(0,0,10),true);

//给一个方块的Z轴添加 10的角度冲量,同时忽视方块自身的质量影响
modelCube.addAngularImpulse(new mw.Vector(0,0,10),true);

//给一个方块的Z轴添加 100线性速度,并与方块当前的物理运动效果进行叠加
modelCube.physicsLinearVelocity = new mw.Vector(0,0,100);
console.log(`modelCube当前线性速度`,modelCube.physicsLinearVelocity);
            
//给一个方块的Z轴添加 50角速度,并与方块当前的物理运动效果进行叠加
modelCube.physicsAngularVelocity = new mw.Vector(0,0,50);
console.log(`modelCube当前角速度`,modelCube.physicsAngularVelocity);
//给一个方块的Z轴添加 500冲量,同时忽视方块自身的质量影响
modelCube.addImpulse(new mw.Vector(0,0,500),true);

//给一个方块的Z轴添加 500力,同时忽视方块自身的质量影响
modelCube.addForce(new mw.Vector(0,0,500),true);

//给一个方块的Z轴添加 10扭力,同时忽视方块自身的质量影响
modelCube.addTorque(new mw.Vector(0,0,10),true);

//给一个方块的Z轴添加 10的角度冲量,同时忽视方块自身的质量影响
modelCube.addAngularImpulse(new mw.Vector(0,0,10),true);

//给一个方块的Z轴添加 100线性速度,并与方块当前的物理运动效果进行叠加
modelCube.physicsLinearVelocity = new mw.Vector(0,0,100);
console.log(`modelCube当前线性速度`,modelCube.physicsLinearVelocity);
            
//给一个方块的Z轴添加 50角速度,并与方块当前的物理运动效果进行叠加
modelCube.physicsAngularVelocity = new mw.Vector(0,0,50);
console.log(`modelCube当前角速度`,modelCube.physicsAngularVelocity);

给物体施加线速度:

给物体施加角速度:

TIP

  • 使用上述方法,对象需要开启物理模拟功能。
  • physicsLinearVelocity 线性速度和 physicsAngularVelocity 角速度 能与当前的物理运动效果进行叠加使用,运动结束后值会变为Vector(0,0,0)

[新增]时间膨胀接口适配单机模式(LS)

  • 此前时间膨胀相关接口(EnvironmentSettings.setGlobalTimeDilation和Pawn.customTimeDilation)仅支持在联机模式(DS)中使用;029起,时间膨胀接口也可以在单机模式(LS)中使用
img
  • 但是使用这两个接口时要注意(和之前在联机模式中一样):

    • 当前修改时间膨胀后角色动画不会受到影响,关闭角色相关优化后才能用接口控制时间膨胀,关闭角色优化的方法:AvatarSettings.setOptimization(player.character, false, false);

    • 部分对象(例如音效、运动器、部分特效)不会受到时间膨胀的影响

      TypeScript
         let player = await Player.asyncGetLocalPlayer();
         /**设置膨胀时间速度 */
         player.character.customTimeDilation = 0.5;
         /**关闭角色优化*/
         AvatarSettings.setOptimization(player.character, false, false)
         let player = await Player.asyncGetLocalPlayer();
         /**设置膨胀时间速度 */
         player.character.customTimeDilation = 0.5;
         /**关闭角色优化*/
         AvatarSettings.setOptimization(player.character, false, false)

[新增]社交功能-聊天服务新增私聊功能、聊天气泡

  • 游戏层面允许/禁止全体用户私聊
img
  • 可屏蔽房间内全部/部分用户的私聊信息

img

  • 聊天时角色上方展示聊天气泡,聊天类型为私聊时气泡仅私聊双方可见,可通过ChatBubble类自定义聊天气泡基础参数

TypeScript
 if (SystemUtil.isClient()) {
     /**开启气泡功能 */
     ChatBubble.bubbleEnabled = true;
     /**设置头顶气泡共存数量 */
     ChatBubble.maxParallelBubbles = 2;
     /**设置气泡背景颜色 */
     ChatBubble.bubbleBackgroundColor = LinearColor.blue;
     /**设置气泡持续时长 */
     ChatBubble.bubbleDuration = 1;
     /**设置气泡文字字体 */
     ChatBubble.bubbleTextFont = UIFontGlyph.BoldItalics;
     /**设置气泡文字颜色 */
     ChatBubble.bubbleTextColor = LinearColor.red;
     /**设置气泡文字大小 */
     ChatBubble.bubbleTextSize = 10;
     /**设置气泡间隔距离 */
     ChatBubble.bubbleDisplayDistance = 200;
  }
 if (SystemUtil.isClient()) {
     /**开启气泡功能 */
     ChatBubble.bubbleEnabled = true;
     /**设置头顶气泡共存数量 */
     ChatBubble.maxParallelBubbles = 2;
     /**设置气泡背景颜色 */
     ChatBubble.bubbleBackgroundColor = LinearColor.blue;
     /**设置气泡持续时长 */
     ChatBubble.bubbleDuration = 1;
     /**设置气泡文字字体 */
     ChatBubble.bubbleTextFont = UIFontGlyph.BoldItalics;
     /**设置气泡文字颜色 */
     ChatBubble.bubbleTextColor = LinearColor.red;
     /**设置气泡文字大小 */
     ChatBubble.bubbleTextSize = 10;
     /**设置气泡间隔距离 */
     ChatBubble.bubbleDisplayDistance = 200;
  }

TIP

  • 创作者中心即时聊天参数修改保存后,新创建的房间将即时生效,运行中的房间配置不会更改。
  • 聊天气泡默认是关闭状态,需要在客户端上设置 ChatBubble.bubbleEnabled = true 开启功能。

[新增]为游戏新增233乐园礼包码兑换API

API PurchaseService新增接口

说明接口返回类型输入说明输入值范围输出说明输出值范围使用域
礼包码兑换redeemGiftCode(player : Player , GiftCode : string , redeemCallback : (result : RedeemResponse) => void ) : voidvoidplayer需要兑换的玩家对象CDKey礼包码redeemCallback兑换结果回调---Server Only

RedeemResponse:属性列表

属性名称英文名称类型说明备注
兑换状态statusnumber200: 兑换成功
400: 兑换失败(兑换码不存在)
1002: 兑换失败(兑换码不在使用期限内)
1010: 兑换失败(兑换码已使用)
1011: 兑换失败(兑换超时)
1012: 兑换失败(礼包库存不足)
1013: 兑换失败(超出兑换次数)
1014: 兑换失败(数据库失败)
1015: 兑换失败(非本游戏道具)
服务端对应状态码
兑换信息messagestring当兑换状态为成功时:兑换信息为礼包内容道具详情当兑换状态为失败时:兑换信息为失败详情,例如:兑换码不存在

示例:

TypeScript
let player1 = Player.getAllPlayers()[0];
PurchaseService.redeemGiftCode(player1,"TestRedeemGiftCode1001",(result)=>{
    if(result.status == 1)
    {
        //兑换成功处理逻辑
        Debug.log("Redeem Success!");
        //根据message发放奖励
        //DoSomething......
    }
    else
    {
        //兑换失败处理逻辑
        Debug.error(result.message);
    }
});
let player1 = Player.getAllPlayers()[0];
PurchaseService.redeemGiftCode(player1,"TestRedeemGiftCode1001",(result)=>{
    if(result.status == 1)
    {
        //兑换成功处理逻辑
        Debug.log("Redeem Success!");
        //根据message发放奖励
        //DoSomething......
    }
    else
    {
        //兑换失败处理逻辑
        Debug.error(result.message);
    }
});

道具配置和礼包配置:

  • 道具管理界面配置道具:
img
  • 道具兑换码生成和兑换记录界面:
img
  • 生成道具兑换码界面:
img
  • 福利配置界面配置福利礼包
img

[新增]基础性能接口

DebugUtil 类中新增获取游戏当前基础性能统计数据接口,便于开发时查看真实线上环境中游戏运行性能统计数据,定位游戏内性能问题。

DebugUtil 静态属性

属性含义属性名类型默认值取值范围说明读写可见调用
帧时间frameTimenumber--帧时间是生成一帧游戏内容所花费的总时间。由于游戏线程和渲染线程在完成一帧之前保持同步,帧时往往接近其中一个线程中显示的时间。单位ms。Read OnlyScript OnlyServer & Client
游戏线程时间gameThreadnumber--对象在游戏线程中消耗的时间,包括脚本,动画,游戏逻辑等。单位ms。如果帧时接近游戏线程中显示的时间,则游戏的性能很可能会受到游戏线程的阻碍。单位ms。Read OnlyScript OnlyServer & Client
渲染线程时间renderThreadnumber--在渲染线程中处理这些对象的时间,受粒子,特效,网格等影响。单位ms。如果帧时接近Draw线程中显示的时间,则游戏的性能很可能会受到渲染线程的阻碍。单位ms。服务端该值为0。Read OnlyScript OnlyServer & Client
占用内存usedMemorynumber--当前使用的总内存大小,单位MB。Read OnlyScript OnlyServer & Client
每秒(网络)发送量sentBytesnumber--一秒内发出的网络包的总大小。单位B。Read OnlyScript OnlyServer & Client
每秒(网络)接收量receivedBytesnumber--一秒内收到的网络包的总大小。单位B。Read OnlyScript OnlyServer & Client
发送的RPC消息sentRPCsArraystring--当前帧发送的RPC消息数组。每个元素是RPC的函数名。Read OnlyScript OnlyServer & Client
接收的RPC消息receivedRPCsArraystring--当前帧收到的RPC消息数组。每个元素是RPC的函数名。Read OnlyScript OnlyServer & Client
堆栈的RPC消息cachedRPCsArraystring--当前帧RPC堆栈内的RPC消息数组。每个元素是RPC的函数名。Read OnlyScript OnlyServer & Client

[新增]模型自定义碰撞组及碰撞响应结果

模型新增属性以及提供物理服务接口,可以自定义模型的(碰撞)对象类型,同时可以自定义碰撞组和组间(内)响应,可以根据需求设置模型之间的碰撞。

Model - 模型

属性

属性含义属性名类型默认值取值范围说明读写可见
碰撞组collisionGroupstringDefault-collisionGroup属性的值是模型所属的碰撞组名称。模型默认属于碰撞组中”Default“组。Read & WriteEditor & Script

PhysicsService - 物理服务类

静态方法

功能说明方法名返回类型输入说明输入值范围输出说明输出值范围使用域
设置组间碰撞。如果两个组名相同,则视为设置组内碰撞。setCollisionBetweenGroups(group1: string, group2: string, collidable: boolean): voidvoidgroup1碰撞组1的名字group2碰撞组2的名字collidable组间碰撞结果---Server & Client
获取组间碰撞。如果两个组名相同,则视为获取组内碰撞。getCollisionBetweenGroups(group1: string, group2: string): booleanbooleangroup1碰撞组1的名字group2碰撞组2的名字-组间碰撞结果-Server & Client
获取当未用的碰撞组数量getAvailableCollisionGroupsCount(): numbernumber--当前剩余可用的碰撞组数量-Server & Client
获取所有碰撞组getValidCollisionGroups(): ArraystringArraystring--当前有效碰撞组的名称数组-Server & Client
判断碰撞组是否有效。无效碰撞组对模型不生效。isCollisionGroupValid(name: string): booleanbooleanname碰撞组的名字-检查碰撞组是否有效。-Server & Client
新增碰撞组addCollisionGroup(name: string): voidvoidname碰撞组的名字--Server & Client
移除碰撞组deleteCollisionGroup(name: string): voidvoidname碰撞组的名字---Server & Client
重命名碰撞组renameCollisionGroup(previousName: string, newName: string): voidvoidpreviousName碰撞组的老名字newName碰撞组的新名字---Server & Client
TypeScript
protected async onStart(): Promise<void> {
        /**添加test1碰撞组 */
        PhysicsService.addCollisionGroup("test1");
        /**添加test2碰撞组 */
        PhysicsService.addCollisionGroup("test2");
        /**设置test1与test2碰撞组之间的碰撞关系为不可发生碰撞 */
        PhysicsService.setCollisionBetweenGroups("test1", "test2", false);
  
        if (SystemUtil.isServer()) {
            this.go = await GameObject.asyncSpawn("197386", { replicates: true }) as Model;
            await this.go.asyncReady();
            this.go.setCollision(PropertyStatus.On)
            /**设置方块的碰撞组为test1 */
            this.go.collisionGroup = "test1";
        }
    }
protected async onStart(): Promise<void> {
        /**添加test1碰撞组 */
        PhysicsService.addCollisionGroup("test1");
        /**添加test2碰撞组 */
        PhysicsService.addCollisionGroup("test2");
        /**设置test1与test2碰撞组之间的碰撞关系为不可发生碰撞 */
        PhysicsService.setCollisionBetweenGroups("test1", "test2", false);
  
        if (SystemUtil.isServer()) {
            this.go = await GameObject.asyncSpawn("197386", { replicates: true }) as Model;
            await this.go.asyncReady();
            this.go.setCollision(PropertyStatus.On)
            /**设置方块的碰撞组为test1 */
            this.go.collisionGroup = "test1";
        }
    }

TIP

  • PhysicsService 接口S端调用自带双端同步,因此推荐在S端使用即可。
  • 新增碰撞组 addCollisionGroup 建议在设置碰撞物体的 collisionGroup 之前设置,避免不必要的同步时序问题。同时需注意碰撞组同步和模型双端对象同步互相独立,建议在早期把碰撞组配置完备,避免不必要的同步时序问题。
  • 设置物体碰撞组,属性面板的编辑态功能未上线,目前只能通过代码设置。

[新增]重新打开多场景管理和跳转功能入口,新增多场景数据互通

  • 为了解决一个项目内可能会存在多个场景关卡的需求,所以新增多场景功能,可以根据需求新建并管理项目内的多个场景。并通过API接口实现场景之间的跳转。详情请见:场景管理与跳转 | 产品手册

img

  • 备注:
    • projectassembly 文件是记录多场景配置的文件,后续项目在多场景开发时,需要将其纳入 git 管理。
    • 升级后,原工程存放会多一个层级目录,原有工程文件会变为该目录子文件,可能会造成 git 无法定位的问题,解决方案详见: 028升级后git无法定位修改指南
    • 子场景暂不支持自动备份/恢复功能,请随时注意保存。
    • 原旧工程目录(...\MetaWorldSaved\Saved\MetaWorld\Projects),029版将不再兼容维护,请将原旧目录下的工程文件移到新工程目录(...\MetaWorldSaved\Saved\MetaWorld\Project\Edit)。
    • 为了方便主场景与子场景存档数据互通,029上的 DataStorage 接口,在主场景/子场景中存取的将是同一份数据,即数据是整个游戏的,在哪里读取都一样。

五、游戏功能对象新增功能及API更新

[新增]角色基础状态功能

  • 为了让开发者更加便捷的对角色状态进行设置,我们将角色的基础状态进行了详细的划分,并拓展了角色的基础状态和相关接口,目前新增8个角色基础状态。
角色基础状态枚举说明
地面移动状态Running角色在地面移动时的状态
飞行状态Flying角色在空中飞行时的状态
游泳状态Swimming角色在游泳时的状态
跳跃状态Jumping角色在执行跳跃功能时的状态
自由落体状态Freefall角色在空中下落时的状态
着陆状态Landed角色在自由落体后,接触到地面时的状态。
布娃娃状态Ragdoll角色在布娃娃状态时的状态
下蹲状态Crouching角色在下蹲时的状态
None取消角色的复杂移动模式,采用简单移动模式状态。

状态解析:

  • Running 指的是正常情况下角色在地面的状态,无论是否在移动。是否在移动需要以character.isMoving状态来判断。
  • Jumping 角色从跳起到即将要下落的时间内为此状态,角色跳跃过程中(跳跃+下落),isMoving 状态为 true。
  • Freefall 角色从下落到落地的时间内为此状态。
  • Landed 角色在落地后会有短暂的1帧为此状态,随后会切换为 Running 状态。这个状态可以用来处理角色落地动作。
  • 如果在空中从 Flying/Swimming 转换到 Running 状态,会在转换成 Running 状态后马上变成 Freefall 状态。
  • 角色部分api废弃,由新的状态切换api代替
  • 详情请见产品手册:
废弃接口替换接口
jump(): voidchangeState(CharacterStateType.Jumping)
switchToSwimming(): voidchangeState(CharacterStateType.Swimming)
switchToFlying(): voidchangeState(CharacterStateType.Flying)
switchToWalking(): voidchangeState(CharacterStateType.Running)
crouch(isCrouch: boolean): voidchangeState(CharacterStateType.Crouching)
get/set ragdollEnabled()changeState(CharacterStateType.Ragdoll)
get movementMode(): mw.MovementModegetCurrentState(): mw.CharacterStateType
onMovementModeChange:mw.MulticastDelegate<mw.OnMovementModeChange>onStateChanged: mw.MulticastDelegate<(prevState: mw.CharacterStateType, currentState: mw.CharacterStateType) => void>
get movementEnabled(): boolean
get jumpEnable(): boolean
get crouchEnabled(): boolean

[新增]多足骨骼插槽

  • 为了让开发者能够在多足形象上附加其他对象,达到装扮效果。我们提供了多足的插槽功能,方便开发者使用。
TypeScript
/**
 * @groups AVATAR
 * @description 非人形角色插槽类型
 */
enum NonHumanoidSlotType {
    /** 根节点 */
    Root = 0,
    /** 胸腔 */
    Chest = 1,
    /** 上脊柱 */
    UpperSpine = 2,
    /** 下脊柱 */
    LowerSpine = 3,
    /** 脖子 */
    Neck = 4,
    /** 头部 */
    Head = 5,
    /** 左前脚 */
    FrontalLeftFoot = 6,
    /** 右前脚 */
    FrontalRightFoot = 7,
    /** 左后脚 */
    RearLeftFoot = 8,
    /** 右后脚 */
    RearRightFoot = 9,
    /** 尾巴 */
    Tail = 10,
    /** 类人型手臂右手01 */
    RightHand = 11,
    /** 类人型手臂左手01 */
    LeftHand = 12,
    /** 类人型头部 */
    HumanoidHead = 13,
    /** 类人型多足怪胸腔位置 */
    HumanoidSpine = 14,
    /** 上表面鱼鳍01 */
    DorsalFin = 15,
    /** 左边鱼鳍01 */
    LeftFin = 16,
    /** 右边鱼鳍01 */
    RightFin = 17,
    /** 右翼翅膀01 */
    RightWing = 18,
    /** 左翼翅膀01 */
    LeftWing = 19,
    /** 多足右脚01 */
    ExtraRightFoot1 = 20,
    /** 多足右脚02 */
    ExtraRightFoot2 = 21,
    /** 多足右脚03 */
    ExtraRightFoot3 = 22,
    /** 多足右脚04 */
    ExtraRightFoot4 = 23,
    /** 多足左脚01 */
    ExtraLeftFoot1 = 24,
    /** 多足左脚02 */
    ExtraLeftFoot2 = 25,
    /** 多足左脚03 */
    ExtraLeftFoot3 = 26,
    /** 多足左脚04 */
    ExtraLeftFoot4 = 27
}
/**
 * @groups AVATAR
 * @description 非人形角色插槽类型
 */
enum NonHumanoidSlotType {
    /** 根节点 */
    Root = 0,
    /** 胸腔 */
    Chest = 1,
    /** 上脊柱 */
    UpperSpine = 2,
    /** 下脊柱 */
    LowerSpine = 3,
    /** 脖子 */
    Neck = 4,
    /** 头部 */
    Head = 5,
    /** 左前脚 */
    FrontalLeftFoot = 6,
    /** 右前脚 */
    FrontalRightFoot = 7,
    /** 左后脚 */
    RearLeftFoot = 8,
    /** 右后脚 */
    RearRightFoot = 9,
    /** 尾巴 */
    Tail = 10,
    /** 类人型手臂右手01 */
    RightHand = 11,
    /** 类人型手臂左手01 */
    LeftHand = 12,
    /** 类人型头部 */
    HumanoidHead = 13,
    /** 类人型多足怪胸腔位置 */
    HumanoidSpine = 14,
    /** 上表面鱼鳍01 */
    DorsalFin = 15,
    /** 左边鱼鳍01 */
    LeftFin = 16,
    /** 右边鱼鳍01 */
    RightFin = 17,
    /** 右翼翅膀01 */
    RightWing = 18,
    /** 左翼翅膀01 */
    LeftWing = 19,
    /** 多足右脚01 */
    ExtraRightFoot1 = 20,
    /** 多足右脚02 */
    ExtraRightFoot2 = 21,
    /** 多足右脚03 */
    ExtraRightFoot3 = 22,
    /** 多足右脚04 */
    ExtraRightFoot4 = 23,
    /** 多足左脚01 */
    ExtraLeftFoot1 = 24,
    /** 多足左脚02 */
    ExtraLeftFoot2 = 25,
    /** 多足左脚03 */
    ExtraLeftFoot3 = 26,
    /** 多足左脚04 */
    ExtraLeftFoot4 = 27
}
  • 详情请见产品手册:
img

TIP

不同的多足对象会支持不同数量的插槽,具体的多足对象有哪些插槽可用,需要参考产品手册。

[新增]动画支持自定义融合时间

角色播放动画时前后均有一个融合阶段。029Animation对象新增接口让您可以自由控制混入时间和混出时间。

Lua
 protected onStart(): void {

    InputUtil.onKeyDown(Keys.One, () => {
        this.ani = Player.localPlayer.character.loadAnimation("14757");
    });

    InputUtil.onKeyDown(Keys.Two, () => {
        this.ani.blendInTime = 0.5;
        this.ani.blendOutTime = 0.5;
        this.ani.play();
    });

    InputUtil.onKeyDown(Keys.Three, () => {
        this.ani.blendInTime = 0;
        this.ani.blendOutTime = 0;
        this.ani.play();
    });
}
 protected onStart(): void {

    InputUtil.onKeyDown(Keys.One, () => {
        this.ani = Player.localPlayer.character.loadAnimation("14757");
    });

    InputUtil.onKeyDown(Keys.Two, () => {
        this.ani.blendInTime = 0.5;
        this.ani.blendOutTime = 0.5;
        this.ani.play();
    });

    InputUtil.onKeyDown(Keys.Three, () => {
        this.ani.blendInTime = 0;
        this.ani.blendOutTime = 0;
        this.ani.play();
    });
}

blendInTime = 0;blendOutTime = 0:

blendInTime = 0.5;blendOutTime = 0.5:

[新增]寻路功能支持玩家角色使用follow()函数

TypeScript
Event.addLocalListener("PlayerfollowNPC", () => {
    Navigation.follow(player.character, NPC, 30, () => {
        console.log("追到了NPC");
    }, () => {
        console.log("跟随目标消失");
    })
});
Event.addLocalListener("PlayerstopFollow", () => {
    Navigation.stopFollow(player.character)
});
Event.addLocalListener("PlayerfollowNPC", () => {
    Navigation.follow(player.character, NPC, 30, () => {
        console.log("追到了NPC");
    }, () => {
        console.log("跟随目标消失");
    })
});
Event.addLocalListener("PlayerstopFollow", () => {
    Navigation.stopFollow(player.character)
});

TIP

玩家角色使用follow()方法需要在玩家主控端调用,即玩家角色自己的客户端。

[优化]Navigation.follow()的回调时机

  • 原接口的回调时机无法满足一些AI机制的逻辑,比如跟随到目标后执行攻击等逻辑,优化后可以支持更多的编写 AI 机制。原来"目标可以进行跟随"的回调可以使用 Navigation.follow() 的返回值进行判断。
TypeScript
/**
 * 优化前
 * follow()在调用时,立即返回OnSuccess或OnFail;
 */
Navigation.follow(NPC, player.character, 10, () => {
    console.log("目标可以进行跟随");
}, () => {
    console.log("目标无法跟随");
})

/**
 * 优化后
 * 当跟随到设定的目标范围内时,返回OnSuccess
 * 当跟随的目标消失或离开寻路区域范围时,返回OnFail
 * follow()新增函数返回值,目标可被跟随返回true,目标不可被跟随返回false
 */
let res = Navigation.follow(NPC, player.character, 10, () => {
    console.log("跟上了目标");
}, () => {
    console.log("目标消失");
})
if(res){
   console.log("目标可被跟随"); 
}else{
    console.log("目标不可被跟随"); 
}
/**
 * 优化前
 * follow()在调用时,立即返回OnSuccess或OnFail;
 */
Navigation.follow(NPC, player.character, 10, () => {
    console.log("目标可以进行跟随");
}, () => {
    console.log("目标无法跟随");
})

/**
 * 优化后
 * 当跟随到设定的目标范围内时,返回OnSuccess
 * 当跟随的目标消失或离开寻路区域范围时,返回OnFail
 * follow()新增函数返回值,目标可被跟随返回true,目标不可被跟随返回false
 */
let res = Navigation.follow(NPC, player.character, 10, () => {
    console.log("跟上了目标");
}, () => {
    console.log("目标消失");
})
if(res){
   console.log("目标可被跟随"); 
}else{
    console.log("目标不可被跟随"); 
}

[优化]高级轮式载具网络同步优化

  • 高级轮载具在多端的位置同步表现更加精准;
  • 载具间互相撞击会有反馈,降低撞墙感;

[优化]高级轮式载具支持 PC 端和移动端无脚本直接使用

img

可在高级轮式载具属性面板设置载具辅助功能;

自动上车:勾选后角色进入绑定触发器立即“执行上车“执行

载具控制:角色"上车"后,在 PC 端可通过 WASD 完成载具控制,通过F键下车;在移动端通过工程默认的DefaultUI完成载具控制,通过跳跃按钮下车;

隐藏驾驶员:勾选后,角色上车后自动隐藏角色形象;

下车位置偏移:设置下车时角色相对于载具的位置;

载具触发器:自动上车所需的触发器逻辑对象,删除后可重新绑定;

载具交互物:自动上车所需的交互物逻辑对象,删除后可重新绑定;

修复BUG