【Godot】テキストスクロールとRichTextLabelのBB Codeについて

# 概要
このページではテキストスクロールを実装する方法について書きます。

→※追記 2021/2/18: ここで紹介している方法を使わなくても **`RichTextLabel` を使うことで簡単にテキストスクロールは実装できます**。

![shot.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/ee6abcb6-850c-05e4-dad3-3822ffd2b8ed.gif)

# テキストスクロールの実装
## 各種ノードの追加

ノードは以下のような構成となります。

```
ScrollContainer
 +-- VBoxContainer
      +-- Label
```

まずはシーンのルートノードに `ScrollContainer` を追加します。
![Godot_Engine_-_TestScroll.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/fc7201fe-7a9b-ad94-71b6-ccc9d362e8e6.png)

次に、ScrollContainerの子ノードとして `VBoxContainer` を追加します。
![Godot_Engine_-_TestScroll_-_ScrollContainer_tscn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/585c96bb-59c2-8c38-8550-3fa6b7a5a4aa.png)

VBoxContainerの子に `Label` を追加します。
![Godot_Engine_-_TestScroll____.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/fbf4d655-a694-8da3-6110-9ec8646dab2d.png)

ノード構成は以下のようになりました。
![Godot_Engine_-_TestScroll_-_ScrollContainer_tscn____.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/69f090ca-b1e4-03e6-02aa-78a5be1bd592.png)

## ScrollContainerのサイズを調整
テキストの実際の表示エリアとなる `ScrollContainer` のサイズを設定します。
![Godot_Engine_-_TestScroll_-_ScrollContainer_tscn____.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/fa0ae64e-e461-82f7-c7b6-58537a05fbf3.png)

* `Rect > Position > x`: 60
* `Rect > Position > y`: 50
* `Rect > Size > x`: 160
* `Rect > Size > y`: 80

としましたが、おおよそのサイズで良いと思います。
![Godot_Engine_-_TestScroll_-_ScrollContainer_tscn____.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/4da421a9-af52-60d0-f0a5-4b7e8cfb0128.png)

## Labelにテキストを設定
スクロール確認用として以下のテキストを `Label` ノードの `Text` に設定します。

```
One
Two
Three
Four
Five
Six
Seven
Eight
Nine
Ten
```

するとスクロールバーが表示されます。
![Godot_Engine_-_TestScroll_-_ScrollContainer_tscn____.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/f37b8422-b3d9-8096-832d-e46d2d9140ab.png)

あとは実行してスクロールの動作を確認します。
![shot.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/ee6abcb6-850c-05e4-dad3-3822ffd2b8ed.gif)
スクロールバーをマウスカーソルで動かしたり、テキストの上でマウスホイールを動かしたり、トラックパッドでスクロールさせることができます。

## スマートフォン向け対応をする
動作は確認していませんが、スマートフォンだとスワイプでテキストが動作しないことがあるようです。

* [GodotのScrollContainerを使ってスマホでスワイプ出来ない場合](https://crieit.net/posts/Godot-ScrollContainer)

Containerの下に Containerが存在すると、スクロール処理をそちらに奪われてしまうとのことです。
ここでは、`VBoxContainer` が悪さをする可能性があるので、`VBoxContainer`  の `Control > Mouse > Filter` を `Ignore` にしておくと、スマートフォンでも正常に動作しそうです。

![Godot_Engine_-_TestScroll_-_ScrollContainer_tscn____.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/50cfa64f-0619-7f66-31c9-f769f23a69b9.png)

## スクリプトからログテキストを追加する
最後にスクリプトからテキストを追加する処理のサンプルです。

`ScrollContainer` にスクリプトをアタッチして以下のように記述します。

```gd:ScrollContainer.gd
extends ScrollContainer

const MAX_LOG = 10 # 10件を最大とする

# Spaceキーを押した回数
var cnt = 0

# ログ
var logs = []

# ラベルノードを取得
onready var label = $VBoxContainer/Label

func _process(delta):
	if Input.is_action_just_pressed("ui_accept"):
		# Spaceキーを押したらログを追加
		logs.append(str(cnt))
		cnt += 1
		if logs.size() > MAX_LOG:
			# 最大数を超えていたら先頭のログを削除
			logs.pop_front()
		
		# テキスト初期化
		label.text = ""
		var is_first = true
		for s in logs:
			if is_first:
				is_first = false
			else:
				# 最初の行以外は改行を入れる
				label.text += "\n"
			label.text += s
```

Spaceキーを押した回数をログに追加する処理となります。
ログの最大件数は 10件として、それを超えた場合は一番古いログを消すようにしています。

![shot.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/cecbf343-ec0e-6bb3-f02a-a4726753fe9e.gif)

# 追記
この記事を書いた後で気がついたのですが、`RichTextLabel` を使用すると、`ScrollContainer` を使わなくてもスクロールできることがわかりました
／(^o^)＼ﾅﾝﾃｺｯﾀｲ

![Godot_Engine_-_TestScroll_-_RichTextLabel_tscn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/8ca0b15f-71c0-a996-4f85-c145fdd16f4b.png)
![shot.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/d6ff4766-c1c3-5a67-3e9f-657b0e3d8dd5.gif)

## RichTextLabelの「BB Code」について
せっかくなので、`RichTextLabel` について調べました。
RichTextLabel では 「BB Code」という特殊な記法でテキストの装飾とアニメーションをすることができます。

### BB Codeを有効にする
BB Codeを有効にするには、`Bb Code > Enabled` にチェックを入れます。
![Godot_Engine_-_TestInherit____.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/22e9eb09-dbed-c4aa-2004-ee22b39afbe8.png)
BB Codeを有効にすると通常のテキストは使用できず、BB Codeのテキストのみ使用できます。

### BB Codeの種類
公式ドキュメントに詳しく書いてあるので、説明は不要かもしれませんが、個人的な勉強のためにまとめておきます。

* [Godot Docs » GUI » RichTextLabelのBBCode](https://docs.godotengine.org/ja/stable/tutorials/gui/bbcode_in_richtextlabel.html)

####色を変える `[Color]`
色を変えるには、テキストを`[color]`で囲みます

```:BB_Code
This is [color=yellow]banana[/color].
```

このように記述すると、以下のように色が変わります
![Banners_and_Alerts_と_Godot_Engine_-_TestInherit____.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/7209ec5b-8c29-2d6a-748a-eee7ff68b309.png)
`[color]`で囲んだ部分だけ色が変化します。

色の数は以下が用意されています。

* aqua: 水色
* black: 黒色
* blue: 青色
* fuchsia: 赤紫色
* gray: 灰色
* green： 緑色
* lime: 明るい緑色
* maroon: あずき色
* navy: 濃紺
* purple: 紫色
* red: 赤色
* silver: 銀色
* teal: 青緑色
* white: 白色
* yellow: 黄色

上記に存在しない色は16進数で指定します。

例えばオレンジ色だと……

```:BB_Code
This is [color=#FFA500]orange[/color].
```

このようになります。
![Godot_Engine_-_TestInherit____.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/05ca4bba-98ba-20e7-fdf2-d1fc6e9aac66.png)

##### 画像を表示する `[img]`
アイテムアイコン画像を表示するなど、テキスト内に画像を表示したい場合は `[img=幅]画像のパス[/img]` を使用します。

```:BB_Code
He is [img=16]res://icon.png[/img].
```

このように Godotくんをテキスト内に表示することも可能です。
![Godot_Engine_-_TestInherit____.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/4e179c20-96c8-754c-d58d-8e1d4decbab1.png)
ただ、サイズがテキストの高さよりも大きい場合、テキストの高さがずれてしまうので注意です。

#### アニメーション
##### `[Wave]` 波のアニメーション

```:BB_Code
[wave amp=50 freq=2]Wave Effect[/wave]
```

ampの値を大きくするほど上下移動が大きくなり、freqの値を大きくするほど上下の移動速度が上昇します。

![shot.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/9a59fe66-84d0-23fd-d41d-d19b538f77e8.gif)

上下移動で描画領域外に出てしまうとテキストが消えてしまうので、上下に1行ほど余裕をもたせたほうが良いかもしれません。

##### `[Tornado]` 竜巻のアニメーション

```:BB_Code
[tornado radius=5 freq=2]Tornade Effect[/tornado]
```

radiusは回転する半径の大きさ、freqは回転速度となります。

![shot.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/2b64415b-87f0-1b96-7398-7e2a78f26f3e.gif)

このアニメーションは上下左右に移動するので、上下左右の余白を大きくする必要がありそうです。

##### `[Shake]` 揺れ

```BB_Code
[shake rate=20 level=10]Shake Effect[/shake]
```

テキストがガクブルします。 rateが揺れの速度で、levelが揺れ幅となります。

![shot.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/d6cfd6c5-0d51-2093-fe89-2189da93f7cb.gif)

##### `[Fade]` フェード

```BB_Code
[fade start=4 length=14]Fade away effect[/fade]
```

![Godot_Engine_-_TestInherit_-_Control_tscn____.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/71361d9b-d070-a1b3-12f0-cbe0af25a4cc.png)

フェードと色変えは組み合わせて表示できない(合成されない)ようです。

```:BB_Code
[fade start=4 length=14]Fade away [color=red]effect[/color][/fade]
```

![Godot_Engine_-_TestInherit_-_Control_tscn____.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/bccbb48c-2dce-56d2-cdd7-a71e140a86d4.png)

合成するには、カスタムエフェクトで新しくタグを作る必要があるのかもしれません……。

以下の動画が詳しそう……
https://www.youtube.com/watch?v=o-cLQ7J1mc8

### BB Codeのカスタムエフェクト
上記動画をもとに、カスタムエフェクトを試してみました。
BB Codeのカスタムエフェクトは、新規スクリプトを作成して
![Godot_Engine_-_TestInherit_-_Control_tscn____.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/b9a18b54-795f-717e-6261-2d3a2d387081.png)
↓↓↓
![Godot_Engine_-_TestInherit_-_Control_tscn____.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/850a84fe-3541-6aef-1cfa-fe4024bf7cf6.png)

```gd:CustomGhostEffect.gd
tool
extends RichTextEffect
class_name RichTextGhost

# Syntax: [ghost freq=5.0 span=10.0][/ghost]

# Define the tag name.
var bbcode = "ghost"

func _process_custom_fx(char_fx):
    # Get parameters, or use the provided default value if missing.
    var speed = char_fx.env.get("freq", 5.0)
    var span = char_fx.env.get("span", 10.0)

    var alpha = sin(char_fx.elapsed_time * speed + (char_fx.absolute_index / span)) * 0.5 + 0.5
    char_fx.color.a = alpha
    return true
```

上記コードを `CustomGhostEffect.gd` として「名前をつけて保存」
![Godot_Engine_-_TestInherit_-_Control_tscn____.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/5e83d127-9ed4-35cf-1a0e-1db9ae271795.png)

すると、`RichTextLabel` から **`RichTextGhost`** (スクリプト内で指定した `class_name`)を選べるようになり `[ghost]` タグが使えるようになります。
![Godot_Engine_-_TestInherit_-_Control_tscn____.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/6bfd2a2f-dc69-45a2-ee4f-c42ae6db9854.png)

以下のBB Codeをテストしてみます。

```:BB_Code
[ghost freq=5 span=5]ghost effect [/ghost]
```

`[ghost]` のカスタムエフェクトが動くようになりました！
![shot.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43772/b29e1d2b-bab2-52c0-4f68-fef8a3f0fb5d.gif)


# 参考

* [GodotのScrollContainerを使ってスマホでスワイプ出来ない場合](https://crieit.net/posts/Godot-ScrollContainer)
* [how works VScrollbar? - Godot Engine - Q&A](https://godotengine.org/qa/4956/how-works-vscrollbar)
* [Godot Docs » GUI » RichTextLabelのBBCode](https://docs.godotengine.org/ja/stable/tutorials/gui/bbcode_in_richtextlabel.html)
* [Creating Custom Rich Text Effects in Godot 3.2](https://www.youtube.com/watch?v=o-cLQ7J1mc8)