【Flutter&Flame Game - Lu】Critical Dash | Use of Text Components

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&FlameIf 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:


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

FlameIn , use the TextComponentwidget to display text. We know Flutterthat TextPaintertext can be drawn through the class in the drawing. In fact, the TextComponentconstruction is essentially TextPaintera layer of encapsulation. A TextPaintclass for use.


The following Liveableis the processing in , just create an TextComponentobject and add it using the addmethod . 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 TextComponentit aligned to the upper left corner of the parent area. In addition, TextComponentis also a PositionComponentfamily of components, and we can perform operations such as translation, scaling, and rotation on it.


For example, the following is tag1positioned 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 _updateLifeTextupdating 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 1safter 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();
}
复制代码

暴击和爆伤,本应是角色的属性,这里暂时不搞这么复杂,在 Liveableloss 方法中,用 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.多次伤害

1sThe 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]


ComponentA feature of is exploited here , each Componenthas a childrenproperty that represents a collection of sub-components. addDamageIn , 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 6000the string of , which will correspond to the relevant images in the font. However, it feels that the flameframework is too simple and should not be supported.

That's all for this article, see you tomorrow~


\

Guess you like

Origin juejin.im/post/7103690366536122399