【Lua言語講座】14.演習-if文-

if文の演習回 Lua言語講座

こんにちは、ロブスタのハルです!
前回の講座では制御構文の一つであるif文について解説しました。
今回はif文に慣れるためにより実用的な演習をします。
前回の講座を見ていない方はこちらからご覧ください。

特殊効果の実装

ゲーム制作をするときプレイヤーに特殊効果を与えたいことがありますよね。例えば、透明にしたり、移動速度を速くしたり、HPを増やしたりなど。ここではそういった特殊効果をつけるプログラムを書いてみたいと思います。

概要

今回作りたいプログラムは以下のようなものです。

  • アイテムに触れると歩行速度を速くする
  • 歩行速度には上限をつけておく

アイテムに触れると~とあるのでif文が使えそうですね。また、以前出てきたTouchedイベントも使えそうです。歩行速度ってどうやって変えるの?と思うかもしれませんが、Humanoidオブジェクト内にあるWalkSpeedプロパティを変更することで変えることができるんです。

Humanoidオブジェクト

HumanoidオブジェクトとはプレイヤーやNPCなどのゲーム内の人型キャラクターを制御するためのオブジェクトのことです。

Humanoidオブジェクトの中にはプレイヤーの動きやアニメーション、HP、死亡状態などの様々な情報や機能が入っています。プレイヤーやNPCに関するプログラムを実装したいときにはHumanoidオブジェクトをいじると思ってもらって大丈夫です。

以下は、Humanoidオブジェクトの主なプロパティです。

  • Health: キャラクターのヒットポイント(体力)を表します。
  • MaxHealth: キャラクターの最大ヒットポイントを表します。
  • WalkSpeed: キャラクターの移動速度を表します。
  • JumpHeight: キャラクターのジャンプの高さを表します。
  • RootPart: キャラクターのルート部位(通常は足元)を表します。
  • PlatformStand: プラットフォーム上でのスタンスを設定するためのフラグです。trueに設定すると、プレイヤーは常に立ち続けます。

実装

アイテムの作成

まずは移動速度上昇用のアイテムを作っていきます。
新しくパーツを追加してください。パーツの名前はSpeedUpItemとしました。
お好みで色を変えたり、materialを変えてみてください。

プログラムを書く

SpeedUpItemオブジェクトを作成できたらプログラムを考えていきます。

流れを考える

今回はSpeedUpItemオブジェクトに触れるとプログラムが実行されるとのことなので、SpeedUpItemオブジェクトに新しいスクリプトを挿入しましょう。名前はWalkSpeedManagerとしました。

さて、演習回の恒例ですね。今回のプログラムがどんなものかを確認してプログラムの流れを考えてみてください。

考えられましたか?
僕は次のように考えました。

  1. SpeedUpItemオブジェクトを変数に代入しておく
  2. アイテムに触れたときに発生するリスナーを作る
  3. リスナーの内部は以下のようにする
  4. まずキャラクターとそのHumanoidオブジェクトを変数に代入しておく
  5. もしHumanoidオブジェクトが存在するなら、WalkSpeedプロパティに数値を加算する
  6. このままだと触れるたびに無限に早くなるので、上限を設ける
  7. リスナーとTouchedイベントを繋ぐ

ざっくりでいいのでこうしようかなというのが思い浮かぶと楽しくなるのかなと思います。

コーディング

いよいよコーディングです。

local speedUpItem = script.Parent

local funciton onTouch(otherObj)
    local character = otherObj.Parent
    local humanoid = character:FindFirstChildWhichIsA("Humanoid")

    if humanoid and humanoid.WalkSpeed <= 60 then
        humanoid.WalkSpeed = humanoid.WalkSpeed + 10
    end
end

speedUpItem.Touched:Connect(onTouch)

今回はこのように実装しました。順に解説していきます。

まず1行目でこのスクリプトの親であるspeedUpItemオブジェクトを変数speedUpItemに代入しています。

3~10行目でonTouchリスナーを定義しています。引数には触れているオブジェクトであるotherObjを設定しています。Touchedイベントは触れたものすべてに反応します。つまり、床であるBaseplateと触れていても処理が実行されてしまうのです。そこでキャラクターと触れたときにだけ処理が実行されるようにします。

4行目ではonTouchと触れたオブジェクトの親をcharacterとして保管しています。5行目ではcharacterオブジェクトのメソッドであるFindFirstChildWhichIsA(“Humanoid”)を実行した結果を変数humanoidに代入しています。

7~9行目ではhumanoidオブジェクトが存在した場合の処理をしています。具体的には8行目で現在の歩行速度に+10しています。また、if文の条件式に含まれているhumanoid.WalkSpeed <= 60は歩行速度の上限を表しており、これ以上なら中の処理が実行されません。

12行目ではTouchedイベントにonTouchリスナーを接続しています。

テスト

ここまででテストしてみましょう。上手くいったら次に進みます。

テストしていると1回踏んだだけなのに複数回実行されることがあるかと思います。
そこでCanTouchプロパティとtask.wait()を用いて1秒おきにしか実行されないようにします。

local speedUpItem = script.Parent

local funciton onTouch(otherPart)
    local character = otherPart.Parent
    local humanoid = character:FindFirstChildWhichIsA("Humanoid")

    if humanoid and humanoid.WalkSpeed <= 60 then
      humanoid.WalkSpeed = humanoid.WalkSpeed + 10
      speedUpItem.CanTouch = false
      task.wait(1)
      speedUpItem.CanTouch = true
    end
end
speedUpItem.Touched:Connect(onTouch)

これで連続して触れないようになったと思います。

FindFirstChildWhichIsA()メソッド

FindFirstChildWhichIsA()メソッドは呼び出し先のオブジェクトの中に引数のオブジェクト(正確にはクラス)が存在するどうかを調べてくれます。複数のオブジェクトが当てはまる場合は一番最初の子を返します。

見つかった場合はそのオブジェクトを返し、見つからない場合はnilを返します。

FindFirstChildWhichIsA()メソッドはInstanceクラスのオブジェクトつまり、すべてのゲームオブジェクトに対して使用することができます。

CanTouchプロパティ

CanTouchプロパティはPartオブジェクト(クラス)が持っているプロパティの一つで、他のパーツとの衝突や接触を許可するかどうかを制御します。trueなら衝突や接触できるようになり、falseならできなくなります。

task.wait()関数

task.wait()関数は引数に指定した秒数だけ動作を待つ関数です。気を付けてほしいのはtaskオブジェクトのwaitメソッドではなく、taskライブラリの中のwait()関数であるということです。ライブラリとは特定の機能をまとめたコードの集まりです。とりあえずtaskはオブジェクトではないんだなという認識で大丈夫です。

トラップの実装

トラップはゲームをより魅力的にする要素の一つですよね。ここではトラップを作ってみたいと思います。

概要

今回作りたいプログラムは次のようなものです。

  • パーツに触れるとHPが0になる

シンプルですが、どんな実装をすればいいか思いつきますか?

実装

アイテムの作成

まずはコードを実装したいパーツを作成します。
先程と同じですね。
今回は名前をLavaとしました。

プログラムを書く

いつもの流れで考えていきましょう。

流れを考える

先程と同じくLavaに触れるとプログラムが実行されるため、Lavaオブジェクトに新しいスクリプトを挿入しましょう。名前はLavaDamageScriptとしました。

さてさて、今回のプログラムの流れを考えてみてください。

思いつきましたか?

  1. Lavaオブジェクトを変数に代入しておく
  2. アイテムに触れたときに発生するリスナーを作る
  3. リスナー内部は以下のようにする
  4. まずキャラクターとそのHumanoidオブジェクトを変数に代入しておく
  5. もしHumanoidオブジェクトが存在するなら、Healthプロパティを0にする
  6. リスナーとTouchedイベントを繋ぐ

先程のプログラムとほとんど同じですね。違いはWalkSpeedプロパティではなくHealthプロパティを変更することぐらいです。

特に4と5のもしHumanoidオブジェクトが存在するならの部分は当たり判定と呼ばれることがあります。なので、今後はこのサイト上でもHumanoidオブジェクトの当たり判定と呼んでいきたいと思います。

コーディング

それではコーディングしていきましょう。

local lava = script.Parent

local function onTouch(otherObj)
  local character = otherObj.Parent
  local humanoid = character:FindFirstChildWhichIsA("Humanoid")

  if humanoid then
    humanoid.Health = 0
  end
end

lava.Touched:Connect(onTouch)

先程のプログラムとほぼ同じなので解説は省略します。

8行目ではhumanoidのHealthプロパティに0を代入することでプレイヤーのHPを0にしています。

humanoidの当たり判定は4~9行目(8行目は除く)ですね。

テスト

テストしてみましょう。

きちんと動作していることがわかります。

スコアの増減の実装

ゲームにはスコアやポイントがよく出てきますよね。ここではスコアに関するプログラムを書いてみたいと思います。

概要

今回作りたいプログラムは以下の通りです。

  • パーツに触れるとスコアがもらえる
  • パーツは一定時間ごとに色が変わる
  • 色ごとにスコアは違う
  • スコアをもらうとリーダーボードに表示される

少し複雑なプログラムになりそうです…

リーダーボード

リーダーボードとはゲーム画面の右上あたりに表示されるUIのことで、ゲーム内の様々な情報が載っています。

リーダーボードを使うにはプログラムで指定してあげる必要があります。

以下のコードをServerScriptServiceにスクリプトとして追加しましょう。
ServerScriptServiceを使うのはゲーム全体で実行されてほしいプログラムだからです。

local function onPlayerJoined(player)
  local leaderstats = Instance.new("Folder")
  leaderstats.Name = "leaderstats"
  leaderstats.Parent = player

  local score = Instance.new("IntValue")
  score.Name = "Score"
  score.Value = 0
  score.Parent = leaderstats
end

game.Players.PlayerAdded:Connect(onPlayerJoined)

簡単に解説します。

1~10行目はプレイヤーがゲームに入室したときに実行されるリスナーです。

2行目では新しいフォルダを作成し、変数leaderstatsに代入しています。つまり、leaderstatsの中身はただのフォルダです。

3行目ではフォルダの名前をleaderstatsにしています。
4行目ではフォルダの親をプレイヤーにしています。
リーダーボードはプレイヤーの子にleaderstatsという名前のフォルダを作ることで表示されるようになるため、3・4行目は必ず必要です。

6行目では整数型のオブジェクトscoreを作っています。
7・8行目はそれぞれscoreの名前と初期値を設定しています。リーダーボードにはここで指定したものが表示されます。
9行目ではleaderstatsフォルダをscoreの親にしています。つまり、leaderstatsフォルダの中にscoreオブジェクトを入れていることになります。

12行目ではgame.PlayersオブジェクトのPlayerAddedイベントとonPlayerJoinedリスナーを接続しています。

余談ですが、onPlayerJoinedリスナーの中で定義している変数は全てローカル変数です。なのでこの関数が呼び出されるたびにプレイヤーごとに別々のフォルダが作られることになります。

PlayerAddedイベント

PlayerAddedイベントはPlayersオブジェクトのイベントで、プレイヤーが入室したことを検知する役割を果たします。

実装

アイテムの作成

まずはスコア加算用のパーツを作成します。今回は名前をScorePartにしました。

プログラムを書く

プログラムを考えるわけですが今回はちょっと難しいので見てしまって構いません。自分で思いつかないときにはネットで検索したり、プログラミングができる人(が近くにいれば)に聞いてみるのも大切です。

流れを考える

今回は以下のようなプログラムを作りたいんでしたね。

  • パーツに触れるとスコアがもらえる
  • パーツは一定時間ごとに色が変わる
  • 色ごとにスコアは違う
  • スコアをもらうとリーダーボードに表示される
  1. スコア加算用のパーツを変数に代入しておく
  2. 各色のオブジェクトを変数に代入しておく
  3. 各スコアを設定しておく
  4. スコア加算用の関数をつくる
  5. パーツに触れたときに実行されるリスナーをつくる
  6. 中身はplayerとパーツの当たり判定とスコア加算の関数
  7. Touchedイベントに接続する
  8. リーダーボードと連携する機能をつくる
  9. 足りない部分を修正する

僕自身もざっくりとしか思いつかなかったのでコーディングしながら修正していこうと思います。

コーディング
-- スコア加算用のパーツを保管
local scorePart = script.Parent

-- 三色を使いやすいように保管
local red = Color3.fromRGB(255, 0, 0)
local green = Color3.fromRGB(0, 255, 0)
local blue = Color3.fromRGB(0, 0, 255)

-- スコアを設定
local redScore = 10
local greenScore = 50
local blueScore = 20

-- プレイヤーを保管
local Players = game:GetService("Players")

-- スコアに関する関数
local function ManageScore(player)
    -- 現在の色とリーダーボード、スコアを保管
	local colorNow = scorePart.Color
	local playerStats = player:WaitForChild("leaderstats")
	local playerScore = playerStats:WaitForChild("Score")
	
    -- 現在の色に応じてスコアを変える処理
	if colorNow == red then
		playerScore.Value = playerScore.Value + redScore
	elseif colorNow == green then
		playerScore.Value = playerScore.Value + greenScore
	else
		playerScore.Value = playerScore.Value - blueScore
	end
	
    -- スコア加算用のパーツを消す
	scorePart:Destroy()
	
    -- パーティクルエミッタを作り、現在のパーツの色に設定
	local particle = Instance.new("ParticleEmitter")
	particle.Color = ColorSequence.new(colorNow)

    -- プレイヤーのキャラクターを保管し、頭部を検索
	local playerChara = player.Character
	particle.Parent = playerChara:WaitForChild("Head")

    -- 1秒待ってパーティクルエミッタを消す
	task.wait(1)
	particle:Destroy()
end

-- スコア加算用のパーツに触れたときに実行されるリスナー
local function onTouch(otherPart)
	local player = Players:GetPlayerFromCharacter(otherPart.Parent)
    -- プレイヤーの当たり判定
	if player then
		ManageScore(player)
	end
end

-- スコア加算用パーツのタッチイベントとリスナーを接続
scorePart.Touched:Connect(onTouch)

-- 無限ループ
while true do
	scorePart.Color = red
	task.wait(3)
	scorePart.Color = green
	task.wait(2)
	scorePart.Color = blue
	task.wait(1)
end

基本的にコード中のコメント通りです。

20行目で現在の色を取得して、それが赤・緑・青いずれかをif文によって判断しています。
38行目のColorSequenceはカラーグラデーションに関するデータ型です。
コンストラクタnewの引数としてColor3型のデータを渡すようになっています。
これは公式リファレンスのここに書いているので読んでみてください。

リファレンス内でColorSequence.new(c: Color3)と書かれていると思いますが、
c引数として変数cを渡していること: Color3変数cの型がColor3であることを表しています。
これに限らず、メソッド名(引数: 型名)のように書かれていることが多いです。

62行目のwhile文については次回解説します。

テスト

リーダーボードに得点が反映されているようです。
ブロックに触れた瞬間の色がParticleEmitterとして出てきているのがわかりますね。

まとめ

  • 制御構文とはプログラムの動作を管理する命令のこと
  • Humanoidオブジェクトでゲーム内の人型キャラクターを制御できる
  • FindFirstChildWhichIsA()で呼び出し先のオブジェクトの中に引数のオブジェクト(正確にはクラス)が存在するかどうかを確認できる
  • CanTouchプロパティで他のパーツとの衝突や接触を許可するかどうかを制御できる
  • task.wait()は引数に指定した秒数だけ動作を待つ
  • リーダーボードとはゲーム画面の右上あたりに表示されるUIのこと
  • PlayerAddedイベントはプレイヤーが入室したことを検知する
  • ColorSequece.new(c: Color3)でカラーグラデーションを持つオブジェクトを作成できる
  • Instance.new(className : string, parent : Instance)でインスタンスを作成できる

お疲れ様でした。今回は制御構文の一つであるif文について演習形式で解説しました。if文はかなり使う機会が多いので徐々に慣れていきたいところですね。

次回はwhile文について解説していきたいと思います。

コメント

タイトルとURLをコピーしました