LoginSignup
0
0

More than 1 year has passed since last update.

Create Your FriskでUndertaleみたいなゲームを作る(エンカウンター編)

Last updated at Posted at 2022-04-07

の続編です。環境が上記事の状態であることを前提として進めさせていただきます。予めご了承ください。

このシリーズの目標

  • サンプルMod "Encounter Skeleton" の中身を理解する
  • "Encounter Skeleton" を日本語化する

中身の確認

とりあえず起動してみましょう。スペースキーを押してMod選択画面に入り、Encounter Skeletonというmodを選択します。
Encounter Skeleton.png
画像のようになっていれば成功です。

中身の解説

フォルダ構成

mod内のフォルダ構成は次のようになっています。

Encounter Skeleton
├── Audio
├── Lua
│  ├── Encounters
│  ├── Monsters
│  └── Waves
└── Sprites

このうちAudioフォルダとSpritesフォルダは、それぞれBGM(oggかwav)や画像(png)を入れておくフォルダなのであまり考えないでもいいです。普通はもう一つ、効果音のための(BGMとは別)フォルダとしてSoundsフォルダが置かれています。重要なのはLua以下のフォルダです。それぞれの役割は次の通りです。

  • Encounters ... バトル画面全体の処理をするファイルの置き場所
  • Monsters ... 対戦するモンスター一体一体の処理をするファイルの置き場所
  • Waves ... 各ターンの敵の攻撃の処理をするファイルの置き場所

これらが相互に働きあってmodが作られています。ちなみにWaveというのはCYFにおける「敵の攻撃」のことです。他では使われない特別な意味なので注意してください。
では、それぞれの処理を見ていきましょう。

Encounterの処理

エンカウンターのコードは以下のようになっています。

encounter.lua
-- A basic encounter script skeleton you can copy and modify for your own creations.

-- music = "shine_on_you_crazy_diamond" --Either OGG or WAV. Extension is added automatically. Uncomment for custom music.
encountertext = "Poseur strikes a pose!" --Modify as necessary. It will only be read out in the action select screen.
nextwaves = {"bullettest_chaserorb"}
wavetimer = 4.0
arenasize = {155, 130}

enemies = {
"poseur"
}

enemypositions = {
{0, 0}
}

-- A custom list with attacks to choose from. Actual selection happens in EnemyDialogueEnding(). Put here in case you want to use it.
possible_attacks = {"bullettest_bouncy", "bullettest_chaserorb", "bullettest_touhou"}

function EncounterStarting()
    -- If you want to change the game state immediately, this is the place.
end

function EnemyDialogueStarting()
    -- Good location for setting monster dialogue depending on how the battle is going.
end

function EnemyDialogueEnding()
    -- Good location to fill the 'nextwaves' table with the attacks you want to have simultaneously.
    nextwaves = { possible_attacks[math.random(#possible_attacks)] }
end

function DefenseEnding() --This built-in function fires after the defense round ends.
    encountertext = RandomEncounterText() --This built-in function gets a random encounter text from a random enemy.
end

function HandleSpare()
    State("ENEMYDIALOGUE")
end

function HandleItem(ItemID)
    BattleDialog({"Selected item " .. ItemID .. "."})
end

英語のコメントを参考にしつつ、CYFで使われる特別な変数・関数について一つずつ説明していきます。説明ない奴はただの変数・関数として考えてください。ここのコードにないがこのスクリプト内に記述して使うことがほとんどな情報も追記します。

初心者向け:基本文法について

プログラミングにおいては、変数というものが存在します。変数というのは、一言でいえば「値を保存しておくための箱」のことです。例えば、以下のコードではaという変数に10という数値を代入しています。

a = 10 -- 変数aに10を代入

ここで注意したいのは、=の使い方です。プログラミングにおける=は数学のそれと異なり、「右辺の値を左辺の変数に代入する」働きをもちます。逆に、数学の時のように「右辺と左辺が等しいことを主張する」場合には==を使い、

print(a == 10) -- aが10と等しければtrue、そうでなければfalseを出力する

という風に表現します。

複数の値をまとめて保存しておきたい場合は、通常の変数ではなくテーブルを使います。

a = {10,20,30} -- テーブルaに10,20,30という三つの値を入れる

print(a[1]) -- aの1番目の要素である10が表示される
print(a[2]) -- aの2番目の要素である20が表示される
print(a[3]) -- aの3番目の要素である10が表示される

print(#a) -- aの要素の数(3)が出力される

条件によって処理を実行する・しないを分けたいときはif文を使います。

if (条件1then
  -- 条件1が成り立つときの処理
end

if (条件1then
  -- 条件1が成り立つときの処理
else 
  -- 条件1が成り立たないときの処理
end

if (条件1then
  -- 条件1が成り立つときの処理
elseif (条件2then
  -- 条件2が成り立つときの処理
else 
  -- 条件1も条件2も成り立たないときの処理
end

繰り返したいときはいろいろありますが、大抵はfor文でどうにかなります。
数学の$\sum$を思い浮かべるとイメージしやすいかも。

for i=1,10 do
 -- iが1から10までひとつずつ増える間に行われる処理
 -- 今回は10回実行される(回数は10の部分を書き換えれば変わる)
end

いろいろなところで使う処理をまとめたものを関数と言います。これも数学の関数とは違い、常に同じことをすることもあれば、入力によって処理を変えたり、出力したりしなかったりと様々です。

function Sans()
  print("SHOULD BE BURNING IN HELL.")
end

Sans() -- "SHOULD BE BURNING IN HELL."と表示される

function Toriel()
  return 'Pie' -- 'Pie'という値を返す
end

item = Toriel()
print(item) -- Toriel()から受け取った'Pie'が表示される

特別な変数

CYF内で使われる、特別な役割を持った変数です。

music (文字列型変数)

冒頭でコメントアウトされているこの変数ですが、自前のBGMを流したい場合に重要です。この変数にAudioフォルダから流したい音楽のファイルまでのパスを拡張子をつけずに代入すると、BGMがその音楽になります。

余談ですが、この解説ではCYFにおける特別な変数や関数の解説の時は名前の後にその変数の型や引数・返り値を記述します。型って何?って人は此方の記事がわかりやすいです。
ちなみにLuaはPythonやJavaScriptなどと同じく型を明示的に指定する必要がありません。しかしプログラミングするときに型を意識する・しないの差は大きいです(初心者がよく陥るバグにつながる)。今後の学習につなげるためにも、型という意識は頭の片隅に留めといてください。

encountertext (文字列型変数)

先程の画像の中央部分に全く同じテキストが出ていると思います。
この変数は、毎ターン開始時にテキストボックスに書かれる言葉です。「こうげきを つづけろ。」とかが表示される部分ですね。
 試しに、ソースコードを次のように書き換えてみましょう。

encounter.lua
encountertext = "Hello World!"

helloworld.png

みんな大好きHello Worldが表示されました。うざいイヌのエラー画面に出くわしたって人は、文字をダブルクォーテーションかシングルクォーテーションでくくってあるか確認してみてください。

nextwaves (テーブル型(文字列型の集まり)変数)

次の攻撃のリストです。テーブル内の攻撃をすべて実行します。一つの攻撃だけ実行したい場合は、テーブル内にひとつだけ記述しましょう。

wavetimer (浮動小数点型変数)

攻撃が実行される長さです。この秒数だけnextwavesで指定した攻撃が行われます。

arenasize (テーブル型(数値型の集まり)変数)

攻撃中の枠の広さをピクセル単位で指定します。
arenasize[1]が横方向、arenasize[2]が縦方向の大きさです。

enemies (テーブル型(文字列型の集まり)変数)

戦うモンスターの一覧です。Monstersフォルダからluaファイルまでのパスを拡張子無しで指定します。

enemypositions (テーブル型(テーブル型(数値型の集まり)の集まり)変数)

モンスターが順番に画面上のどこに立つかをx,y座標で指定します。enemiesの一つ目の位置をenemypositionsの一つ目が指定する、といった形のため、enemiesと要素数が合わないとエラーになります。

ゲーム内イベント発生時の処理

イベントが発生した時の処理を記述します。これらは必ず定義しなければいけないわけではありませんが、機能を充実させたいなら知っておくべきです。function (関数名)() ~ (処理) ~ endの形で宣言し、その内部に処理を記述します。。

EncounterStarting()

エンカウンターが起動した際に行われる処理です。サンズみたいに戦闘開始時に話したり不意打ちしたりしたいときは後述のState()をここに記述して実装します。

EnemyDialogueStarting()

敵が話し始める前に実行される関数です。特定の行動をとっていた場合に台詞を変えるなどの処理はここで実装します。

EnemyDialogueEnding()

敵が話し終わったタイミング(=攻撃が開始する直前)で実行される関数です。攻撃時間の変更やアリーナ(CYFにおいて、攻撃中にタマシイが動き回れる枠のことをいう)のリサイズ、攻撃の指定(nextwavesへの代入)などをここで行います。

DefenseEnding()

攻撃が終了した後に実行されます。後述のRandomEncounterText()でダイアログテキストを変更するのはここが最適です。

HandleSpare()

プレイヤーがモンスターを逃がそうとしたとき(逃がせるかどうかにかかわらず)に実行されます。ある程度技術が身につくと、Spareを選択した瞬間に問答無用でブラスターを浴びせる、なんてこともできるようになります。

HandleItem(ItemID,(position))

プレイヤーがアイテムを使用したときの処理です。
ItemIDには使用したアイテムの名称が全て大文字に変換されて代入され、positionには(これは省略可能)何番目のアイテムが使用されたかが数値で代入されます。アイテムを初期化するには後述のInventoryオブジェクトを使います。

その他の関数・オブジェクト

宣言の必要なく使用できる組み込みの機能一覧です。

State(state)

強制的に状態を遷移させます。引数stateは以下の状態を文字列型で指定します。主な状態の一覧は次の通りです。

  • ACTIONSELECT … 行動選択画面
  • ENEMYDIALOGUE … 敵の会話中画面
  • DEFENDING … 敵の攻撃中画面

これ以外の状態もありますが、まず使いません。何なら使ったらバグりやすくなるのでお勧めしません。

function EncounterStarting()
  State('ENEMYDIALOGUE')   -- エンカウンター開始時に敵との会話を始める
end

function HandleSpare()
  State('DEFENDING')   -- Spareした時、問答無用で攻撃に移る
end

こんな使い方ができます。

RandomEncounterText() 返り値...文字列型

毎ターン、自動的にダイアログボックスに書かれるメッセージをMonsterスクリプトのcomments変数から受け取ります。詳しくは次回、Monsterスクリプト編で解説します。

BattleDialog(message)

ダイアログボックスに文字を表示します。アイテムを使用したときやACTした時などの処理に必須です。
引数messageはテーブル型(文字列型の集まり)です。

Playerオブジェクト

プレイヤーに関する情報です。より詳しいことはWaveスクリプトの解説で行います。

Player.maxhp,Player.hp (数値型)

プレイヤーのHPです。maxhpは最大HP,hpは現在HPです。

Player.lv (数値型)

LOVEの値を指定します。これによりPlayer.atk,Player.def(ATK,DEF…どちらも数値型)が決定させます。

Player.Heal(healhp)

プレイヤーを回復する関数です。healhpは数値型で、どれだけHPを回復するか指定します。

Inventoryオブジェクト

アイテム欄に関する情報です。

Inventory.AddCustomItems(items,itemtype)

デフォルトのアイテムはすべてTestDog1~7になっている(らしい)ので、この関数でデフォルト以外のアイテムを登録します。itemsはテーブル型(文字列型の集まり)でアイテム名を入れ、itemtypeはテーブル型(数値型の集まり)でitemsの要素のそれぞれに対しアイテムのタイプを指定します。アイテムのタイプは以下の通りです。

  • 0 ... 消耗品。回復アイテムなどに使う
  • 1 ... 武器。使用すると装備できる
  • 2 ... 鎧。使用(ry
  • 3 ... その他。使用時に消費されない

Inventory.SetInventory(items)

インベントリにアイテムをセットします。itemsはテーブル型(文字列型の集まり)でアイテム名を入れます。

まとめ

  • Encounterでは全体やプレイヤーに関わる処理を行う。
  • ゲーム内イベントに関する処理もここで記述する。

次回はMonsterスクリプトの解説を行います。

参考

CYF本体のDocumentation CYF 0.6.5/documantation.htmlにも同じ内容が書いてありますので、わからない場合はgoogle翻訳などを利用しドキュメントを見ることをおすすめします。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0