6.試合終了

試合はタイムアップや1人だけ生き残るなど、いくつかの条件で終了することがあります。

敗北したプレイヤーの管理

現在、倒されたプレイヤーはアリーナでリスポーンしています。これを、次の対戦を待つためにロビーに転送する処理に変えます。

  1. PlayerManager で、respawnPlayerInLobby() という名前の関数を作成します。その関数で、次の操作を行います。
  • プレーヤーの RespawnLocation プロパティを LobbySpawn に設定します。
  • player:LoadCharacter() でキャラクターをリロードします。LoadCharacter() はスポーン時にプレイヤーを再作成し、持っていたツールをすべて削除します。

アノニマス関数の使用

アノニマス関数は、高度なスクリプトで特定の状況下で一般的に使用されます。それらを定義する前に、スクリプトが遭遇する可能性のある状況を理解することが重要です。

少し考えてみましょう。倒したプレイヤーをロビーに転送させるには、リスポーン機能をプレイヤーの Died イベントに接続する必要があります。しかし1つ問題があります。これまでのようにイベントに接続しただけでは、Diedイベントからプレイヤーの名前を取得する方法がありません。スクリプトは、アクティブなプレーヤーを追跡するテーブルから倒されたプレーヤーを削除するために、プレイヤー名が必要です。

Died イベントをトリガーしたプレイヤーの名前を取得するには、アノニマス関数を使用します。アノニマス関数には名前がなく、Connect() 内で直接作成することができます。

アノニマス関数は以下のように記述します。

myPlayer.Died:Connect(function()
    print(player)
end)

Diedイベントのコーディング

この関数では、プレイヤーのキャラクターが死亡するたびに、プレイヤーを復活させる関数がトリガーされます。

  1. プレーヤーの Died イベントにアクセスするには、 PlayerManager の preparePlayer() で、player の Humanoid 変数を追加します。
local function preparePlayer(player, whichSpawn)
    player.RespawnLocation = whichSpawn
    player:LoadCharacter()

    local character = player.Character or player.CharacterAdded:Wait()
    local sword = playerWeapon:Clone()
    sword.Parent = character

    local humanoid = character:WaitForChild("Humanoid")
end
  1. Died イベントに接続するアノニマス関数を作成します。まず、 humanoid.Died:Connect() と入力します。次に、Connect() 内 にfunction() と入力し、 Enter キーを押して end をオートコンプリートし、余分な括弧を削除します。
    local humanoid = character:WaitForChild("Humanoid")

    humanoid.Died:Connect(function()

    end)
  1. アノニマス関数で respawnPlayerInLobby() を呼び出します。パラメータの player は、次の試合に出る準備をしているプレイヤーになります。
    local humanoid = character:WaitForChild("Humanoid")

    humanoid.Died:Connect(function()
        respawnPlayerInLobby(player)
    end)
  1. サーバーを起動して試合を行います。プレイヤーが死ぬと、ロビーでリスポーンすることをテストします。プレイヤーを倒すには、ロビーでリスポーンしたことを確認できるまで、プレイヤーに近づき、武器を使用します。
プレイヤー2への攻撃
リスポーン後のロビーの Player2

必要に応じて、いくつかの異なるテスト方法もあります。

  • プレイヤーを強制的に倒すには、「Server/サーバー」の「Output Window/出力ウィンドウ」の「Command Bar/[コマンド バー」に、workspace.Player1.Humanoid.Health = 0 をコピーして貼り付けます。Enter を押してコマンドを実行します。他のプレーヤーを削除するには、Player2、Player3 などを使用します。
  • GameSettings の matchDuration を長めの時間に設定すると、すべてのプレイヤーを見つけて排除するための時間が作れます。
  • テストを高速化するには、GameSettings の minimumPlayers を 2 などの小さい数値に変更します。

ゲームの終了

敗北したプレイヤーがリスポーンしたところで、ゲームを終了する処理を追加します。MatchEnd イベントを作成したことを覚えていますか? タイマーが切れるか、勝者が見つかったときに発生します。

どの条件でゲームが終了したかを他のスクリプトに伝えるには、TimerUp と FoundWinner の変数を含むテーブルを作成します。試合終了イベントが発生すると、変数が渡され、他のスクリプトが応答できるようになります。

  1. GameSettings で、 endStatesという名前の空のモジュールテーブルを作成します。
local GameSettings = {}

-- Game Variables
GameSettings.intermissionDuration = 5
GameSettings.matchDuration = 10
GameSettings.minimumPlayers = 2
GameSettings.transitionTime = 5

-- Possible ways that the game can end.
GameSettings.endStates = {
}

return GameSettings
  1. TimerUp および FoundWinner という名前の 2 つの変数を作成します。それぞれに、名前と同じ文字列を設定します。これらの文字列は、コードのテストに使用されます。
GameSettings.endStates = {
    TimerUp = "TimerUp",
    FoundWinner = "FoundWinner"
}

タイマーで終了する

タイマーが終了したら、MatchEnd イベントを発生させ、MatchEnd ステート変数を送信します。そうすれば、そのイベントをリッスンしている他のスクリプトは、それに応じて応答することができます。イベントがシグナルを発生させる一方で、TimerUp や FoundWinner のようにリスニングしているスクリプトが受信するデータを送信することもできることを忘れないでください。

  1. GameManager の while true do ループで、matchEnd.Event:Wait() を見つけます。行頭に「local endState =」を追加します。
while true do
    displayManager.updateStatus("Waiting for Players")

    repeat
        wait(gameSettings.intermissionDuration)
    until Players.NumPlayers >= gameSettings.minimumPlayers

    displayManager.updateStatus("Get ready!")
    wait(gameSettings.transitionTime)

    matchManager.prepareGame()
    local endState = matchEnd.Event:Wait()
end
  1. 正しい状態を受信したことを確認するために、 endStateを含む print 文を追加します。
while true do
    displayManager.updateStatus("Waiting for Players")

    repeat
        wait(gameSettings.intermissionDuration)
    until Players.NumPlayers >= gameSettings.minimumPlayers

    displayManager.updateStatus("Get ready!")
    wait(gameSettings.transitionTime)

    matchManager.prepareGame()
    local endState = matchEnd.Event:Wait()
    print("Game ended with: " .. endState)
end
  1. MatchManager で timeUp() を見つけて、print 文を削除します。次に試合終了イベントを発生させるには、matchEnd:Fire() と入力して gameSettings.endStates.TimerUp を渡します。
-- Values
local displayValues = ReplicatedStorage:WaitForChild("DisplayValues")
local timeLeft = displayValues:WaitForChild("TimeLeft")

-- Creates a new timer object to be used to keep track of match time.
local myTimer = timer.new()

-- Local Functions
local function timeUp()
    matchEnd:Fire(gameSettings.endStates.TimerUp)
end
  1. テストします。タイムアップした際に print 文で TimerUp と表示されることを確認します。

トラブルシューティング

この時点ではメッセージが表示されなかった場合は以下のいずれかを試してみてください。

  • matchEnd:Fire の引数が、gameSettings.endStates.TimerUp になっているかどうかを確認してください。
  • matchEnd:Fire() のように、ドット演算子の代わりに: (コロン演算子) を使用してください。

勝利試合のコーディング

次に、勝ったプレイヤーを特定し、負けたプレイヤーを削除し、タイマーを止めるなどのクリーンアップ処理を実行する必要があります。また、1人でもプレイヤーが残っていれば、試合は終了します。FoundWinner の条件が満たされたかどうかを確認するために、試合中のプレーヤーを追跡するテーブルの残り人数をチェックする関数が必要です。

プレイヤー数の確認

プレイヤーが 1 人しか残っていない場合、試合は終了します。これを確認するには、スクリプトでラウンド中のプレーヤーを追跡する必要があります。1 人のプレーヤーが残ったら、勝者を割り当てることができます。

  1. PlayerManager で、次の変数を定義します。
    • ModuleScripts フォルダ
    • GameSettings モジュール :終了状態変数へのアクセスに使用
    • Events フォルダと MatchEnd イベント
local PlayerManager = {}

-- Services
local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- Modules
local moduleScripts = ServerStorage:WaitForChild("ModuleScripts")
local gameSettings = require(moduleScripts:WaitForChild("GameSettings"))
-- Events
local events = ServerStorage:WaitForChild("Events")
local matchEnd = events:WaitForChild("MatchEnd")
  1. respawnPlayerInLobby() の上に、 checkPlayerCount( ) という名前の新しいローカル関数を追加します。
-- Player Variables
local activePlayers = {}
local playerWeapon = ServerStorage.Weapon

local function checkPlayerCount()

end

local function respawnPlayerInLobby(player)
local function で関数を作成した場合、この関数を呼んでいる場所よりも前の方で作成しなくてはなりません。
  1. その関数内で、if then 文を使用して勝者をチェックします。その文は次の内容になります。
  • activePlayers テーブルの数が 1 かどうかを確認します。
  • その場合は、matchEnd を起動し、 gameSettings.endStates.FoundWinner を渡します。
local function checkPlayerCount()
    if #activePlayers == 1 then
        matchEnd:Fire(gameSettings.endStates.FoundWinner)
    end
end

プレーヤーの削除

プレイヤーが削除されない限り、正確なカウントを維持することはできません。プレイヤーが敗北した場合、プレイヤー テーブルからプレイヤーを削除して、正確なプレイヤー数を維持します。次に、アクティブ プレイヤー テーブルの数をチェックして、勝者がいるかどうかを確認します。

  1. checkPlayerCount() の下に、removeActivePlayer() という名前の関数を作成します。パラメータは削除対象の player になります。
local function checkPlayerCount()
    if #activePlayers == 1 then
        matchEnd:Fire(gameSettings.endStates.FoundWinner)
    end
end

local function removeActivePlayer(player)

end
  1. activePlayers テーブルでプレーヤーを見つけるには、pairs() を使って activePlayer テーブルを対象とする for ループを使用します。次にパラメータ player に一致するプレーヤーを if 文で探します。
local function removeActivePlayer(player)
    for playerKey, whichPlayer in pairs(activePlayers) do
        if whichPlayer == player then

        end
    end
end
似たような変数名は別にしておきます。上記の関数が player と whichPlayer の2つの player を含むように、変数名が重複しないように注意してコーディングしてください。

例えば、反復テーブルの変数名に which をつけると、whichPlayer や whichPart のように混乱を避けることができます。
  1. プレーヤーを削除するには、if ステートメントで次のようにします。
  • table.remove() を呼び出します。パラメータは対象になるテーブルと、削除するキーになりますので、activePlayers と playerKey を渡します。
  • playerLeft の値を #activePlayers に設定します。
  • checkPlayerCount() を実行して、勝者を確認します。
local function removeActivePlayer(player)
    for playerKey, whichPlayer in pairs(activePlayers) do
        if whichPlayer == player then
            table.remove(activePlayers, playerKey)
            playersLeft.Value = #activePlayers
            checkPlayerCount()
        end
    end
end

イベントとテストの接続

作成したこの関数を使用するには、プレイヤーの Died イベントに接続されたアノニマス関数内から呼び出します。

  1. preparePlayer() を検索します。Diedイベント接続のアノニマス関数で、removeActivePlayer() を呼び出します。そして、パラメータとして player を渡します。
    humanoid.Died:Connect(function()
        respawnPlayerInLobby(player)
        removeActivePlayer(player)
    end)
  1. 勝利したプレイヤーが見つかったかどうかを確認するには、テストサーバーを起動します。プレイヤーが1人だけになったとき、出力ウィンドウにFoundWinnerと表示されるはずです。
  1. テストを続行し、試合を終了させます。新しい試合が開始されると、出力ウィンドウにエラーが表示されることに注意してください。

このエラーは、タイマーが停止していないことが原因で発生します。これは、次のセクションで修正します。

タイマーを止める

試合に勝ったプレイヤーで終了するたびに、タイマーも停止する必要があります。タイムアップになる前にタイマーを停止するには、試合終了イベントが発生するたびにタイマーを停止させるようにします。これは、イベントを作成する利点の 1 つです。イベントを作成するメリットのひとつは、複数の場面で再利用して、因果関係をスクリプト化できることです。

  1. MatchManager で、 stopTimer() という名前の新しいローカル関数を作成します。その中で myTimer:stop() と入力してタイマーを停止します。
-- Creates a new timer object to be used to keep track of match time.
local myTimer = timer.new()

-- Local Functions
local function stopTimer()
    myTimer:stop()
end

local function timeUp()
    matchEnd:Fire(gameSettings.endStates.TimerUp)
end
  1. 試合終了時にタイマーを停止させるには、matchEnd イベントを stopTimer() に接続します。
-- Module Functions
function MatchManager.prepareGame()
    playerManager.sendPlayersToMatch()
    matchStart:Fire()
end

matchStart.Event:Connect(startTimer)
matchEnd.Event:Connect(stopTimer)

return MatchManager
  1. サーバーを起動して、以前のエラーが表示されなくなったことを確認します。1 人を除くすべてのプレイヤーを排除し、試合が終了したら数秒待ちます。

次のステップ

2 つの勝利条件が終了しても、ゲーム ループを終了するためにまだいくつかのタスクが残っています。たとえば、勝者がロビーにテレポートされることはありません。次のレッスンでは、試合がどのように終了したかをプレーヤーに表示し、ゲームをリセットして、最終的にループ全体を完了させます。

完成したスクリプト

完成したスクリプトを掲載しますのでチェックしてみてください。

PlayerManager スクリプト

local PlayerManager = {}

-- Services
local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- Modules
local moduleScripts = ServerStorage:WaitForChild("ModuleScripts")
local gameSettings = require(moduleScripts:WaitForChild("GameSettings"))

-- Events
local events = ServerStorage:WaitForChild("Events")
local matchEnd = events:WaitForChild("MatchEnd")

-- Map Variables
local lobbySpawn = workspace.Lobby.StartSpawn
local arenaMap = workspace.Arena
local spawnLocations = arenaMap.SpawnLocations

-- Values
local displayValues = ReplicatedStorage:WaitForChild("DisplayValues")
local playersLeft = displayValues:WaitForChild("PlayersLeft")

-- Player Variables
local activePlayers = {}
local playerWeapon = ServerStorage.Weapon

-- Local Functions
local function checkPlayerCount()
    if #activePlayers == 1 then
        matchEnd:Fire(gameSettings.endStates.FoundWinner)
    end
end

local function removeActivePlayer(player)
    for playerKey, whichPlayer in pairs(activePlayers) do
        if whichPlayer == player then
            table.remove(activePlayers, playerKey)
            playersLeft.Value = #activePlayers
            checkPlayerCount()
        end
    end
end

local function respawnPlayerInLobby(player)
    player.RespawnLocation = lobbySpawn
    player:LoadCharacter()
end


local function onPlayerJoin(player)
    player.RespawnLocation = lobbySpawn
end

local function preparePlayer(player, whichSpawn)
    player.RespawnLocation = whichSpawn
    player:LoadCharacter()

    local character = player.Character or player.CharacterAdded:Wait()
    local sword = playerWeapon:Clone()
    sword.Parent = character

    local humanoid = character:WaitForChild("Humanoid")

    humanoid.Died:Connect(function()
        respawnPlayerInLobby(player)
        removeActivePlayer(player)
    end)
end

-- Module Functions
function PlayerManager.sendPlayersToMatch()
    local arenaSpawns = spawnLocations:GetChildren()

    for playerKey, whichPlayer in pairs(Players:GetPlayers()) do
        table.insert(activePlayers,whichPlayer)
        local spawnLocation = arenaSpawns[1]
        preparePlayer(whichPlayer, spawnLocation)
        table.remove(arenaSpawns, 1)
    end

    playersLeft.Value = #activePlayers
end

-- Events
Players.PlayerAdded:Connect(onPlayerJoin)

return PlayerManager

GameSettings スクリプト

local GameSettings = {}

-- Game Variables
GameSettings.intermissionDuration = 5
GameSettings.matchDuration = 10
GameSettings.minimumPlayers = 2
GameSettings.transitionTime = 5

-- Possible ways that the game can end.
GameSettings.endStates = {
    TimerUp = "TimerUp",
    FoundWinner = "FoundWinner"
}

return GameSettings

GameManager スクリプト

-- Services
local ServerStorage = game:GetService("ServerStorage")
local Players = game:GetService("Players")

-- Module Scripts
local moduleScripts = ServerStorage:WaitForChild("ModuleScripts")
local matchManager = require(moduleScripts:WaitForChild("MatchManager"))
local gameSettings = require(moduleScripts:WaitForChild("GameSettings"))
local displayManager = require(moduleScripts:WaitForChild("DisplayManager"))

-- Events
local events = ServerStorage:WaitForChild("Events")
local matchEnd = events:WaitForChild("MatchEnd")

while true do
    displayManager.updateStatus("Waiting for Players")

    repeat
        wait(gameSettings.intermissionDuration)
    until Players.NumPlayers >= gameSettings.minimumPlayers

    displayManager.updateStatus("Get ready!")
    wait(gameSettings.transitionTime)

    matchManager.prepareGame()
    local endState = matchEnd.Event:Wait()
    print("Game ended with: " .. endState)
end

MatchManager スクリプト

local MatchManager = {}

-- Services
local ServerStorage = game:GetService("ServerStorage")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- Module Scripts
local moduleScripts = ServerStorage:WaitForChild("ModuleScripts")
local playerManager = require(moduleScripts:WaitForChild("PlayerManager"))
local gameSettings = require(moduleScripts:WaitForChild("GameSettings"))
local timer = require(moduleScripts:WaitForChild("Timer"))

-- Events
local events = ServerStorage:WaitForChild("Events")
local matchStart = events:WaitForChild("MatchStart")
local matchEnd = events:WaitForChild("MatchEnd")

-- Values
local displayValues = ReplicatedStorage:WaitForChild("DisplayValues")
local timeLeft = displayValues:WaitForChild("TimeLeft")

-- Creates a new timer object to be used to keep track of match time.
local myTimer = timer.new()

-- Local Functions
local function stopTimer()
    myTimer:stop()
end

local function timeUp()
    matchEnd:Fire(gameSettings.endStates.TimerUp)
end

local function startTimer()
    print("Timer started")
    myTimer:start(gameSettings.matchDuration)
    myTimer.finished:Connect(timeUp)

    while myTimer:isRunning() do
        -- Adding +1 makes sure the timer display ends at 1 instead of 0.
        timeLeft.Value = (math.floor(myTimer:getTimeLeft() + 1))
        -- By not setting the time for wait, it offers more accurate looping
        wait()
    end
end

-- Module Functions
function MatchManager.prepareGame()
    playerManager.sendPlayersToMatch()
    matchStart:Fire()
end

matchStart.Event:Connect(startTimer)
matchEnd.Event:Connect(stopTimer)

return MatchManager
2 thoughts on “6.試合終了”
  1. NPCをフィールドに複数配置してのそのNPCのHealthが0になったらスコアが+1のような事を行いたいと思っています。例えばNPCを5体倒したら終了イベントへ通知するようなプログラムを書きたいのですがどのように書けばよいのでしょうか?

    1. この「バトルロイヤルゲーム」の仕組みとプログラムが理解できれば実現可能です。
      現在は対人(Player)となっていますが、これをNPCを対象としたものに改造することになります。
      その場合、activePlayers に追加するのはNPCになります。
      NPCにもHumanoidが入っていますので、Diedイベントが使用できます。
      checkPlayerCount()で、「activePlayers == 0」とすれば終了します。
      頑張ってください!

コメントを残す コメントをキャンセル

モバイルバージョンを終了