Create Your Frisk - Mod作成のヒント & よくあるエラーの対処法

CYF(Create Your Frisk)のちょこっとしたよくある(かもしれない)質問や疑問をここに置いておく。

いくつか個人的に気になったことを備忘録として残してもおく。

ちなみにCYF付属のドキュメントを読んで解決できることはここではほぼ書かない。

少しずつ追加していくかもしれない...


## ドキュメントの見方(どこにどの機能があるか)

ドキュメントに書いてあることはほぼ書かないが、どこにどの機能に関して書いてあるか、ドキュメントの見方を軽く紹介しよう。

+ **Basic setup:** Modのフォルダ階層や各スクリプトの概要が書かれている。
+ **Special Variables:** 予約変数。(Encounter Skeltonで)最初から書かれている変数や、MERCYメニュー関連、Monsterスクリプトの敵のステータスなどに関わるものが多い。Encounterで使える変数に便利なものもあるので一度チェックすることをおすすめする。
+ **Terminology:** 用語集。ドキュメントで意味の分からない単語があったら見るページ。さほど見ない。

API
+ **Text commands:** テキストの効果(色やエフェクト(揺らすなど)、スキップできないようにする、声を変えるなど)や、テキスト内で関数を呼び出す方法など。
+ **Game events:** (Encounter Skeltonで)最初から書かれている関数(ゲームイベント)について書いてある。いくつか追加できるものがあるので、一度見てみることをおすすめする。
<hr>

Functions & Objects
+ **Misc. Functions:** その他関数。どこでも使える関数や、ゲームの流れを変える関数、セーブロードの関数、敵の状態を変える関数などがある。
+ **The Player Object:** プレイヤーのステータスや、ソウルの位置に関わるクラス。強制攻撃の関数もある。
+ **The Script Object:** (量が無いので)さほど見ないが、他スクリプト(基本的にEncounter)の値を変える方法が書かれている。他スクリプトの関数を呼び出すこともできる。
+ **The Audio Object:** 音楽(BGM)やサウンド(SE)に関わるクラス。デフォルトで登録されている音を変える方法も書かれている。
+ **The NewAudio Object:** 上記の`The Audio Object`の強化版。
+ **The Input Object:** 入力関係のクラス。他の色ソウルや、メニューなどを作る場合はこれが必須。
+ **The Time Object:** 見ないな。フレームベースなコードを書いている私にとっては不要。
+ **The Inventory Object:** アイテム関係のクラス。アイテムを追加したり、プレイヤーのインベントリを変えたりできる。
+ **The Misc Object:** ウインドウや画面の制御(動かしたり揺らしたり)やファイル操作のクラス。`AlMightyGlobal`を使用したセーブロードが嫌いな方はこちらをどうぞ。
+ **The Discord Object:** Discordとの連携について。
+ **The Arena Object:** Waveスクリプト限定(AsteriskModではStatesスクリプト含む)のアリーナを動かしたりサイズ変更したりするクラス。
<hr>

+ **Projectile Management:** Waveスクリプトに限らないが、Waveスクリプトの作成で役に立つ、発射体(弾丸)オブジェクトに関して書かれている。
+ **The Pixel-Perfect Collision System:** 上記の発射体オブジェクトの`PPCollision`に関しての説明。
+ **Sprites & Animation:** スプライトの作成と制御方法。
+ **The Text Object:** テキストの作成と制御方法。
<hr>

Resources
+ **Dialog bubble names:** 吹き出し名一覧
+ **Key List:** `Input.GetKey()`の引数に入れられるもの(ほぼ)一覧。

Sharders? Overworld...? ~~知らないよ...そんなの~~



## エラー関連

Luaの文法が間違ってるとか、スペルミスなどが原因とか、どちらかと言うとCYFの問題ではないのだがよくあるエラーはここに置いておこう。

※ 以下で「`nil`**が代入されている**」と言う表現があるが、これは未定義やスペルミスで存在しないことや、文字通りnilを代入していることを表す。
(動的言語であるLuaでは未定義の変数(関数)は全て`nil`である。)

しっかり定義したといっても、定義する前に読み込もうとしたらアウトだぞ。

```Lua:しっかり定義したはず...?
local framecounter = 0
function Update()
    if framecounter == 1 then
        a = a .. "\n" 
    end
    framecounter = framecounter + 1
    a = "" -- 定義したもん！！！
end
```
(例とはいえど、こりゃすげぇクソコードだな)

### attempt to index a nil value

```Lua:エラー例
local a = b[1]
```

`nil`**が代入されている変数を、テーブルとして読み取ろうとした場合**に発生する。
上記の例では、変数`b`が存在しない(未定義or`nil`代入)のに(テーブルとして)要素を取り出そうとしている。

### attempt to perform arithmetic on a nil value

```Lua:エラー例
local a = 1
a = a + b
```

`nil`**が代入されている変数を、計算しようとした場合**に発生する。
上記の例では、変数`b`が定義されていないのに、変数`a`と計算しようとしている。


### attempt to call a nil value

```Lua:エラー例
Hoge()
```

`nil`**が代入されている変数を、呼び出そうした場合**に発生する
上記の例では、関数(変数)`Hoge`を定義していない。
定義していても、書いている場所が定義が下にあると呼び出されない可能性がある。(`local`使っていればなおさら)

### 'end' expected near '\<eof\>'

```Lua:エラー例
if true then
    if true then
end
```

endが足りない場合に発生する。

### cannot convert a 型名 to a clr type 型名

```Lua:エラー例
Player.Hurt("100")
```

型を間違えている。
上記の例では、`Player.Hurt()`の引数は数値型(number)であるのに文字列型(string)を代入している。

### unexpected symbol near '何か'

```Lua:エラー例
local fakePlayerData = {
    LV = 1,
    HP = 20
    MasHP = 20,
}
```

''内にある単語の近くに何か文法がおかしいものがあることを表す。
上記では、`unexpected symbol near 'MasHP'`というエラーメッセージであり、コンマが足りていない。
(正しくは`HP = 20,`としなければならない。)

### bad argument #n to 'メソッド名?' (型名1 expected, got 型名2)

```Lua:エラー例 (テキトー)
local t = {93874, 3657896, 348950}
local t2 = {}
for i = 1, 3 do
    t2[i] = math.pow(t, 2) -- 本来は t[i] のところが t　になっている
end
-- bad argument #1 to 'pow' (number expected, got table)
```

標準ライブラリにあるメソッドの呼び出しで、型名が違う場合に発生する。
この例では、`bad argument #1 to 'pow' (number expected, got table)`と表示され、
`number`が入るところに`table`が入っているぞ と言う意味。

### attempt to perform arithmetic on a string value

```Lua:エラー例
local t = "hoge"
t = t + t
```

**文字列を`+`で足し算しよう(繋ごう)とした場合**に発生する。
文字列の連結は`..`を使いましょう。

### cannot access field メンバ名 of userdata\<クラス名\>

```Lua:エラー例
Misc.SakeScreen()
```

クラス名 に メンバ名 という 変数/関数(正しくはメンバだがここでは分かりやすく)は存在しないことを表す。
上記の例では`Misc.ShakeScreen()`を呼び出すつもりがスペルミスをしている。
ドキュメントで確認しよう！


## Encounterスクリプト関連

### 敵の攻撃時間を自由にしたい(変えたい) / `wavetimer`をいちいち設定するのがめんどくさい

個人的には攻撃ごとに時間が異なる方が作りやすい。

```Lua:例
-- Encounterスクリプト内
wavetimer = math.huge
```

上記のように設定する場合は、**Waveスクリプトで手動で`EndWave()`を呼び出す必要があるので注意。**

### 行数が多くなりすぎてとても読みずらい...

無理矢理ファイルで分けることが可能。

```:ファイル階層(例)
📁 Lua
├─ Encounters
│   └─ encounter.lua
├─ EncounterScripts
│   ├─ MaskMethods.lua
│   └─ FakePlayer.lua
├─ Monsters (割愛)
└─ Waves (割愛)
```

上記の例で、`EncounterScripts`内にあるファイルはファイル最終行に`return`さえあれば問題ない。
通常のEncounterスクリプトとして書く。

そしてEncounters/encounter.luaで

```Lua:encounter.lua
-- ファイルのどこか(関数外が良い)
require "EncounterScripts/MaskMethods"
require "EncounterScripts/FakePlayer"
```

で読み込む。


## Monsterスクリプト関連

### 攻撃しても常にMISSにしたい

敵の防御力を強くすれば自動的にMISSになる。(計算して0ダメージの場合はMISSと表示されるようになる)

```Lua:例
-- Monsterスクリプト内
def = 1023 -- このくらいあれば基本的に問題はないと思う。
def = 2147483647 -- Moonsharpでは数値(number)はdouble型だが... [要検証]
def = math.huge -- [要検証] バグりそう
```

だがしかし、今ではこっちの方を推奨する。

```Lua:
-- Monsterスクリプト内
function BeforeDamageCalculation()
	SetDamage(0) -- 強制的にダメージを上書きする関数 0 を指定すべし
end
```

### 敵が避けるようにしたい

頑張れ。


### 原作のように一定時間経過したら攻撃(wave)に移行したい

結構簡単だった。

```Lua:
-- Monsterスクリプト内なら
currentdialogue = {"(内容)[w:60][next]"}
```

`w`テキストコマンドだが、`60`ならその４倍の`240フレーム`(=`4秒`)となるので、自分の好きな長さを設定できる。
ちなみにCYFはしゃべっている途中でも`Zキー`を押して先に進める(っぽい)。


## 発射体(Projectile)/弾丸(bullet)オブジェクト関連

### `x`と`absx`、`y`と`absy`の違い。

`x`や`y`はアリーナ(ダイアログボックス)の中心を`(0, 0)`とした座標。

`absx`や`absy`は画面(ウインドウ)左下端を`(0, 0)`とした座標。

### アリーナ(Arena)(ダイアログボックス)内にあるときだけ表示したい

アリーナ内にあるときだけ表示したい場合は、`empty`でスプライトオブジェクトを作成して、弾丸オブジェクトのスプライトを子オブジェクトにする。

`sprite.Mask()`の使い方は`スプライトオブジェクト関連`にある。

```Lua:例
-- Waveスクリプト内
local bullets = {}
local box = CreateSprite("empty", "BelowBullet")
box.MoveToAbs(ArenaUtil.centerabsx, ArenaUtil.centerabsy)
box.Scale(Arena.width, Arena.height)
box.Mask("box")

function CreateBullet() --これはただ単に弾丸を生成する関数を定義しただけ
    local bullet = CreateProjectile("bullet", Arena.width / 2 + 8, Player.y)
    bullet.sprite.SetParent(box)
    table.insert(bullets, bullet)
end
```

なお、**親スプライトより先に弾丸オブジェクトを削除(Remove)しないと完全に削除されないバグが存在する。**

上の例でいけば

```Lua:
function EndingWave()
    -- 先に弾丸オブジェクトを削除
    for i = 1, #bullets do
        if bullets[i].isactive then
            bullets[i].Remove()
        end
    end
    -- 最後に親スプライトを削除
    box.Remove()
end
```

としなければならない。

### 弾丸オブジェクトが生成されない

もし、`sprite.Mask()`を使っているなら、ちょうど上に解決策がある。先に弾丸オブジェクトを削除すべし。



## スプライト(Sprite)オブジェクト関連

### Unityを使って`Remove()`確認

もしUnityを持っているなら、CYFのコードをダウンロードして、スプライトの削除確認ができる。

大きなプロジェクト(オーバーワールドなど)を作る際に役に立つ。

`~Layer`というオブジェクトに子オブジェクトが存在するかしないかで
スプライトを全て`Remove()`しているか確認できる。


### スプライトを反転したい

`sprite.Scale()`で負の値を代入すると反転する模様。

後で試すが、CYF作成者直々の回答なので間違いは無いだろう...

### `sprite.Mask()`の使い方

マスクをかけられる側 = 親スプライト(`sprite.SetParent()`の()内に入れられる方)に、この`Mask()`を使う。

翻訳しても文が支離滅裂すぎて分からなかったので実際に聞いた。

```Lua:例
local bullets = {}
local box = CreateSprite("empty", "BelowBullet")
box.MoveToAbs(ArenaUtil.centerabsx, ArenaUtil.centerabsy)
box.Scale(Arena.width, Arena.height)
box.Mask("box")

function CreateBullet()
    local bullet = CreateProjectile("bullet", Arena.width / 2 + 8, Player.y)
    bullet.sprite.SetParent(box)
    table.insert(bullets, bullet)
end
```

### いくつも違うサイズの長方形(正方形)を用意するのがめんどくさい/合計ファイルサイズが大きくなってしまう

CYFにはデフォルトで`px`というテクスチャがあるので、それを使うとよい。
Modフォルダに入れる必要は無い。

私がよく使う長方形(正方形)を生成する関数があるので君に贈ろう。

```Lua
function NewSquare(x, y, width, height, color, layer)
	if color == nil then color = {1, 1, 1} end
	if layer == nil then layer = "BelowBullet" end
	local px = CreateSprite("px", layer)
	px.MoveToAbs(x, y)
	px.color = color
	px.Scale(width, height)
	return px
end
```

ちなみに透明なものが欲しい場合は`empty`を使用するとよい。



## テキスト(text)オブジェクト関連

### `text.Move()`/`text.SetVar()`/`text.GetVar()`がエラー

そりゃエラーですよ、だって**無いんだもん**。

`text.SetVar()`/`text.GetVar()`の方は将来のアップデートで追加される可能性はある模様。

ちなみに`text.Move()`が無い理由を聞こうとしたが答えてはくれなかった。

### 使いづらい

私がめちゃくちゃ使う、お勧めの関数を君に贈ろう。

```Lua:
function NewText(text, x, y, layer)
	if layer == nil then layer = "BelowBullet" end
	local text = CreateText(text, {x, y}, 10000, layer)
	text.HideBubble()
	text.progressmode = "none"
	return text
end
```



## フォント関連

### `uibattlesmall`フォントで上手く表示されない

実はこのフォント、小文字対応していない。
しかもそのことは、ドキュメントに書かれていない。

### 日本語のフォントが使いたい

Determination JP をCYF用に作っている者がいるので調べるといいだろう。

え？公式のフォント？

私が既に作っている。AsteriskMod v0.5.3で公開する予定があるのでそこまで待っていてほしい。

### フォントの作り方

フォントの作り方はドキュメントには載っていない。

次のサイトに載っている。

https://www.reddit.com/r/Unitale/comments/fbnr0f/tutorial_creating_and_editing_fonts_in_unitale/



## 公式Disocrdチャンネル関連

### 質問したらRTFDというスタンプが来た

意味は`Read The F***ing Documentation`、(ドキュメントを読め(、書いてあるだろ？))
という意味である。

翻訳かけて読めないことはないだろう...読もう！



## その他

### 各色が知りたい

```Lua:UTColors.lua
local BLACK  = {   0,   0,   0 }
local WHITE  = { 255, 255, 255 }
local RED    = { 255,   0,   0 }
local ORANGE = { 255, 166,   0 }
local YELLOW = { 255, 255,   0 }
local GREEN  = {   0, 192,   0 }
local CYAN   = {  66, 226, 255 }
local BLUE   = {   0,  60, 255 }
local PURPLE = { 213,  53, 217 }
```

水色(シアン色)のみアップデートで色が変化している

```Lua
local CYAN_ALT   = { 66, 226, 255 }
```

16進数の方はドキュメントの`Text commands`に載っている

### タイトルやメニューが作りたい

開発者側としてはAsteris(略)をお勧めするが、CYFでの常套手段としてあるので紹介しよう。

簡単だ。Waveスクリプトで、戦闘画面自体を隠して、その上にスプライトやテキストを生成する。
それだけ。

```Lua:例
-- Waveスクリプト
local mask = CreateSprite("px", "Top")
mask.color = {0, 0, 0}
mask.Scale(640, 480)

-- スプライトやテキストを生成

function Update()
    -- ...
end

function EndingWave()
    -- スプライトやテキストを削除
    mask.Remove()
end
```

### KRを実装したい

頑張れ。

...。

おっと、AsteriskModというのがあれば実は簡単に作れるんだぞ。そこにKRの例もある。

### 青色ソウルを実装したい

私が既に作っている。AsteriskMod v0.5.2.9で公開する予定があr(略)

実際、他の人が作っているものもある。