在游戏开发中,有一条我个人总结出来的原则:数据决定显示

什么是“数据决定显示”?

举个例子:
在界面中开发音乐的开关时,往往都会使用 nodeA 表示音乐开,nodeB 表示音乐关,用一个按钮事件来控制音乐的开关。
如果是“显示决定显示”,那么按钮开关的逻辑应该为:
nodeA.active = !nodeA.active;
nodeB.active = !nodeB.active;
如果是“数据决定显示”,则需要一个新数据(假设为 switch)来表示开关,新的逻辑如下:
switch = !switch;
nodeA.active = switch;
nodeB.active = !switch;
可以看到,虽然代码很类似,但是二者在逻辑上却有很大的不同。后者将数据独立出来,成为了一个中间层,那么游戏逻辑只需要操作数据即可,再由数据触发相应的显示逻辑。

分类

“数据决定显示”这一原则下还有很多不同情况的分类:

  1. 对应关系:
    1. 一个数据决定多个显示
    2. 多个数据决定一个显示
    3. 多个数据决定多个显示
  2. 显示延迟
    1. 同步显示,无延迟
    2. 异步显示,有延迟(一般是显示的动画过程)
  3. 数据冲突处理
    1. 在显示过程中传入新数据
    2. 在显示过程结束后,执行一个新的显示过程
  4. 触发条件
    1. 在数据发生变动时,调用显示逻辑
    2. 在 update 中获取数据,调用显示逻辑(也可以间隔多帧调用)

      在 cocos-creator 中的简单实现

      不同的分类,也对应了不同的显示,这里仅选择一种情况进行实现,即在游戏中常用的:分数变动。
  • 多个数据决定一个显示
  • 异步显示,有延迟(一般是显示的动画过程)
  • 在显示过程中传入新数据
  • 在 update 中获取数据,调用显示逻辑(也可以间隔多帧调用)

代码部分:

  1. // test.ts
  2. const { ccclass, property } = cc._decorator
  3. @ccclass
  4. export class PanelTest extends cc.Component {
  5. start() {
  6. // 每2s加500分
  7. this.schedule(() => {
  8. this.set_score(this.score += 500)
  9. }, 2, 800)
  10. }
  11. update() {
  12. this.update_score()
  13. }
  14. // 数据部分
  15. score: number = 0 // 实际的分数
  16. show_score: number = 0 // 显示的分数
  17. set_score(v: number) {
  18. this.score = v
  19. }
  20. // 显示部分
  21. @property(cc.Label)
  22. label_score: cc.Label = null
  23. update_score() {
  24. // 每一步至少为19,至多为当前分与实际分差距的1/20
  25. let step = Math.max(19, Math.floor((this.score - this.show_score) / 20))
  26. // 加分
  27. this.show_score += step
  28. // 防止分数溢出
  29. this.show_score = Math.min(this.score, this.show_score)
  30. // 修改分数显示
  31. this.label_score.string = `${this.show_score}`
  32. }
  33. }

GIF部分(gif帧率较低,在游戏内帧率更高):
GIF.gif

以上简单实现的优化

以上简单实现还有一些问题:

  1. 无法明显的显示出数据与显示之间的绑定关系(特别是在多数据时)

    使用一个函数绑定数据获取和显示

    如下所示的 bind_data_show,如果获得的数据是多个,还可以通过扩展运算符(…)进一步优化。 ```typescript // test.ts

const { ccclass, property } = cc._decorator /* 绑定数据和显示 / const bind_data_show = (get_data_f: () => T, update_ui_f: (data: T) => void) => { update_ui_f(get_data_f()) } export class Data {

static ins: Data;

constructor() {
    setInterval(() => {
        this.set_score(this.score + 500)
    }, 2e3)
}

score: number = 0
set_score(v: number) {
    this.score = v
}
get_score() {
    return this.score
}

} Data.ins = new Data()

@ccclass export class PanelTest extends cc.Component {

update() {
    this.update_ui()
}

// 显示部分

update_ui() {
    bind_data_show(() => Data.ins.get_score(), (score) => this.update_score(score))
}

@property(cc.Label)
label_score: cc.Label = null
show_score: number = 0  // 显示的分数
update_score(score: number) {
    // 每一步至少为19,至多为当前分与实际分差距的1/20
    let step = Math.max(19, Math.floor((score - this.show_score) / 20))
    // 加分
    this.show_score += step
    // 防止分数溢出
    this.show_score = Math.min(score, this.show_score)
    // 修改分数显示
    this.label_score.string = `${this.show_score}`
}

} ```