Skip to content
027API修改常见问题

027API修改常见问题

判断资源使用是否需要下载

typescript
// 同步创建
// xxx为对象的AssetID
GameObject.spawn("xxx")

// 异步创建
GameObject.asyncSpawn("xxx").then(go=>{
	
})
// 同步创建
// xxx为对象的AssetID
GameObject.spawn("xxx")

// 异步创建
GameObject.asyncSpawn("xxx").then(go=>{
	
})
  • 异步创建对象,都无需走下载或优先加载流程

  • 同步创建,需要走下载或优先加载流程保证创建成功

    027这里出现的变化就是,在027之前,即使异步加载,动画资源也是需要优先加载或下载的,而027之后,省略了这个步骤

资源加载方式变更,preloadAssets删除

  • 首次打开项目走自动升级流程时,会自动执行预加载preloadAsset的兼容方案,老项目使用preloadAsset预加载的资源会自动进行读取导入至优先加载栏中。

9cc06add-07cb-4163-b33a-c1b02c6d5ead

  • 代码中出现的preloadAssets会导致@Component或其下面的类名出现报错(同时或单个报错都有可能是preloadAssets造成的):

    image-20231018113824511

类“xxxx”错误扩展基类“Script”。 属性“preloadAssets”在类型“Script”中是私有属性,但在类型“xxxx”中不是

解决办法就是直接将preloadAssets及其上边的@Property相关代码删除即可

GUID变更

以前无论是资源ID还是对象ID都统一叫做guid,现在资源ID对应assetID,对象ID对应gameObjectId

image-20231016175457898

image-20231016175521426

角色部分的改动

1. 换装方式变更

在前面的版本,换装确实是一件麻烦事,API更新之前需要经过:获取角色装扮信息、设置角色装扮信息外观类型、设置角色装扮、等待角色外观设置完毕等一系列流程才能成功完成一次换装,这次更新后流程将极大的简化!具体更改内容有以下几点:

  • getAppearance() 以及 setAppearance()被去掉,原角色外观对象替换为CharacterDescription。可以直接通过character.description进行调用

原换装方式:

TypeScript
Gameplay.asyncGetCurrentPlayer().then((p: Gameplay.Player) => {
    let hv2 = p.character.setAppearance(Gameplay.HumanoidV2);
    hv2.setAppearanceData([CharGuid], () => {
        p.character.setAppearance(Gameplay.HumanoidV2);
        p.character.getAppearance(Gameplay.HumanoidV2).appearanceSync();
    })
}
Gameplay.asyncGetCurrentPlayer().then((p: Gameplay.Player) => {
    let hv2 = p.character.setAppearance(Gameplay.HumanoidV2);
    hv2.setAppearanceData([CharGuid], () => {
        p.character.setAppearance(Gameplay.HumanoidV2);
        p.character.getAppearance(Gameplay.HumanoidV2).appearanceSync();
    })
}

现换装方式:

TypeScript
Gameplay.asyncGetCurrentPlayer().then((p: Gameplay.Player) => {
    //角色换装
    p.character.setDescription([CharGuid]);
    //将角色装扮同步给其他客户端
    p.character.syncDescription()
})
Gameplay.asyncGetCurrentPlayer().then((p: Gameplay.Player) => {
    //角色换装
    p.character.setDescription([CharGuid]);
    //将角色装扮同步给其他客户端
    p.character.syncDescription()
})
  • 使用getDescription()可以获取当前外观数据的拷贝,并且修改这个拷贝值并不会影响当前角色外观。需要将当前角色外观数据应用于其他角色时可以使用此方法(例如领奖台的实现)。

  • 角色外观异步等待函数appearanceReady替换为GameObjectasyncReady。对角色的一切操作,最好都等一下asyncReady(),如头顶UI,mesh,胶囊体,切换摄像机父级对象等。

  • 角色换装完成委托统一整合为onDescriptionComplete

    • 注意:同步外观数据syncDescription后会再次调用onDescriptionComplete。避免在onDescriptionComplete委托方法中执行外观数据同步,造成无限循环。

      typescript
      /** 错误写法*/
      this.localPlayer.character.setDescription([appearanceGuid]);
      this.localPlayer.character.onDescriptionComplete.add(()=>{
          //在委托中使用同步
          this.localPlayer.character.syncDescription();
      })
      
      /** 正确写法*/
      this.localPlayer.character.setDescription([appearanceGuid]);
      //设置完外观直接同步
      this.localPlayer.character.syncDescription();
      /** 错误写法*/
      this.localPlayer.character.setDescription([appearanceGuid]);
      this.localPlayer.character.onDescriptionComplete.add(()=>{
          //在委托中使用同步
          this.localPlayer.character.syncDescription();
      })
      
      /** 正确写法*/
      this.localPlayer.character.setDescription([appearanceGuid]);
      //设置完外观直接同步
      this.localPlayer.character.syncDescription();

2. 外观类型删除

NPC、Humanoid等类型都被删掉了,现在人形对象不会分成V1、V2、NPC等各种类型,而是统一为Character,使管理人形对象变得更加方便。现在判断一个character是不是NPC可以用更加符合其定义的方式:(obj instanceof mw.Character)&&(obj.player === null)来判断obj是不是NPC(Non-Player Character)

原判断方式:

TypeScript
//判断角色为NPC
obj1 instanceof Gameplay.NPC
//判断角色为玩家角色
obj2 instanceof Gameplay.Character
//判断角色为NPC
obj1 instanceof Gameplay.NPC
//判断角色为玩家角色
obj2 instanceof Gameplay.Character

现判断方式:

TypeScript
//判断角色为NPC
(obj1 instanceof mw.Character)&&(obj1.player === null)
//判断角色为玩家角色
(obj2 instanceof mw.Character)&&(obj2.player !== null)
//判断角色为NPC
(obj1 instanceof mw.Character)&&(obj1.player === null)
//判断角色为玩家角色
(obj2 instanceof mw.Character)&&(obj2.player !== null)

3. Player不再继承GameObject

player之前可以直接获得或者修改character的属性及函数,为了能够更好的区分player和character的职责,在027更新之后做了限制:必须获取character才能够对其属性与函数作出修改,之前直接从player获取worldLocation例如:

TypeScript
player.worldLocation
player.worldLocation

在更新后需要变为

TypeScript
player.character.worldLocation
player.character.worldLocation

4. Character上两种绑定方式变更

旧版本当character隐藏时,其slot关联的对象不会被隐藏,这就导致如果需要完全隐藏玩家需要手动隐藏所有其slot关联的对象。在版本更新后,character隐藏时其slot关联的对象也会被一起隐藏。这就导致在更新版本后,旧工程中与character的slot关联的对象都会被隐藏,如果需要维持旧版本的表现,可以尝试以下修改。

原方法:

TypeScript
character.setVisibility(PropertyStatus.Off,true)
//027更新后此处xmodel设置为slot之后会被隐藏
//如果需要设置slot但是仍旧显示需要看新用法
character.attachToSlot(this.xmodel,mw.HumanoidSlotType.Root)
character.setVisibility(PropertyStatus.Off,true)
//027更新后此处xmodel设置为slot之后会被隐藏
//如果需要设置slot但是仍旧显示需要看新用法
character.attachToSlot(this.xmodel,mw.HumanoidSlotType.Root)

现方法:

TypeScript
//将方法后面的“是否同步子物体”设置为false
character.setVisibility(PropertyStatus.Off,false)
//通过parent绑定两个物体
this.xmodel.parent = character
//将方法后面的“是否同步子物体”设置为false
character.setVisibility(PropertyStatus.Off,false)
//通过parent绑定两个物体
this.xmodel.parent = character

5. 角色不再自动进行RPC同步

之前版本中,角色在客户端做更改后会自动同步其他角色包括动画、姿态等多种属性,当角色多了之后RPC可能会过于频繁而使游戏经常卡顿导致表现不佳。本次更新之后将取消客户端属性更改时的大量RPC同步,需要同步的操作可以在服务端实现。

玩家角色只有Transform属性(position, rotation, scale)在主控端(该角色的客户端)修改会同步。服务端修改会通知主控端执行移动。因此需要注意,在027上从服务端修改角色Transform属性时需要确认客户端的角色已经准备好了,再通知进行移动。如果脚本中有玩家刚进入房间就从服务端修改其角色位置的逻辑(例如角色出生后传送到某个特定地点),可以尝试在修改位置前加入延时,确保其客户端已经准备好再进行位置修改,或是直接在客户端修改位置。

修改前:

img

修改后:

img

除了Transform之外其他属性都将不再同步,例如:动画,姿态,头顶名字,布娃娃状态,胶囊体偏移等,这些属性都需要在服务端做相对应的修改。

非玩家角色NPC仅在服务端修改会同步,这可能会造成在客户端修改NPC位置后被拉回服务端位置(一般是NPC生成点),例如:

若遇到此问题,可尝试在代码中将修改NPC位置在服务端上运行,或者将NPC改为客户端对象。

img

img

6. UserId变为player下的只读参数

userId将作为Player下的成员,可以直接使用player.userId来获取,同时需要检查旧工程中有没有使用同名变量的情况,若出现同名变量并且对其进行了设置,则会该客户端出现报错,例如报错显示"Cannot set property userId of #<Subdata> which has only a getter"如图:

img

原因是在代码中原来定义的userId变量和现在的userId重名了,并且现在的player.userId为只读属性,所以在运行时会出现客户端报错

img

若代码中有类似情况,可以将原代码中的userId变量名替换为非重名的名字,或者直接将设置userId的代码删除即可。

7. 原NPC类型变更导致的表现差异

旧版本的NPC现在已经归类为Character,其模型会默认有碰撞。这会导致可能有部分NPC在场景中由于碰撞挤压,表现的不正常(例如出现浮空、下沉或是位置偏移)如图:

img

这是由于NPC的碰撞盒碰到了椅子,将其抬高。若是要恢复原来的效果,首先需要使用setCollision将NPC的碰撞关闭:

img

关闭碰撞之后,若仍有部分NPC在场景中表现不正确,由于此时不会有碰撞造成的位置偏差,可以找到对应NPC并且调整其位置即可。

imgimg

imgimg

8. CharacterBase类型升级为了Pawn

部分方法参数可能以前是CharacterBase类型,升级后(挨着instanceof 的CharacterBase都会被升级为Pawn类型,防止bug),把Pawn对象直接传入就不行了

img

027上有以下几种方式修修改:

  1. xxx.player.character

img

  1. 也可以修改Pawn为Character

img

9. 角色姿态在工程升级后发生变化

由于027本次升级,角色和姿态都发生了较大的变化,角色姿态将不再与角色形象绑定,所以在旧工程升级到新工程的时候角色姿态有可能发生变化,此时我们可以通过以下几步来恢复原有姿态:

首先我们需要在对象管理器中找到"Player"并且点击其属性面板中的"编辑玩家形象"如图:

img

进入玩家编辑界面之后点击"动画"选项卡,此时将''基础姿态''选项调整至需要的姿态并且保存角色形象,再次进入游戏,角色的行动动作和姿态就变回我们选择的姿态啦,如图:

imgimg

10. 双端角色,C端地面,导致的角色位置拉扯

可能存在这样的表现,换装展示的角色运行时一直在抖动拉扯

img_v2_e982d4b9-6e16-4d18-a4db-d24d9e9e714g

其原因在于双端(S&C)角色的下方存在一个客户端(C)底板

img_v2_5dbfc1ed-fa0a-46ae-afab-b78c75e2defg

由于这里的换装NPC其实不需要实时同步给其他玩家的,因此只需要将其改为客户端(C)即可

img_v2_88ff11e8-e6b6-40e1-bcb3-f19c9f3ad70g

如若是需要同步角色或NPC位置的情况,我们则需要将角色和地面都修改为双端,使得其能保证正确的同步显示效果

天空盒部分的改动

在新版编辑器上,天空盒的操作也更为简化。现在操作天空盒将不再需要提前查找,可以直接在mw.Skybox下对天空盒进行操作。在天空盒的操作中需要注意,修改类似starTextureID、moonTextureID、sunTextureID、cloudTextureID等参数前,需要确保将其设置为可见状态(mw.Skybox.startVisible = true)

原操作天空盒方式:

TypeScript
let skyBox: Gameplay.SkyBox
//需要先查找天空盒这个物体才能操作
skyBox = await Core.GameObject.asyncFind('天空盒的gameObjectId') as Gameplay.SkyBox
if(skyBox){
    skyBox.skyDomeTextureAssetByID = '101570'
    skyBox.cloudEnable = false
}
let skyBox: Gameplay.SkyBox
//需要先查找天空盒这个物体才能操作
skyBox = await Core.GameObject.asyncFind('天空盒的gameObjectId') as Gameplay.SkyBox
if(skyBox){
    skyBox.skyDomeTextureAssetByID = '101570'
    skyBox.cloudEnable = false
}

现操作天空盒方式:

TypeScript
//可以直接在mw.Skybox下进行操作
mw.Skybox.skyDomeTextureID = '101570'
mw.Skybox.cloudVisible = false
//需要先将star设置为显示状态再修改参数
mw.Skybox.starVisible = true;
mw.Skybox.starTextureID = "14307"
//可以直接在mw.Skybox下进行操作
mw.Skybox.skyDomeTextureID = '101570'
mw.Skybox.cloudVisible = false
//需要先将star设置为显示状态再修改参数
mw.Skybox.starVisible = true;
mw.Skybox.starTextureID = "14307"

模块框架通信(net_方法)改动

现在编辑器中的模块管理框架在双端通信的传输参数上面做了一点修改,原来net_方法中默认存在player?这个可选参数,系统会默认该参数为调用该方法的客户端的player,这会导致在使用net_传递自定义的可选参数时出现异常,例如以下写法会导致接收到的参数中多出一个object(默认传输的Player),从而导致无法正确获取自定义传输的数据:

TypeScript
 public net_release(onlyID: number, ...args: any[]) {
        console.log("接收到的id:" + onlyID)
        console.log("接收到的参数:" + args)
    }
    //此时接收到的参数args中末尾将多出player导致数据混乱
 public net_release(onlyID: number, ...args: any[]) {
        console.log("接收到的id:" + onlyID)
        console.log("接收到的参数:" + args)
    }
    //此时接收到的参数args中末尾将多出player导致数据混乱

所以net_方法中默认传输player的这个特性在027上将被取消,例如:

TypeScript
/**服务端模块错误示范*/
public net_test(player?: mw.Player){
    //如果player不传参数,此处无法获取player
    this.getClient(player)
}
/**服务端模块错误示范*/
public net_test(player?: mw.Player){
    //如果player不传参数,此处无法获取player
    this.getClient(player)
}

类似的代码将无法获取player。正确的传输player方法:

TypeScript
/**服务端模块*/
public net_getPlayer(playerId: number){
    //使用playerId进行传输
    const player = mw.Player.getPlayer(playerId);
    //在服务端net_方法中调用,可以拿到调用该方法的服务端的player
    const player2 = this.currentPlayer;
}
/**服务端模块*/
public net_getPlayer(playerId: number){
    //使用playerId进行传输
    const player = mw.Player.getPlayer(playerId);
    //在服务端net_方法中调用,可以拿到调用该方法的服务端的player
    const player2 = this.currentPlayer;
}

大家在升级完旧工程后可能会遇到运行时某些功能不起反应,例如升级后出现关卡无法记录,背包物品无法使用等情况,并且有类似"Cannot read Property 'XXX' of null"报错显示错误位置在net_XXX方法下,如:

img

可以检查一下代码中的RPC通信方法参数中有没有使用player?的情况。

摄像机部分改动

本次更新后,相机位置模式的作用将更加明确,方便大家理解如何操作摄像机。同时修改了相机位置模式的部分默认设置,使其更加合理易用。摄像机部分的更新可能会导致可能之前调好的部分摄相机出现偏移,若升级项目之后发现相机位置出现了偏移,可以排查以下情况:

2. 相机旋转模式的变动

相机旋转模式cameraRotationMode,现替换为了rotationMode。在原来版本中设置为"旋转固定"时,摄像机会将世界旋转设置成(0,0,0)从而出现一个突然的偏移。在027升级后将rotationMode设置为"旋转固定"时,将会沿用上一帧控制器的旋转值(摄像机相对于挂载对象的旋转)。在升级编辑器版本之后如果相机旋转出现混乱,可以在原代码上做以下更改。

旧版本代码:

TypeScript
//获取相机
let camera = this.currentPlayer.character.cameraSystem;
camera.cameraRotationMode = Gameplay.CameraRotationMode.RotationFixed;
//此处相机世界旋转会设为(0,0,0)
let transform = new Type.Transform();
transform.location = new Type.Vector(-940, -554.5, 168);
transform.rotation = new Type.Rotation(-9, -4.5, 29);
camera.cameraRelativeTransform = transform;
//获取相机
let camera = this.currentPlayer.character.cameraSystem;
camera.cameraRotationMode = Gameplay.CameraRotationMode.RotationFixed;
//此处相机世界旋转会设为(0,0,0)
let transform = new Type.Transform();
transform.location = new Type.Vector(-940, -554.5, 168);
transform.rotation = new Type.Rotation(-9, -4.5, 29);
camera.cameraRelativeTransform = transform;

修改后代码:

TypeScript
//获取相机
let camera = Camera.currentCamera;
camera.rotationMode = mw.CameraRotationMode.RotationControl;
//此处相机世界旋转不会自动设为(0,0,0),故旧工程升级时若需要维持之前的效果,需要手动设置
this.getCamera.springArm.worldTransform.rotation = Rotation.zero
//后续正常操作
let transform = new mw.Transform();
transform.position = new mw.Vector(-940, -554.5, 168);
transform.rotation = new mw.Rotation(-9, -4.5, 29);
camera.localTransform = transform;
//获取相机
let camera = Camera.currentCamera;
camera.rotationMode = mw.CameraRotationMode.RotationControl;
//此处相机世界旋转不会自动设为(0,0,0),故旧工程升级时若需要维持之前的效果,需要手动设置
this.getCamera.springArm.worldTransform.rotation = Rotation.zero
//后续正常操作
let transform = new mw.Transform();
transform.position = new mw.Vector(-940, -554.5, 168);
transform.rotation = new mw.Rotation(-9, -4.5, 29);
camera.localTransform = transform;

2. 相机位置模式的变动

相机位置模式cameraLocationMode,现替换为了positionMode。在原来版本中设置为"位置固定"时,摄像机会使用弹簧臂的相对坐标作为世界位置(零点位置加上弹簧臂偏移)。在027升级后将positionMode设置为"位置固定"时,将会直接沿用上一帧世界位置。在升级编辑器版本之后如果相机位置出现混乱,可以在原代码上做以下更改。

旧版本代码:

TypeScript
//获取相机
let camera = this.currentPlayer.character.cameraSystem;
camera.cameraLocationMode = Gameplay.CameraLocationMode.LocationFixed;
//此处相机世界坐标会设为相机弹簧臂偏移值
let transform = new Type.Transform();
transform.location = new Type.Vector(-940, -554.5, 168);
transform.rotation = new Type.Rotation(-9, -4.5, 29);
camera.cameraRelativeTransform = transform;
//获取相机
let camera = this.currentPlayer.character.cameraSystem;
camera.cameraLocationMode = Gameplay.CameraLocationMode.LocationFixed;
//此处相机世界坐标会设为相机弹簧臂偏移值
let transform = new Type.Transform();
transform.location = new Type.Vector(-940, -554.5, 168);
transform.rotation = new Type.Rotation(-9, -4.5, 29);
camera.cameraRelativeTransform = transform;

修改后代码:

TypeScript
//获取相机
let camera = Camera.currentCamera;
//在切换模式前先记录摄像机弹簧臂的相对偏移
let tempOffset = camera.springArm.localTransform.position;
camera.positionMode = mw.CameraPositionMode.PositionFixed;
//此处相机世界坐标不会设为相机弹簧臂偏移值,故旧工程升级时若需要维持之前的效果,需要手动设置
camera.springArm.localTransform.position = tempOffset;
//后续正常操作
let transform = new mw.Transform();
transform.position = new mw.Vector(-940, -554.5, 168);
transform.rotation = new mw.Rotation(-9, -4.5, 29);
camera.localTransform = transform;
//获取相机
let camera = Camera.currentCamera;
//在切换模式前先记录摄像机弹簧臂的相对偏移
let tempOffset = camera.springArm.localTransform.position;
camera.positionMode = mw.CameraPositionMode.PositionFixed;
//此处相机世界坐标不会设为相机弹簧臂偏移值,故旧工程升级时若需要维持之前的效果,需要手动设置
camera.springArm.localTransform.position = tempOffset;
//后续正常操作
let transform = new mw.Transform();
transform.position = new mw.Vector(-940, -554.5, 168);
transform.rotation = new mw.Rotation(-9, -4.5, 29);
camera.localTransform = transform;

交互物部分的改动

1. 交互位置更加准确

本次升级之后,交互物的交互位置会在交互物的坐标处,这样大家之后在调整交互位置的时候能更方便地找到交互点。同时会导致在旧工程升级后,可能会出现交互物交互位置偏移的情况,例如:

img

此时只需要修改交互物坐标即可解决

imgimg

2. 交互结束成功的逻辑需要用事件委托

在新版本中,使用mw.Interactor.leave()结束交互后,为了保证已经完全退出交互,需要使用需要使用onLeave委托来执行退出交互物之后的逻辑。若在运行游戏时出现在结束交互之后的逻辑不正确(例如结束交互之后的冲量未生效或位置修改未生效),有可能是因为当前交互并未完全退出,可以使用以下方法确保退出交互。

结束交互旧用法:

TypeScript
//旧用法
let isExitSuceed = await this.interactivitys[triggerIndex].leave(this.curPlayers[triggerIndex].character.worldTransform.position)
if (!isExitSuceed) return;
//后续逻辑
//旧用法
let isExitSuceed = await this.interactivitys[triggerIndex].leave(this.curPlayers[triggerIndex].character.worldTransform.position)
if (!isExitSuceed) return;
//后续逻辑

结束交互新用法:

TypeScript
//新用法
let isExitSuceed = this.interactivitys[triggerIndex].leave(this.curPlayers[triggerIndex].character.worldTransform.position)
if (!isExitSuceed) return;

this.interactivitys[triggerIndex].onLeave.add(()=>[
    //后续逻辑
])
//新用法
let isExitSuceed = this.interactivitys[triggerIndex].leave(this.curPlayers[triggerIndex].character.worldTransform.position)
if (!isExitSuceed) return;

this.interactivitys[triggerIndex].onLeave.add(()=>[
    //后续逻辑
])

Util相关变动

1. 部分命名空间所属变动

在027之前的版本,部分Util相关接口处于特定的命名空间之下,例如mw.TweenUtil.EasingFunction需修改为mw.TweenEasingFunction

img

我们使用相关接口写出来的代码是如下情况:

img

在027中,有不少接口直接在mw命名空间之下了,写法改为如下情况

img

2.Util命名空间删除, SystemUtil改为了类并且命名空间有变动

Util命名空间已经被删除,升级027后相关部分代码直接删除即可

264d029a-2a2a-445f-8f9f-52fdff74afe5

8d3694b0-555d-4a72-8efb-d82b6e256572

026上,SystemUtil是作为命名空间存在

img

故在026上的使用方法如下:

TypeScript
DataStorage.setTemporaryStorage(Util.SystemUtil.isPIE);
DataStorage.setTemporaryStorage(Util.SystemUtil.isPIE);

在027上,SystemUtil改为了类并且从Util命名空间移动到了mw命名空间之下

img

SystemUtil改为了类并且从Util命名空间移动到了mw命名空间之下,因此写法也产生了改变:

TypeScript
DataStorage.setTemporaryStorage(SystemUtil.isPIE)
DataStorage.setTemporaryStorage(SystemUtil.isPIE)

3. SystemUtil.currentPlatform枚举类型变动

SystemUtil.currentPlatform在026时为字符串(string)类型,而在027上改为了枚举类型。当旧工程升级后可能有报错提示"This comparison appears to be unintentional because the types 'RuntimePlatform' and 'string' have no overlap",如图:

img

img

动画资源播放改动

在026中动画资源播放没问题,但是升级后如果出现了移动时播动画,只有上半身或下半身正常播放动画,另外半身没有正确的表现角色的基础姿态(举个例子:一个上半身喝酒的动画,在026中移动时播放会表现为上半身喝酒,下班身播放跑步动画),此时需要设置动画的slot为对应的上下半身情况,才能正确表现

027中没有使用slot设置动画播放插槽

027中使用slot设置动画播放插槽

typescript
// 示例代码
let ani = this.character.loadAnimation("98747")
// 加入slot后能正确表现动画
ani.slot = AnimSlot.Upper
ani.play()
// 示例代码
let ani = this.character.loadAnimation("98747")
// 加入slot后能正确表现动画
ani.slot = AnimSlot.Upper
ani.play()

升级后材质颜色出现偏差

极个别项目升级后可能出现整体材质颜色偏差,如下对比:

img

img

上图中颜色仍然是偏暗的,这需要挨个排查材质颜色是否正确,并手动去修改材质颜色,因为从工程内容里找到材质并修改颜色,多个使用到对应材质的对象都会恢复正常,因此整体修改量是可控的

修改方式如下步骤:

  1. 在工程内容中找到需要修改的材质
  2. 在材质编辑界面的属性面板中找到漫反射颜色参数
  3. 将其值修改为白色
  4. 回到游戏主视口页面查看效果是否符合预期,如果还有差异,可以尝试再调整颜色参数进行微调

视频:

偶现因为代码报错保存项目后,自定义属性数据消失

升级成功后Levels文件夹下会有026NewLevel.level文件(026版本的备份文件)及NewLevel.level文件(升级后的工程使用中的文件)

image-20231020102822211

在升级过程中,如果自定义属性相关代码处于报错中,保存项目后可能会导致数据丢失

9ac0befd-1c54-4d03-8d9f-4ea1b1160d7d

image-20231019190824504

image-20231019191847959

此时我们需要下载:修复工具 ,并将其复制粘贴到对应工程里的Levels文件里,双击运行该exe文件,运行完成后再查看NewLevel.level中的表现,若与备份文件中一样,则修复完成,如若仍有问题,请及时到论坛反馈

image-20231020102603890

其它情况修改

由于重名导致的自动升级的错误替换

如果出现了下列类似情况或一段代码看不出问题来,可以通过备份的026项目,比对原本代码与新代码的情况,自动替换出现了非接口替换情况的话,很可能就是同名方法导致了替换错误。

1. 可能存在为了节省每次写EffectService.GetInstance(),自己写了个类成员的情况

img

实际使用情况如下:

img

在自动升级后出现了代码变成GeneralManager.rpcPlayEffectAtLocation

TypeScript
let play_id = this.GeneralManager.rpcPlayEffectAtLocation("86375",player_transfrom.position,0,mw.Rotation.zero,new mw.Vector(2,2,2))
let play_id = this.GeneralManager.rpcPlayEffectAtLocation("86375",player_transfrom.position,0,mw.Rotation.zero,new mw.Vector(2,2,2))

导致报错显示"Property 'GeneraManager' does not exist on type 'XXX' "如图:

img

img

我们需要手动将其修改为EffectService.playAtPosition,并注意里边的参数修改后要对上

TypeScript
let play_id = this.EffectService.playAtPosition("86375",player_transfrom.position,{loopCount:0,rotation:mw.Rotation.zero,scale:new mw.Vector(2,2,2)})
let play_id = this.EffectService.playAtPosition("86375",player_transfrom.position,{loopCount:0,rotation:mw.Rotation.zero,scale:new mw.Vector(2,2,2)})

img

2. 用户自己写了一个setWorldLocation方法

img

TypeScript
// 自动升级后被修改为了,需要手动修改回去
this.worldTransform.position = player.charactre
// 自动升级后被修改为了,需要手动修改回去
this.worldTransform.position = player.charactre

3. 用户自己写了一个setWholeBody方法

自动升级后被修改为

img

需要手动将它替换回去

img

4. 用户自己写了一个名叫Events的对象

img

自动升级后被修改为

img

需要手动替换回去

img

自动升级代码出错

偶现过attachToGameObject接口升级为parent时,后续自定义函数升级后使用方式错乱的情况

企业微信截图_16974442374658

企业微信截图_16974442551048

企业微信截图_16974442871997

玩家进入离开游戏接口所在类变更

相关接口从EventListener修改到了MulticastDelegate上,如下所示修改

img

由于onPlayerLeave,onPlayerJoin已经不属于EventListener类下,故会报错显示"Type 'void' is not assignable to type 'EventListener' ",如图:

img

此时我们将声明时定义的类改为MulticastDelegate即可解决报错,修改后如图:

img

同时,对于Player.onPlayerJoin、onPlayerLeave等玩家加入退出相关的委托,慎用clear()函数清空委托,可能会导致玩家数据未清空从而导致报错。需要使用Player.onPlayerJoin.remove清除委托,例如:

TypeScript
//加入委托
Player.onPlayerLeave.add(this.onPlayerLeftGame)
//清除委托
Player.onPlayerLeave.remove(this.onPlayerLeftGame)

private onPlayerLeftGame(){
//玩家退出时执行的逻辑
}
//加入委托
Player.onPlayerLeave.add(this.onPlayerLeftGame)
//清除委托
Player.onPlayerLeave.remove(this.onPlayerLeftGame)

private onPlayerLeftGame(){
//玩家退出时执行的逻辑
}

PlayerManagerExtesion断言判断出现"never"

PlayerManagerExtesion.isNpc(obj)PlayerManagerExtesion.isCharacter(obj)作为判断时,后面的else条件中出现了obj类型为never,如图报错显示'player' does not exist on type 'never'

原因是由于前面isNPC(obj)函数中会修改obj的类型,导致后续的逻辑可能发生改变,此时需要手动将原来的isNPC或者inCharacter改为对应的判断逻辑:

isNPC(obj)改为(obj instanceof Character) && obj.player == null

isCharacter(obj)改为(obj instanceof Character) && obj.player != null

img

img

严格编译导致的属性赋值写法错误(例如"?." )

由于自动升级时代码情况较复杂,升级后可能出现属性设置左值赋值写法会导致编译报错,报错提醒为"The left-hand side of an assignment expression may not be an optional property access"如图

img

img

img

由于可见性设置从父对象获取,导致的UI显示异常

027升级之后,对象属性面板中的"可见性"设置为"从父对象获取"将生效,这可能导致项目升级后出现需要游戏中途显示的UI在一开始就显示的情况,例如:

img

此时需要在对象管理器中找到这个UI,并且将其属性面板中的"可见性"由"从父对象获取"设置为"关闭"即可解决

img

部分类构造函数被标记为私有导致拓展类失效

在027之前,可能有用户尝试自己再封装过代码,使其更方便于自己调用,如下图:在026版本中的继承SoundService并重写部分接口

img

在027中,这种操作不可行了,不可继承并拓展

报错提示:Cannot extend a class 'mw.SoundService'. Class constructor is marked as private

中文意思:无法扩展类“mw.SoundService”。类构造函数标记为私有

img

对象非空判断连写导致报错

在027之前,部分代码存在使用&&做判空处理的写法,首先判断character不为空然后才执行后续代码,如图:

img

027自动升级替换方法后,attachToGameObject接口会被替换为parent,而前半截的这种写法会报错,报错提示" ';' expected ",如图:

img

img

自动化升级结束后导致复杂逻辑出现升级错误

本次升级的大部份内容编辑器会自动帮我们修改,在自动升级中有可能会因为代码中的复杂逻辑导致升级出错,有可能是语句顺序错误,也有可能出现标点、参数不正常。这时候就需要手动将其修改至正常的语句。例如:

  • 升级后rpcPlayAnimation()出现语句混乱情况
TypeScript
//升级前
this.expressUI.setCurAction(Gameplay.getCurrentPlayer().character.playAnimation("XXX", 0, 1));
//错误升级后
PlayerManagerExtesion.rpcPlayAnimation(this.expressUI.setCurAction(Player.localPlayer.character, "XXX", 0, 1));
//升级前
this.expressUI.setCurAction(Gameplay.getCurrentPlayer().character.playAnimation("XXX", 0, 1));
//错误升级后
PlayerManagerExtesion.rpcPlayAnimation(this.expressUI.setCurAction(Player.localPlayer.character, "XXX", 0, 1));

此时需要查看API文档,将其修复为正确状态:

TypeScript
//正确修改
this.expressUI.setCurAction(PlayerManagerExtesion.rpcPlayAnimation(Player.localPlayer.character,"XXX", 0, 1));
//正确修改
this.expressUI.setCurAction(PlayerManagerExtesion.rpcPlayAnimation(Player.localPlayer.character,"XXX", 0, 1));

同样,还会出现另外一种较乱的情况

TypeScript
//升级前
Gameplay.getPlayer(playerId).character.playAnimation("XXX",0,1)
//错误升级后
mw.getPlayerPlayerManagerExtesion.rpcPlayAnimation((playerId).character, "XXX", 0, 1);
//升级前
Gameplay.getPlayer(playerId).character.playAnimation("XXX",0,1)
//错误升级后
mw.getPlayerPlayerManagerExtesion.rpcPlayAnimation((playerId).character, "XXX", 0, 1);

恢复正常的方法也一样,需要在顺序颠倒的语句中进行排列组合,将其恢复成符合格式的语句

TypeScript
//正确修改
PlayerManagerExtesion.rpcPlayAnimation(Player.getPlayer(playerId).character, "XXX", 0, 1);
//正确修改
PlayerManagerExtesion.rpcPlayAnimation(Player.getPlayer(playerId).character, "XXX", 0, 1);

调用AccountService.addFriend添加好友

以前传入的参数需要包含userID和openID,现在只需要传入userID即可

由于设置旋转缩放的时候物体已经被销毁导致的"Cannot set property position of undefined"报错

在之前版本的setWorldLocation()、setWorldRotation()等设置旋转缩放的函数,在027版本自动升级时会将其升级为worldTransform.position = XXX,这种方式下如果在设置时物体已被销毁则会出现运行时报错,报错提示"Cannot set property position of undefined"。遇到此类报错需要在设置物体位置的时候加入判断物体是否被销毁。修改后代码如下:

TypeScript
let obj = GameObject.spawn("1234")
if(obj){
    obj.worldTransform.position = new Vector(0,0,0)
}
let obj = GameObject.spawn("1234")
if(obj){
    obj.worldTransform.position = new Vector(0,0,0)
}

属性面板可见性与运行时表现不一致

可以看到视频里的模型在属性面板中可见性设置为了关闭,但是运行时仍然能看到角色显示出来,目前极少遇到该问题,目前绕过方案是去跟目录的Levels文件里找到xxx.level文件并搜索到Name对应名字的对象,然后讲其repIsVisible值改为true,如下图中红框里的值:

img_v2_918256aa-0e06-4977-badd-b7419fe31a4g

论坛提供的导表工具更新,使用旧版本的EXCEL表转换成ts文件的工具导出表格后出现报错

由于027上有许多类型名做了更改,若使用旧的导表工具则会出导出脚本类型名不存在的情况。在更新到027版本后,需要使用新版本的导表工具 导表工具

下载完新版本的导表工具后,在重新进行一次表格导出即可解决导出文件报错。

ModuleManager修改为ModuleService

部分项目使用过ModuleManager管理调用模块,现在命名修改了,并且调用方式也进行了简化,修改案例如下:

2fecd003-c903-4c91-b9ca-12878e11a4ab

6afa3839-ef27-45af-afd3-372dbf70e76c

Pawn下的characterName属性被升级为displayName

自动升级时可能存在部分Pawn类型的name被错误的修改为了displayName,将其改为name即可

image-20231018131951280

image-20231018131310654

image-20231018131343105

Play3DSound传入参数变更

Play3DSound中innerRadius与maxDistance分别改为radius与falloffDistance

f6d8dbc0-bff6-481b-9841-5fd734ca64fc

dc1b6efd-e176-4f47-8082-cd5446a0e2e4

GameInitializer用法变更

某些特殊项目里有用到GameInitializer这种写法,升级后需要将其改为mwext["GameInitializer"]

9e33c174-3d14-42ac-8503-c8dabe33bd42

企业微信截图_16975087432572

ConvertScreenResult的worldLocation升级错误

ConvertScreenResult的属性worldLocation由于和gameObject属性的修改重合,自动升级后需要手动修改为worldPosition

73c75e89-6d33-4b99-9530-497852a54661

a6d95222-e942-4fb4-84f5-9278a1e1e087

不写@component标签,导致自定义面板数值保存重启项目后丢失

如果一个脚本类作为基类,一个子类继承它,但是不写@component标签。在子类里面写一个面板属性后修改面板值,保持项目后重进,面板值会消失

image-20231020104748174

image-20231020104818837