持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 7 天,点击查看活动详情
前言
这是一套 张风捷特烈 出品的 Flutter&Flame
系列教程,发布于掘金社区。如果你在其他平台看到本文,可以根据对于链接移步到掘金中查看。因为文章可能会更新、修正,一切以掘金文章版本为准。本系列文章一览:
- 【Flutter&Flame 游戏 - 壹】开启新世界的大门
- 【Flutter&Flame 游戏 - 贰】操纵杆与角色移动
- 【Flutter&Flame 游戏 - 叁】键盘事件与手势操作
- 【Flutter&Flame 游戏 - 肆】精灵图片加载方式
- 【Flutter&Flame 游戏 - 伍】Canvas 参上 | 角色的血条
- 【Flutter&Flame 游戏 - 陆】暴击 Dash | 文字构件的使用
- 【Flutter&Flame 游戏 - 柒】人随指动 | 动画点触与移动
- 【Flutter&Flame游戏 - 捌】装弹完毕 | 角色武器发射
- 【Flutter&Flame游戏 - 玖】探索构件 | Component 是什么
- 【Flutter&Flame游戏 - 拾】探索构件 | Component 生命周期回调
- 【Flutter&Flame游戏 - 拾壹】探索构件 | Component 使用细节
- 【Flutter&Flame 游戏 - 拾贰】探索构件 | 角色管理
- 【Flutter&Flame 游戏 - 拾叁】碰撞检测 | CollisionCallbacks
- 【Flutter&Flame 游戏 - 拾肆】碰撞检测 | 之前代码优化
- 【Flutter&Flame 游戏 - 拾伍】粒子系统 | ParticleSystemComponent
- 【Flutter&Flame 游戏 - 拾陆】粒子系统 | 粒子的种类
- 【Flutter&Flame 游戏 - 拾柒】构件特效 | 了解 Effect 体系
- 【Flutter&Flame 游戏 - 拾捌】构件特效 | ComponentEffect 一族
- 【Flutter&Flame 游戏 - 拾玖】构件特效 | 了解 EffectController 体系
- 【Flutter&Flame 游戏 - 贰拾】构件特效 | 其他 EffectControler
- 【Flutter&Flame 游戏 - 贰壹】视差组件 | ParallaxComponent
- 【Flutter&Flame 游戏 - 贰贰】菜单、字体和浮层
未完待续 ~
1. 文字的价值
无论是应用也好、游戏也好,文字
和 图片
是永恒的显示主体。人类文明是依赖于文字进行传承的,文字的最大价值在于传递信息。如下是阴阳师的战斗截图,其中角色的伤害、当前的成绩、鬼火数量、是否暴击、式神存活状态等,都依赖于文字来显示,向玩家反馈交互的信息。
这里先显示在血条上显示一下血量信息,如下所示:
2. 文字构件 - TextComponent
在 Flame
中,使用 TextComponent
构件显示文字。我们知道 Flutter
的绘制中通过 TextPainter
类可以绘制文字,其实 TextComponent
构建本质上就是对 TextPainter
的一层封装而已。提供了一个 TextPaint
类进行使用。
如下是 Liveable
中的处理,只需要创建一个 TextComponent
对象,然后使用 add
方法添加即可。另外任何构件都可以通过 add(RectangleHitbox)
来显示边框信息,方便查看所占区域。代码详见:【06/01】
final TextStyle _defaultTextStyle = const TextStyle(fontSize: 10, color: Colors.white);
late final TextComponent _text;
void initPaint({
required double lifePoint,
Color lifeColor = Colors.red,
Color outlineColor = Colors.white,
}) {
// 略...
// 添加生命值文字
_text = TextComponent(textRenderer: TextPaint(style: _defaultTextStyle));
_updateLifeText();
// 添加外框信息
_text.add(RectangleHitbox()..debugMode = true);
add(_text);
}
void _updateLifeText(){
_text.text = 'Hp ${_currentLife.toInt()}';
}
复制代码
如下可见,默认情况下 TextComponent
会与父区域的左上角对齐。另外 TextComponent
也是 PositionComponent
一族的构件,我们可以对其进行平移、缩放、旋转等操作。
比如下面的 tag1
处通过指定 _text
的 position
进行定位,左侧和血条对齐,并在血条上方:
代码语言:javascript复制// 添加生命值文字
_text = TextComponent(textRenderer: TextPaint(style: _defaultTextStyle));
_updateLifeText();
double y = -(offsetY _text.height 2);
double x = (size.x/2)*(1-widthRadio);
_text.position = Vector2(x, y); // tag1
add(_text);
复制代码
去掉信息框后如下所示,在点击时减少生命,它是通过 _updateLifeText
更新文字显示即可:
https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/32da7838d3c243a89cb18bb572cdb1ac~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?
3.显示伤害数据
在怪物受到攻击时,一般会显示造成伤害的数据,来让操作者有更直观的体验。现在期望在当怪兽受伤时,左侧显示伤害量,另外伤害量维持 1s
之后自动消失。如下所示:代码见 【06/02】
https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ff3f70e882784a1285d959b7e8942988~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?
伤害数据是在 Liveable
中维护的,虽然可以直接在 Liveable
中添加文字。但这样的话会使得 Liveable
的职能过于复杂,也不利于后续的拓展。我们可以单独定义一个 DamageText
构件,来维护伤害数值的显示逻辑。
如下代码所示,在 Liveable
中添加一个 addDamage
的方法,在 tag1
处添加 damageText
文字。然后使用 Future.delayed
方法,延迟 1s
中,调用 damageText.removeFromParent
方法即可移除。
class DamageText extends PositionComponent{
final TextStyle _damageTextStyle = const TextStyle(
fontSize: 14,
color: Colors.white,
fontFamily: 'Menlo',
shadows: [
Shadow(color: Colors.red, offset: Offset(1, 1), blurRadius: 1),
]);
Future<void> addDamage(int damage) async {
TextComponent damageText =
TextComponent(textRenderer: TextPaint(style: _damageTextStyle));
damageText.text = damage.toString();
damageText.position = Vector2(-30, 0);
add(damageText); // tag1
await Future.delayed(const Duration(seconds: 1));
damageText.removeFromParent();
}
}
复制代码
这样在 Liveable
中就不必处理具体添加伤害文字的逻辑,只需要通过 DamageText
来管理即可。比如在 loss
方法中,当角色受到伤害,通过 _damageText.addDamage
来添加伤害文字,这样处理就非常方便。想要对伤害文字进行显示进行修改或拓展,直接在 DamageText
处理即可,这就是职责的分离。
final DamageText _damageText = DamageText();
void initPaint({
required double lifePoint,
Color lifeColor = Colors.red,
Color outlineColor = Colors.white,
}) {
// 略...
add(_damageText);
}
void loss(double point) {
_damageText.addDamage(-point.toInt());
// 略...
}
复制代码
4. 暴击伤害
这里来模拟一下产生暴击的情况:如下图所示,伤害时有一定概率产生暴击,此时使用另一种文字样式。并给出 暴击
的字样提示:代码见 【06/03】
https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f1c8a3d02b8647f2aee5ea619892e16f~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?
实现也比较简单,在 addDamage
中,传入 isCrit
的入参,区分是否暴击。如果是暴击,使用 _addCritDamage
进行处理,添加黄色伤害和暴击字样即可。
---->[DamageText]----
void addDamage(int damage,{bool isCrit = false}) {
if(!isCrit){
_addWhiteDamage(damage);
}else{
_addCritDamage(damage);
}
}
Future<void> _addCritDamage(int damage) async {
TextComponent damageText =
TextComponent(textRenderer: TextPaint(style: _critDamageTextStyle));
damageText.text = damage.toString();
damageText.position = Vector2(-30, 0);
TextStyle style = _critDamageTextStyle.copyWith(fontSize: 10);
TextComponent infoText = TextComponent(textRenderer: TextPaint(style:style ));
infoText.text = '暴击';
infoText.position = Vector2(-30 damageText.width-infoText.width/2, -infoText.height/2);
add(infoText);
add(damageText);
await Future.delayed(const Duration(seconds: 1));
infoText.removeFromParent();
damageText.removeFromParent();
}
复制代码
暴击和爆伤,本应是角色的属性,这里暂时不搞这么复杂,在 Liveable
的 loss
方法中,用 75%
暴击和 165%
爆伤进行简单的测试,代码如下:
---->[Liveable]----
final Random _random = Random();
void loss(double point) {
double crit = 0.75;
double critDamage = 1.65;
bool isCrit = _random.nextDouble() < crit;
if (isCrit) {
point = point * critDamage;
}
_damageText.addDamage(-point.toInt(),isCrit: isCrit);
// 略...
}
复制代码
5.多次伤害
伤害是在 1s
后消失,当连续伤害在一秒之内,或者在一次伤害中附加多段伤害,就会产生遮挡。所以需要对多次伤害进行一下偏移处理,效果如下:代码见 【06/04】
https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8e6a3e266b294f92b9c7d517c81e693f~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?
这里利用了 Component
的一个特性,每个 Component
都有 children
属性,表示子构件集合。在 addDamage
中,只需要根据集合情况,获取最后一个元素,来确定添加文字的偏移即可:
void addDamage(int damage,{bool isCrit = false}) {
Vector2 offset = Vector2(-30, 0);
if(children.isNotEmpty){
final PositionComponent last;
if(children.last is PositionComponent){
last = children.last as PositionComponent;
offset = last.position Vector2(5, last.height);
}
}
if(!isCrit){
_addWhiteDamage(damage,offset);
}else{
_addCritDamage(damage,offset);
}
}
复制代码
文字本身比较简单,但与文字相关的数据维护和逻辑处理还是非常复杂的。本文通过显示角色的生命值
和伤害值
,简单说明了一下文字的使用方式。一般游戏中,都是使用图片作为文字,比如阴阳师的伤害数字。记得应该有图片形成字体的工具,比如 6000
的字符串,会对应字体中相关的图片。不过感觉 flame
框架太简单了,应该没有支持。
那本文就介绍到这里,明天见 ~
@张风捷特烈 2022.05.31 未允禁转
我的 掘金主页
: 张风捷特烈我的 B站主页
: 张风捷特烈我的 github 主页
: toly1994328