Continue to create, accelerate growth! This is the 7th day of my participation in the "Nuggets Daily New Plan · June Update Challenge", click to view the details of the event
foreword
This is a series of tutorials produced by Zhang Fengjietlie and published in the Nuggets community. Flutter&Flame
If you see this article on other platforms, you can move to the Nuggets to view it according to the link. Because the article may be updated and revised, the version of the Nuggets article shall prevail. List of articles in this series:
- 【Flutter&Flame Game-One】Open the door to a new world
- 【Flutter&Flame Game - Ⅱ】Joystick and Character Movement
- 【Flutter&Flame Game - 3】Keyboard Events and Gesture Operation
- 【Flutter&Flame game - 4】Sprite image loading method
- 【Flutter&Flame Game - Wu】Canvas Participation | Character's Health Bar
- 【Flutter&Flame Game - Lu】Damage Value | Use of Text Components (
本文
)
1. The value of words
Whether it is an application or a game, 文字
and 图片
is the eternal display subject. Human civilization relies on words for inheritance, and the greatest value of words lies in conveying information. The following is a screenshot of Onmyoji's battle, in which the character's damage, current performance, number of ghost fires, whether it is critical, and the survival status of Shikigami, etc., all rely on text to display and feedback interactive information to the player.
Here, first display the blood volume information on the blood bar, as shown below:
2. Text component - TextComponent
Flame
In , use the TextComponent
widget to display text. We know Flutter
that TextPainter
text can be drawn through the class in the drawing. In fact, the TextComponent
construction is essentially TextPainter
a layer of encapsulation. A TextPaint
class for use.
The following Liveable
is the processing in , just create an TextComponent
object and add it using the add
method . In addition, any component add(RectangleHitbox)
can , which is convenient for viewing the occupied area. Please refer to the code: 【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()}';
}
复制代码
As can be seen below, by default TextComponent
it aligned to the upper left corner of the parent area. In addition, TextComponent
is also a PositionComponent
family of components, and we can perform operations such as translation, scaling, and rotation on it.
For example, the following is tag1
positioned by the specified , the_text
left side is aligned with the health bar, and above the health bar:position
// 添加生命值文字
_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);
复制代码
After removing the information box, it is shown as follows, and the life is reduced when clicked. It can be displayed by _updateLifeText
updating text:
3. Display damage data
When the monster is attacked, the damage data will generally be displayed to allow the operator to have a more intuitive experience. Now it is expected that when a monster is injured, the damage amount will be displayed on the left, and the damage will disappear automatically 1s
after the . As shown below: see the code [06/02]
伤害数据是在 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】
实现也比较简单,在 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
The damage disappears after . When the continuous damage is within one second, or when multiple damages are added to one damage, occlusion will occur. Therefore, it is necessary to perform offset processing on multiple damages, and the effect is as follows: see the code [06/04]
Component
A feature of is exploited here , each Component
has a children
property that represents a collection of sub-components. addDamage
In , you only need to get the last element according to the collection to determine the offset of the added text:
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);
}
}
复制代码
The text itself is relatively simple, but the data maintenance and logical processing related to the text are still very complicated. This article briefly explains how the text is used by displaying the 生命值
sum of the roles . 伤害值
In general games, pictures are used as text, such as the damage numbers of Onmyoji. Remember that there should be tools for images to form fonts, such as 6000
the string of , which will correspond to the relevant images in the font. However, it feels that the flame
framework is too simple and should not be supported.
That's all for this article, see you tomorrow~
@张风捷特烈 2022.05.31 未允禁转
我的 公众号: 编程之王
我的 掘金主页
: Zhang Fengjieteli我的 B站主页
: Zhang Fengjieteli我的 github 主页
: toly1994328
\