👊

【ロブロックス】他ユーザーを押し出すパンチを実装

2025/02/12に公開

概要

他ユーザーをパンチしてステージから押し出せる機能を実装します

調査

調べたらこのチュートリアルが紹介されてたのでコードを読んでみます
https://www.youtube.com/watch?v=DpHm0wIeUKI

コードはここから入手可能
https://create.roblox.com/store/asset/4770745044/Punch-Script

-- Made by: AccessQ
-- Youtube: https://youtube.com/AccessQ
-- Twitter: https://twitter.com/RealAcccessQ



CanDoDmg = true -- If the player can do damage
Keybind = "f" -- Keybind to activate the punch
dmg = 10 -- How much damage the player takes


local plr = game.Players.LocalPlayer
local mouse = plr:GetMouse()

mouse.KeyDown:Connect(function(key)
	if key == Keybind then
		local anim = script.Parent.Humanoid:LoadAnimation(script.Animation)
		anim:Play()
		script.Parent.RightHand.Touched:connect(function(hit)
			if hit.Parent.Humanoid and CanDoDmg == true then
			hit.Parent.Humanoid:TakeDamage(dmg)
			CanDoDmg = false
			wait(1)
			CanDoDmg = true
			end
		end)
	end
end)

StarterCharacterScripts

チュートリアルではこのコードを StarterCharacterScripts に入れていた

キャラクターがリスポーン時は維持されない(値が?)
https://create.roblox.com/docs/reference/engine/classes/StarterCharacterScripts

Localスクリプトに Animate, Sound, or Health などを定義すると上書きできるらしい
https://create.roblox.com/docs/reference/engine/classes/LocalScript

アニメーションの確認

上記のスクリプトをそのまま配置して定義されてる「f」キーを押してもエラーになる
Failed to load animation with sanitized ID rbxassetid://4738262351: Animation failed to load

これはL17で呼び出そうとしているscript直下のAnimationに定義されている rbxassetid://4738262351 が見つからないから

script.Parent.Humanoid:LoadAnimation(script.Animation)

チュートリアルではアニメーションを作ってアップロード、そのIDを定義してる
https://youtu.be/DpHm0wIeUKI?t=350

アニメーションの作成と公開

  1. 「リグビルダー」からリグを配置、「アニメーション編集」を押してアニメーションウィンドウを表示1 > 名前を入力
  2. Unityみたいにタイムラインで編集するがかなり分かりやすい
  3. ロブロックスに公開
  4. 公開するとURLとIDが入手できる
    1. https://create.roblox.com/store/asset/86353972447011/Punch
  5. このIDをスクリプト直下のAnimationへ定義するとスクリプトから実行できるようになる
  6. リグがあってないのか変なツッコミみたいな動きになった…

クリックでパンチする

要素のクリックイベントはClick Detectorを使うみたいだが、画面全体のクリックの判定はUserInputServiceを使うのが良さそう

描いたコード

local UserInputService = game:GetService("UserInputService")

UserInputService.InputBegan:Connect(function(input)
	if
		input.UserInputType == Enum.UserInputType.MouseButton1
	then
		print("punching")
		local anim = script.Parent.Humanoid:LoadAnimation(script.Animation)
		anim:Play()
		script.Parent.RightHand.Touched:connect(function(hit)
			if hit.Parent.Humanoid then
				print("hit")
				wait(1)
			end
		end)
	end
end)

※ ただこれだとメニュークリック時も反応しちゃうので、避けたいときはクリックした対象の判定が必要

パンチ中に他ユーザーに当たると押し出せる

これはLocalでパンチを実行して、「パンチ中のユーザーに当たった」という判定を他ユーザーにさせようとしたらローカルとサーバーでのデータのやり取りでハマりましたが最終的にちゃんとReplicatedStorageにたどり着きました

色々記事を見たけど下記の記事が一番理解できたかつエラーが起こらなかった
https://selegee.com/8107/

RemoteEventとローカル、サーバで参照できる範囲とかについてはまた別途まとめようと思います
(RmoteEventのデータの渡し方はreactの手癖で書きました😅)

クライアント側

local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- クリックしたときのイベント
UserInputService.InputBegan:Connect(function(input)
	local player = Players.LocalPlayer
	if
		input.UserInputType == Enum.UserInputType.MouseButton1
	then
		player:SetAttribute("IsPunching", true)

		-- パンチのアニメーション
		local anim = script.Parent.Humanoid:LoadAnimation(script.PunchAnimation)
		anim:Play()
		wait(1)
		player:SetAttribute("IsPunching", false)
	end
end)

-- 何かにぶつかった時
local function onPunch(hit)
	local player = Players.LocalPlayer
	local targetCharacter = hit.Parent
	local targetPlayer = Players:GetPlayerFromCharacter(targetCharacter)

	local punchEvent = ReplicatedStorage:WaitForChild("PunchEvent")

	-- 自分がパンチ中 & Humanoidだった場合
	if
		targetCharacter:FindFirstChild("Humanoid")
		and player:GetAttribute("IsPunching")
	then
		player:SetAttribute("IsPunching", false)
		local nockBackVector = player.Character.HumanoidRootPart.CFrame.LookVector
		print("リモートイベント送信")
		local data = {
			["eventKey"] = "PUNCH";
			["payload"] = {
				["targetPlayer"] = targetPlayer;
				["nockBackVector"] = nockBackVector;
			};
		}
		punchEvent:FireServer(data)
	end
end

-- Serverイベント受信時
local moveEvent = ReplicatedStorage:WaitForChild("moveEvent")
moveEvent.OnClientEvent:Connect(function(_data)
	print("サーバイベント受信", _data)
	local nockBackVector = _data.payload
	local LinearVelocity = Instance.new("LinearVelocity")
	local player = Players.LocalPlayer
	local finalVector = (nockBackVector * 50 ) + Vector3.new(0,12,0)
	local Attachment = Instance.new("Attachment", player.Character.HumanoidRootPart)

	LinearVelocity.VectorVelocity = finalVector
	LinearVelocity.ForceLimitsEnabled = true
	LinearVelocity.MaxForce = 5000
	LinearVelocity.Parent = player.Character.HumanoidRootPart
	LinearVelocity.Attachment0 = Attachment
	LinearVelocity.Enabled = true
	wait(0.01)
	LinearVelocity:Destroy()
end)


script.Parent.RightHand.Touched:connect(onPunch)
script.Parent.Head.Touched:connect(onPunch)

サーバ側

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

--

local punchEvent = Instance.new("RemoteEvent", ReplicatedStorage)
punchEvent.Name = "PunchEvent"

local moveEvent = Instance.new("RemoteEvent", ReplicatedStorage)
moveEvent.Name = "moveEvent"

punchEvent.OnServerEvent:Connect(function(player, _data)
	print("クライアント側でイベント受信")
	print(_data)
	local eventKey = _data["eventKey"]
	local payload = _data["payload"]

	if eventKey == "PUNCH" then
		print(`eventKey == "PUNCH"`)
		print(payload)
		local data = {
			["eventKey"] = "MOVE_EVENT";
			["payload"] = payload.nockBackVector;
		}
		print(data)
		moveEvent:FireClient(payload.targetPlayer, data)
	end
end)

--

まとめ

ゲームのリアルタイム通信系の処理を割と初めて書いた気がする
遅延とかリアルタイム性を除けば割とWebのクライアント(ブラウザ)側とサーバ側と同じ感覚でいいかもしれない

Discussion