跳转至

场景 / Scene

场景是 AVG 的重要组成部分之一,也就是 AVG 里的背景图。

此处我们之所以采用「场景」而不是「背景」,是因为在 AVGPlus 中,一个完整的画面可能由很多个 分层的背景图 组成,他们叠加在一起所呈现出来的最终效果,用「场景」的说法似乎更为贴切。

场景通过 scene 类进行管理,具备显示、隐藏、动画、滤镜等方法。

扩展知识

在 AVGPlus 引擎中,所有图像类的元素,都属于 Sprite (精灵), 尽管他们的使用场合不尽相同,但技术特性是完全一致的。他们都是同一个类派生出来的子类。

因此,立绘、场景、屏幕图片组件等元素,都属于此类别内,所以他们大多数 API 的用法几乎是一致的。

不过,这一点只是帮助您加深概念,对此不理解也完全没关系,因为并不影响学习 API 的使用。

关于范例加载

由于本章的范例都涉及较大的图片资源,可能在执行范例的时候导致加载速度较慢或有网络堵塞,这些都是正常现象。请耐心等待加载完毕,首次加载过后会有浏览器缓存,再次执行可获最佳体验。

关于本章

本章内容和 立绘 的内容是大量重合的,只有 API 的名称略有出入,可以先前往立绘篇学习细节,尤其是关于动画方面的内容。部分细节在本章不会复述,但仍然会有大量例子可供参考。

显示场景

我们可以通过 scene 类来管理场景,显示场景通过 scene.show() 完成。

例子与演示

我们可以简单地显示一个背景图片,仅需指定一个名称和文件的路径。

1
scene.show("izakaya", "resdiy/居酒屋-2560x1440.png");

我们平平无奇地显示了一张名为 “izakaya” 的居酒屋背景图。不过因为此处范例用的是2K分辨率的图,目前看上去似乎显示并不完整。

前一个范例我们可以看到居酒屋背景图并不能完全显示,某些情况下如果我们的图片素材相对而言分辨率过大,可以考虑把图片拉伸至游戏屏幕的尺寸,但需要注意的是图片的比例需要和分辨率一致,否则会变形。

1
2
3
4
5
scene.show("izakaya", "resdiy/居酒屋-2560x1440.png", {
  renderer: {
    stretch: true // 我们在渲染器中把 stretch 属性设为 true 即可
  }
});
1
2
3
4
5
6
7
8
9
scene.show("izakaya", "resdiy/居酒屋-2560x1440.png", {
  renderer: {
    // 把尺寸设置为原图的 40%
    size: "40%",

    // 居中显示 (x, y)
    position: "(center, center)",
  }
});

关于位置描述

AVGPlus 中所有表示位置的单位写法都一致,具体如何指定 position 请参考:坐标与尺寸

我们还可以在 renderer 中设置滤镜,让场景获得较好的表现效果。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
scene.show("izakaya", "resdiy/居酒屋-2560x1440.png", {
  renderer: {
    // 让场景拉伸至窗口大小
    stretch: true,

    // 添加一个移轴镜头效果
    filters: [
      {
        name: "tilt-shift",
        data: {
          gradientBlur: 300,
        }
      }
    ]
  }
});

淡出淡入

我们可以很简单地给场景附加一个淡入的动画效果。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
scene.show("izakaya", "resdiy/居酒屋-2560x1440.png", {
  renderer: {
    // 让场景拉伸至窗口大小
    stretch: true,

    // 透明度初始为 0
    alpha: 0
  },
  animation: {
    timeline: [
      {
        duration: 7000,
        alpha: 1
      }
    ]
  }
});

多个场景的显示交替

如果希望在切换场景时,可以让两个场景进行交替动画。

提前把需要切换的场景加载,并使其透明度为0,再进行动画:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 显示居酒屋
scene.show("izakaya", "resdiy/居酒屋-2560x1440.png", {
  renderer: {
    stretch: true,
  }
});

// 等待 3 秒
flow.wait(3000);        

// 加载新的场景并淡入
scene.show("meeting-room", "resdiy/社团会议室-2560x1440.png", {
  renderer: {
    // 让场景拉伸至窗口大小
    stretch: true,

    // 透明度初始为 0
    alpha: 0
  },
  animation: {
    timeline: [
      {
        duration: 2000,
        alpha: 1
      }
    ]
  }
});

我们可以通过粒子,可以在某些场景的切换时获得更好的演出效果。

下面例子演示街道到积雪状态的切换。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
scene.show("street-sunny", "resdiy/古风街道-晴朗-2560x1440.png", {
  renderer: {
    stretch: true
  }
});

// 提前加载雪景图,把它的透明度设置为 0
// 防止下面显示时实时加载时导致卡顿
scene.show("street-snowy", "resdiy/古风街道-下雪-2560x1440.png", {
  renderer: {
    stretch: true,
    alpha: 0
  }
});

flow.wait(1000);

// 下雪特效
particle.snow({});

flow.wait(1000);

scene.animate("street-snowy", {
  timeline: [
    {
      duration: 5000,
      alpha: 1
    }
  ]
})

隐藏/移除场景

在显示一个场景之后,如果需要切换场景,则需要使用 scene.hide() 方法。

该方法支持在隐藏的时候播放一段关键帧动画,在动画完毕后会把场景对象彻底销毁。

例子与演示
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 显示一个场景
scene.show("street-sunny", "resdiy/古风街道-晴朗-2560x1440.png", {
  renderer: {
    stretch: true
  }
});

// 等待 3 秒
flow.wait(3000);

// 隐藏名为 street-sunny 的场景图
scene.hide("street-sunny");

hide() 方法的使用非常简单,只需要给它传递立绘时的名称 space 即可。 立绘在隐藏时,默认效果是淡出。

如果我们需要在移除场景的时候有更加丰富的动画效果,可以附加一段关键帧动画。

动画结束后,场景会被自动移除。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 显示一个场景
scene.show("street-sunny", "resdiy/古风街道-晴朗-2560x1440.png", {
  renderer: {
    stretch: true
  }
});

flow.wait(3000);

// 开始隐藏场景
scene.hide("street-sunny", {
  timeline: [
    {
      duration: 1000,     // 播放时长为 0.5s
      x: "-=500",         // 在当前 x 坐标的基础上 +500
      y: "-=500",         // 在当前 y 坐标的基础上 -500
      angle: "-=45",      // 旋转
      alpha: 0,           // 透明
    }
  ]
});

场景动画

例子与演示

针对普通的动画,我们搭配时间轴,添加关键帧即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 显示居酒屋
scene.show("izakaya", "resdiy/居酒屋-2560x1440.png", {
  renderer: {
    stretch: true,
  }
});

// 实现一个视线快速聚焦,并打量四周环境的动画效果
scene.animate("izakaya", {
  timeline: [
    {
      duration: 3000,
      scaleX: "+=0.2",    // 放大 1.2 倍
      scaleY: "+=0.2",    // 放大 1.2 倍
      ease: "easeInOut",
      filters: [
        {
          // 运动模糊滤镜
          // 放大时附加运动模糊效果
          name: "zoom-blur",
          data: {
            strength: 0.1
          }
        }
      ]
    },
    {
      duration: 1200,
      filters: [
        {
          name: "zoom-blur",
          data: {
            strength: 0
          }
        }
      ]
    },
    {
      duration: 3000,
      x: "-=200",
      ease: "easeInOut",
      filters: [
        {
          name: "zoom-blur",
          data: {
            strength: 0
          }
        }
      ]
    },
  ]
})

设置场景滤镜

使用 scene.filter() 方法可以给场上的场景附加一个或多个滤镜。和在 renderer 里设置滤镜是完全一样的。

在本小节,我们就不再简单地设置一个普通的滤镜啦。为了让您看到滤镜对场景的强大作用,这里我们会使用 自动滤镜

在前面我们看到的滤镜渲染后的图像都是静态的,不过在 AVGPlus 中,也有一些滤镜是支持自己动画的,换言说,只要添加了该滤镜,并且做一些简单的回调处理,场景就能动起来。

这一小节里,我们会用到一个强大的 自动滤镜 —— 置换滤镜 (Displacement)

关于置换滤镜 (Displacement)

在 3D 中,我们通常可以看到有一些凹凸不平的模型,实际上,这都是置换贴图的功劳。置换贴图是一种特制的黑白贴图,它通过计算置换可以让顶点进行偏移,从而让我们看到模型产生了凹凸不平的效果。

同理,在 2D 中,我们也可以用同样的方法来让场景图出现“凹凸不平”的效果,当然,这种效果在 2D 中体现在视觉效果上就是图片会变得扭曲。

关于置换滤镜你需要知道:

  • 需要传入一个置换贴图文件,目录路径默认位于 graphics/d-maps 下;如果不传,将会使用 AVGPlus 默认提供的一个微扭曲效果的置换贴图;
  • 置换滤镜带有 data.animate 字段,弱该字段的值为 true, 则每个帧循环,都会回调 data.onUpdate() 方法;
  • 通过 data.onUpdate() 方法,我们可以对置换贴图的精灵进行一些操作,达到动画目的;
  • 置换贴图在渲染的时候默认是平铺模式,也就是说它可以无限重复滚动。当然,贴图本身也应该是可以左右拼接起来的;

下面是相关例子。

例子与演示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 显示居酒屋
scene.show("izakaya", "resdiy/居酒屋-2560x1440.png", {
  renderer: {
    stretch: true,
  }
});

scene.filter("izakaya", [
  {
    // 置换滤镜的名称为 "displacement"
    name: "displacement",
    data: {
      // 定义一个置换贴图
      dmap: {
        file: "displacement_map.png",   // 置换贴图的文件名
      },
    }
  }
]);

唔,运行上面的例子可以发现,这居酒屋的场景突然变得扭曲了~

在上一个例子中,我们可以看到附加了置换滤镜置换,置换贴图的纹理让居酒屋场景图发生了很明显的扭曲。但它并没有动起来。现在,我们稍作修改,让它可以像水体一样出现扭曲的动画。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 显示居酒屋
scene.show("izakaya", "resdiy/居酒屋-2560x1440.png", {
  renderer: {
    stretch: true,
  }
});

scene.filter("izakaya", [
  {
    // 置换滤镜的名称为 "displacement"
    name: "displacement",
    data: {
      // 定义一个置换贴图
      dmap: {
        file: "displacement_map.png",   // 置换贴图的文件名
      },
      animate: true,                  // 指定为动画模式
      onUpdate: (dmapSprite) => {     // 一旦 animate 为 true, 回调就会生效
        /* 
        在帧循环回调中,我们对置换贴图的精灵,进行一个 x 轴的累加,也就是说在每一次回调中,置换贴图都会向右平移10像素,因为置换贴图是可以无限滚动的平铺模式,即使我们不断累加 x 轴它也永远不会超出屏幕范围。
        */
        dmapSprite.x += 3;
      },
    }
  }
]);

运行脚本范例,我们可以看到整个居酒屋都扭曲地动起来了。是不是很酷。

通过上面的例子,我们接触到了置换滤镜的使用,当然,置换滤镜是个特殊的滤镜,除此之外绝大多数都是普通的滤镜,使用也更加简单。

当然我们用的「居酒屋」来作为范例也并不完美,常见的置换滤镜一般用于打造扭曲效果,比如流动的水体、炙热扭曲的空气、醉酒状态下的人的视角。这里我们仅做抛砖引玉,要组合出更多华丽的演出效果,等待您进一步挖掘。


清除滤镜

在上一节,我们知道了设置滤镜的方法,同样的,我们也有清除滤镜的方法。

我们可以使用 scene.clearFilter() 来清除场景的所有滤镜。

例子与演示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 显示居酒屋
scene.show("izakaya", "resdiy/居酒屋-2560x1440.png", {
  renderer: {
    stretch: true,
    filters: [
      {
        // 附加一个撕裂效果滤镜
        name: "glitch",
        data: {
          slices: 50    // 撕裂块数量设为 50
        }
      }
    ]
  }
});

// 等待 3 秒
flow.wait(3000);

// 清空滤镜
scene.clearFilters("izakaya");