跳转至

立绘 / Character

立绘是 AVG 的重要组成部分之一,可以在屏幕中显示一个角色图像,用于表示当前在场的角色以及说话者。

立绘通过 character 类进行管理,具备显示、隐藏、更新、动画、滤镜等方法。

扩展知识

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

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

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

关于范例加载

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

显示立绘

通过改显示立绘方法,我们可以在屏幕上显示一个立绘图像。

例子与演示

我们可以简单地显示一个图像,仅需指定一个名称,以及文件的路径即可。

1
2
// 显示一个立绘
character.show("char1", "girl-smile.png");
  • char1 是标识这个立绘的一个 唯一名称,用于对它进行一些后续操作,比如隐藏它、播放动画等等。
  • girl-smile.png 是这个立绘的文件名,它的默认相对路径位于 graphics/characters 下。

点击右下角的三角形查看实时演示,我们可以看到屏幕上出现了一名美少女,但是…… 位置似乎有点不太对?

通过前面的简单例子,我们知道了如何去显示一个立绘,那么现在我们来调整一下它的尺寸和显示的位置。

1
2
3
4
5
6
7
// 在指定位置以指定尺寸显示立绘
character.show("char1", "girl-smile.png", {
  renderer: {
    size: "80%",               // 宽高分别设置为原图的 80%
    position: `(center, 80%)`, // 指定 x 坐标居中,y 为窗口的 85% 位置
  },
});

我们在 character.show() 方法的第三个参数,第三个参数包含了 渲染器(renderer)动画(animation) 两个对象。

描述尺寸和坐标

这里我们在 renderer 对象中指定了 sizeposition,分别表示立绘的尺寸和坐标。这两个参数可以指定一个字符串。

  • size 我们可以直接赋值 80% ,那么默认情况下,它的宽高都是原图的 80%. 其次,你还可以单独指定宽高的不同比例,比如 (80%, 50%), 或者通过像素单位 (120px, 50%)
  • position 我们可以直接图像显示的坐标。坐标的描述方式和尺寸一致;

描述尺寸和坐标的方式非常灵活,如果用于描述尺寸,那么百分比将是相对于立绘原图的尺寸;如果是描述坐标,则是相对于游戏分辨率的大小。

如果是描述坐标,您甚至还可以用内置的变量:”(center, top)”,这样图像将在 x 轴居中,并在 y 轴贴近屏幕的上方。

我们还可以在渲染器中使用滤镜,AVGPlus 提供了数十种滤镜,可以通过图形计算的方式给立绘附加一些酷炫的效果,大量节省了美术资源。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
character.show("char1", "girl-smile.png", {
  renderer: {
    position: "(center, 70%)",
    filters: [
      {
        name: "blur",
        data: {
          strength: 10,
        },
      },
    ],
  },
});

滤镜在渲染器里面的字段是 filters, 它是一个数组,意味着我们可以对一个立绘附加多个不同的滤镜。

在本例子中我们给它指定了一个 高斯模糊

  • name 滤镜的名称
  • data.strength 滤镜的具体参数,strength 表示高斯模糊的强度

data 对象中,可以具体指定对应的滤镜的参数。

如需了解滤镜的更多知识,请参考 图形滤镜

立绘在显示时,默认效果是淡入。若干要有更加丰富的动画表现,就要用到 关键帧动画引擎

立绘它通过 animation 对象,指定一个初始帧和时间轴,并且在时间轴内定义多个 renderer,每一个 renderer 是一个关键帧,在动画引擎执行时,会对关键帧之间的数值进行插帧。

实际应用很简单。我们通过脚本来实现一个立绘进场的效果:

  • 立绘从全透明,从屏幕左边飞入并淡入
  • 立绘飞入后,放大少许

以下是范例代码,请留意注释:

 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
// 同样通过 show 方法来显示一个立绘
character.show("char1", "girl-smile.png", {
  renderer: {
    // 在渲染器中指定 position 调整到一个合适的位置
    position: "(0, bottom)",
  },
  animation: {
    // 我们通过 initialFrame 来描述动画开始时的初始状态
    // 当然,在上面的 renderer 中写也是没问题的
    // initialFrame 实际上就是一个 renderer 对象
    // 它会在时间轴开始执行时初始化立绘的参数
    initialFrame: {
      alpha: 0,         // 设置初始透明度为0
      scaleX: 1,        // x 轴缩放
      scaleY: 1,        // y 轴缩放
    },

    // 接下来,我们来定义一个时间轴
    // 时间轴是一个 renderer 数组,但是它比 renderer 多了一个 duration 字段
    timeline: [
      {
        duration: 1000,         // 这一状态帧的动画时长,单位是毫秒
        alpha: 1,               // 透明度设为 1
        x: 500,                 // 移动到 x 坐标 500
      },
      {
        duration: 1000,         // 这一状态帧的动画时长
        scaleX: 1.1,            // x 轴缩放
        scaleY: 1.1,            // y 轴缩放
        ease: "easeOutElastic"  // 缓动函数
      }
    ]
  }
});

点击右下角的三角形,我们可以看到一个立绘从左往右飞入,并在最后放大的时候略微抖动了一下。通过上面的脚本,我们应该也能看出个大概了,毕竟我注释都写了这么多(已经是我一年的注释量了)。

关键帧动画的执行原理

其实 timeline 中的每个关键帧,都可以看作是一个 renderer,它描述了这个关键帧的图形状态。动画引擎会根据 duration 字段来插帧。

针对上面的代码,动画引擎的执行流程是这样的:

  • 初始状态帧 (initialFrame):立绘在开始渲染的时候,会初始化为 initialFrame 中的状态
  • 关键帧1:在 1 秒内,把立绘的透明度从 0 变成 1, x 坐标从 0 变成 500.
  • 关键帧2:在 1 秒内,把立绘 x, y 轴的缩放倍率都变成 1.1

缓动函数

值得注意的是,我们看到关键帧2里面有一个 ease 参数,这是 缓动函数

在动画执行的时候,如果不使用缓动曲线的情况下,动画往往是匀速运动的,自始至终都是均匀的速度,不会进行加速。

但如果使用了缓动函数,就会生成一个运动曲线。例如: - 汽车启动的时候,都会有一个逐渐加速的时间,从慢到快,在数秒内达到峰值速度。然后在刹车的时候再变慢。 - 比如一个自由落体的球,会在地板上弹个几下,才会停止;

缓动函数的意义就是生成一个运动曲线,让动画变得更加生动。在 AVGPlus 中提供了数十种缓动函数,分别有不同的表现。

如需了解关于动画引擎和缓动函数,请参考 关键帧动画引擎

虽然只是一个简单的立绘显示,但上面也涉及了比较多的概念。

包括渲染器、滤镜、动画的使用,此处仅做抛砖引玉,如果您想挖掘更多的功能,需要另外进行学习。

当然,如果对图形了解不多,这对您来说可能会有一点复杂。但是没有关系,这些都是 AVGPlus 的必修课,只学会使用它,就是一个成熟的开发者了。

以下是关于图形相关的章节传送门:


隐藏立绘

可以根据指定的 name, 隐藏一个立绘。

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

例子与演示

我们可以通过 character.hide() 方法来隐藏立绘。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 我们先显示一个 name 为 'space' 的立绘
character.show("space", "space-normal.png", {
  renderer: {
    position: "(center, 90%)",
  }
});

text.show("「没什么事我先走了。」", {
  name: "空格"
});

text.hide();

// 隐藏 name 为 'space' 的立绘
character.hide("space");

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

如果我们需要在退场的时候有更加丰富的动画效果,那就可以给它指定一个动画。

相信您在上面显示立绘的时候已经简单了解过 关键帧动画引擎,在这里,我们可以通过同样的方式来描述一段动画。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 我们先显示一个 name 为 'latyas' 的立绘
character.show("latyas", "latyas-laugh.png", {
  renderer: {
    position: "(center, 90%)",
  }
});

text.show("「我先走啦!」", {
    name: "拉铁丝"
});

text.hide();

// 隐藏 name 为 'latyas' 的立绘
character.hide("latyas", {
  timeline: [
    {
      duration: 500,    // 播放时长为 0.5s
      x: "+=500",       // 在当前 x 坐标的基础上,偏移 +500
      alpha: 0,         // 透明
    }
  ]
});

点击右下角的三角形,可以看到立绘往右边飞出,并且逐渐消失。

……等等,好像这次的 x 轴的赋值有点不一样?

我们在显示立绘的时候,都是直接指定一个具体的数值,在这里我们使用了 +=500 这样的值。

实际上,这个是一种偏移值的描述方式,通过指定 +=500,动画引擎在执行关键帧的时候,会以立绘当前的 x 坐标为基准再加上 500.

聪明的小朋友可能就反应过来了,这其实是个加减法表达式,同理,你也可以把它改为 -=500,效果就会变成从左边飞出了。


更新立绘

更新立绘可以替换一个已经存在的立绘的图片,通常会用于替换不同表情的立绘。

例子与演示
 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
// 我们先显示一个 name 为 'space' 的立绘
character.show("space", "space-normal.png", {
  renderer: {
    position: "(center, 90%)",
  }
});

text.show(["「疫情期间,大家都要戴好口罩。」", "「我……我要变身了。」"], {
    name: "空格"
});

text.hide();

// 等待 500 毫秒
flow.wait(500);

// 加点特效
character.animate_async("space", {
  timeline: [
    {
      duration: 300,
      filters: [
        {
          name: "adjustment",
          data: {
            gamma: 10
          }
        }
      ]
    },
    {
      duration: 500,
      filters: [
        {
          name: "adjustment",
          data: {
            gamma: 1
          }
        }
      ]
    }
  ]
})

character.update("space", "space-hidden.png");

上述例子可以看到,角色在闪烁之后,更换了一张带上了口罩的立绘。

(我们无视现在为什么出现了好像很复杂的闪烁动画…… 这个在后面会说到。)


立绘动画

上面我们在显示立绘和隐藏立绘里面,都可以加入关键帧动画。其实在上面更新立绘的范例脚本里已经做了一些「剧透」。

对于一些已经显示了立绘的场合,我们希望主动再对它执行一次动画操作,就可以用 character.animate() 方法。

这个方法的用法非常简单,和上面涉及到动画的 API 的用法基本一致:

例子与演示

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

 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
// 显示一个立绘
character.show("char1", "girl-smile.png", {
  renderer: {
    position: "(center, 70%)",
  }
});

character.animate("char1", {
  // 指定重复播放次数,-1 为无限循环
  repeat: -1,

  // 时间轴的总时长,该时间会平摊到每个关键帧上
  // 如下面有 4 个关键帧,则每个关键帧的 duration 为 1200/4 = 300ms
  totalDuration: 1200,  
  timeline: [
    {
      x: "+=300"
    },
    {
      x: "-=300"
    },
    {
      y: "+=200"
    },
    {
      y: "-=200",
      ease: "easeOutBounce"
    }
  ]
})

设置立绘滤镜

使用 character.filter() 方法可以给场上的某个立绘附加一个或多个滤镜。就结果上而言,和显示立绘时在 renderer 里设置滤镜是完全一样的。

例子与演示

 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
// 显示一个立绘
character.show("char1", "girl-smile.png", {
  renderer: {
    position: "(center, 70%)",
  }
});

let currentSize = 0;

// 我们在一个死循环里循环设置滤镜
// 此处仅为了演示,实际场合一般不会用这种做法
while (true) {

  // 设置滤镜
  // 这里我们尝试用「字符化」滤镜,让立绘图像看起来像字符组成
  character.filter("char1", [
    {
      name: "ascii",
      data: {
        size: currentSize
      }
    }
  ])

  // 每次循环累加 0.1
  currentSize += 0.1;

  // 如果大于或等于 10,则重置为 0
  if (currentSize >= 10) {
    currentSize = 0;
  }

  // 等待 10 毫秒
  flow.wait(10);
}

清除滤镜

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

我们可以使用 character.clearFilter() 来清除立绘的所有滤镜。

例子与演示

 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
// 显示一个立绘
character.show("char1", "girl-smile.png", {
  renderer: {
    position: "(center, 70%)",
  }
});

// 这里我们给立绘附加两种滤镜,分别是「高斯模糊」和「发光效果」
character.filter("char1", [
  {
    name: "blur",
    data: {
      strength: 5,
    }
  },
  {
    name: "glow",
    data: {
      outerStrength: 10,
    }
  },
])

text.show("继续对话,清空身上的所有滤镜");

text.hide();

// 清空名为 char1 的立绘身上的所有滤镜
character.clearFilters("char1");