# 序列化系统

序列化系统是用来把小游戏框架的实例属性,自动化地序列化为可持久化的数据,以及将数据再反序列化为实例属性。

许多地方都是用了序列化系统,比如保存与加载组件或是资源,脚本组件的持久化,inspector窗口中用来展示属性,配置窗口等。

# 脚本组件

开发者在 Project 窗口中创建的脚本组件模版中,就使用了序列化系统。

@engine.decorators.serialize("Move")
export default class Move extends engine.Script {
  @engine.decorators.property({
    type: engine.TypeNames.String
  })
  public name: string = "myname"
}

serialize装饰器,首先开发者可以通过 serialize 装饰器来标明它是一个可序列化的脚本,并且给这个序列化类型设置一个全局唯一的名字作为入参。

property装饰器,开发者可以给他的一个属性加 property 装饰器,去标明这是一个可序列化的属性,通过 type 来表示这是一个什么类型的属性。

# property装饰器

标记完属性了以后,小游戏框架就知道该如何处理这个可序列脚本,主要在两个地方体现。

  1. UI。序列化根据这个类型在 Inspector 窗口中显示这个属性出来,并且保证所见即所得。在播放态调试的过程中,这个属性在内存中的值会实时更新到 Inspector 面板上,开发者也可以通过面板直接修改这个属性的值。支持多选以及批量编辑。

  2. 保存与恢复。同一个脚本类在不同的 entity 上面可以有不同的实例,不同实例可以有不同的属性值。开发者在编辑态可以直接在 UI 上修改属性的值,在保存 Scene 的时候,序列化系统能通过 property 装饰器知道用什么类型来保存这个属性。当再次打开这个 Scene 的时候,序列化系统也能知道该如何将这个属性恢复成之前保存的数值。从而让小游戏工具能实现资源可视化编辑的能力。但是在播放态,属性的修改是无法落地保存的。

# 属性类型

property装饰器提供了非常多的类型。

# 纯数值类型

比如String,Boolean,Number,Object。

Object类型比较特殊,就是一般的JSON Object,但不保持引用关系,保存的时候单纯JSON.stringify(),反序列化的时候 JSON.parese 因此实例化以后有多份实例。也不展示UI。建议使用嵌套类型的方式来实现

# 数组类型

开发者可以通过给类型加一个数组来完成定义。比如,type: [engine.TypeNames.String],意味着是一个不定长字符型数组。

@engine.decorators.property({
  type: [TypeNames.String]
})
public names: string[] = ["myname"]

# Component组件类型

声明为组件类型,意味着属性对某个组件的引用关系。开发者先声明一个这样的类型,然后就可以在 Hierarchy 窗口中将一个 entity 拖入到 inspector 对应的属性上,工具在这个 entity 上去找这个类型的Component,然后将引用赋值到属性上。保存的时候会特殊处理引用关系的序列化,保证下次启动的时候能恢复。在播放态开发者也可以用代码来赋值,inspector也会实时更新。

@engine.decorators.property({
  type: TypeNames.Transform3D
})
public target: engine.Transform3D

type不一定非要是 engine.TypeNames 下的一个枚举值,也可以直接声明为小游戏框架的 compoennt 类。


@engine.decorators.serialize("Shoot")
export default class Shoot extends engine.Script {...}

@engine.decorators.serialize("Move")
export default class Move extends engine.Script {
  @engine.decorators.property({
    type: engine.Transform2D
  })
  public target: engine.Transform2D

  @engine.decorators.property({
    type: Shoot
  })
  public shootComp: Shoot
}

# 资源类型

申明为资源类型,意味着属性对某个资源单例的引用关系。比如 type 类型是一个Texture2D的时候,开发者可以把一个 Project 窗口的texture2d类型的图片拖到 Inspector 窗口的属性上。

保存修改后就能让这个 Scene 或是 Prefab 静态依赖这个资源。当使用时,其实是将这个资源的单例赋值给属性,这样开发者就不需要自己去 Loader.load 这个资源了。

@engine.decorators.property({
  type: TypeNames.Texture2D
})
public tex: engine.Texture2D

# 属性嵌套

开发者可以创建一个是用序列化系统的普通类,不一定是脚本组件,用于实现层层嵌套的复杂属性。


@engine.decorators.serialize("Rank")
export class Rank {
  @engine.decorators.property({
    type: [engine.TypeNames.String]
  })
  public rankNames: String[] = ['青铜''黄金']
}

@engine.decorators.serialize("Soldier")
export default class Soldier extends engine.Script {
  @engine.decorators.property({
    type: Rank,
  })
  public rank: Rank = new Rank()
}

# 自定义序列化与反序列化

@property除了可以标示 UI 的可见即所得以外,还用来表示如何持久保存在Prefab/Scene中。如果使用了默认的逻辑开发者可以完全不需要理解数据是如何保存的,但是如果有些数据过于复杂不方便使用默认的保存能力,开发者也自定义序列化与反序列函数。

开发者可以定义 onDeserialized 作为自定义反序列化函数,也可以定义 onSerialized 作为序列化函数。自定义的函数在执行完默认的逻辑之后执行,将默认处理的结果返回给开发者的代码进行后处理。

@engine.decorators.serialize("Soldier")
export default class Soldier extends engine.Script {
  @engine.decorators.property({
    type: engine.TypeNames.String
  })
  public Name: String = 'janzenzhang'
  // 自定义反序列化逻辑
  onDeserialized(json) {
    this.Name = json.Name.toLowerCase()
  }
  // 自定义序列化逻辑
  onSerialized(dataJson) {
    dataJson.Name = this.Name.toUpperCase()
  }
}

# 属性配置项

@property装饰器除了可以定义 type 标示是什么类型的属性以外,还有许多其他配置能力。

interface IPropertyBaseConfig {
  deserialize?: boolean; // 是否参加反序列化
  serialize?: boolean; // 是否参加序列化
  required?: boolean; // 是否可以是undefined
  visible?: boolean; // 在工具内是否可见,=== false时不显示
  tooltips?: string; // 属性描述
  placeholder?: string; // string类型的 input 框的 placeholder 属性
  readonly?: boolean; // ===true时,不可编辑
  animatable?: boolean; // true时,表示此值可以被动画(animationClip)控制。
}

# 热更新

在编辑态,开发者对脚本的修改都能热更新到工具上。脚本编辑后会先进行编译,在编译之后会注入到环境中,并把场上所有对应的组件替换为新的,同时尽可能保留之前的数据。