Unityで作成しているVRchat用の格闘ゲームで、ダメージを表示するキャンバスと手に追従させている当たり判定の同期をしたい
現在、UnityでVRChatに投稿する用の二人プレイ限定格闘ゲームを作成しています。
・スマブラのように、相手を殴るとダメージが溜まり吹っ飛ぶ距離がのびる。
・ダメージを与えるための手の当たり判定「Gloveオブジェクト」左右と、ダメージを表示するためのキャンバス「DamageUIキャンバス」を人数分用意。is Triggerオンにしたstartzoneオブジェクトの範囲内に2プレイヤー入るとそれぞれのプレイヤーにGloveオブジェクトとDamageUIキャンバスがアタッチされる。
解決したいこと
・殴られた側のプレイヤーのDamageUIキャンバスとGloveオブジェクトのオーナー権限が外れてしまいます。
ソースコード
startzoneにアタッチされた「playerStartTrigger.cs」
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
using System;
using UnityEngine.UI;
using TMPro;
using System.Collections;
using System.Collections.Generic;
public class playerStartTrigger : UdonSharpBehaviour
{
[SerializeField] private GameObject canvasPrefab;
[SerializeField] private GameObject DamageUI1; //プレイヤー1のダメージキャンバス
[SerializeField] private GameObject DamageUI2; //プレイヤー2のダメージキャンバス
[SerializeField] private GameObject FollowObjectTransformLeft; //プレイヤー1の左グローブ
[SerializeField] private GameObject FollowObjectTransformRight; //プレイヤー1の右グローブ
[SerializeField] private GameObject FollowObjectTransformLeft2; //プレイヤー2の左グローブ
[SerializeField] private GameObject FollowObjectTransformRight2; //プレイヤー2の右グローブ
[UdonSynced(UdonSyncMode.None)] private int player1currentDamage = 0; //プレイヤー1の現在のダメージを保存する
[UdonSynced(UdonSyncMode.None)] private int player2currentDamage = 0; //プレイヤー2の現在のダメージを保存する
private Vector3 offset = new Vector3(0, 0.7f, 0); // 頭上のオフセット
private float displayDistance = 2.0f;
private float limitTime = 3.0f;
private int lastSecond = 0;
private int currentSecond;
private int playerCount = 0; //プレイヤーの人数
bool InTrigger;
private VRCPlayerApi[] playersTrigger = new VRCPlayerApi[2];
private GameObject[] canvases = new GameObject[2];
private DamageUI DamageUIScript;
private DamageUI2 DamageUI2Script;
void Start()
{
for (int i = 0; i < canvases.Length; i++)
{
if (canvases[i] != null)
{
canvases[i].SetActive(false);
}
}
DamageUIScript = GameObject.Find("DamageUI1").GetComponent<DamageUI>();
}
void Update()
{
UpdateGlobePosition();
danageUIPosition();
for (int i = 0; i < playerCount; i++)
{
if (playersTrigger[i] != null && canvases[i] != null)
{
PositionCanvas(canvases[i], playersTrigger[i]);
}
}
if (playerCount == 2 && limitTime > 0)
{
limitTime -= Time.deltaTime;
currentSecond = Mathf.CeilToInt(limitTime);
if (currentSecond != lastSecond)
{
lastSecond = currentSecond;
foreach (var canvas in canvases)
{
if (canvas != null)
{
TextMeshProUGUI messageText = canvas.GetComponentInChildren<TextMeshProUGUI>();
if (messageText != null)
{
messageText.text = currentSecond.ToString("0");
}
}
}
if (countDownAudioSource != null)
{
countDownAudioSource.PlayOneShot(countDownAudioSource.clip);
}
}
}
}
public override void OnPlayerTriggerEnter(VRCPlayerApi player) //VRCPlayerApi playerは:トリガーに入ったプレイヤーの情報を持つオブジェクト。位置情報や名前などの情報を渡している。
{
//プレハブをオブジェクトとして複製しプレイヤー分生成
if (!IsPlayerTrigger(player))
{
playersTrigger[playerCount] = player; //VRCPlayerApi player変数をplayersInTrigger配列のplayerCount番に格納
GameObject canvasInstance = Instantiate(canvasPrefab); //Instantiate:あるオブジェクトのクローンを生成。位置や回転も指定できる。Instantiate(canvasPrefab original, Vector3 position, Quaternion rotation)
canvases[playerCount] = canvasInstance; //生成したcanvasInstance(canvasPrefabのクローン)をcanvases配列のplayerCount番(初手は0)に格納
PositionCanvas(canvasInstance, player); //生成したcanvasInstance(canvasPrefabのクローン)とVRCPlayerApi player変数を引数として、 PositionCanvasインスタンスに渡す
playerCount++;
}
if (playerCount == 2)
{
InTrigger = true;
DamageUISetOwner(playersTrigger[0], DamageUI1);
DamageUISetOwner(playersTrigger[1], DamageUI2);
GloveSetOwner(playersTrigger[0], FollowObjectTransformLeft, FollowObjectTransformRight);
GloveSetOwner(playersTrigger[1], FollowObjectTransformLeft2, FollowObjectTransformRight2);
}
}
public bool IsPlayerTrigger(VRCPlayerApi player)
{
for (int i = 0; i < playerCount; i++)
{
if (playersTrigger[i] == player) //引数で受け取ったVRCPlayerApi player変数をplayersInTrigger[i]に格納。もし格納出来たらtrueを返す。
{
return true;
}
}
return false;
}
public void PositionCanvas(GameObject canvas, VRCPlayerApi player)
{
player = Networking.LocalPlayer;
if (player != null && canvas != null)
{
Vector3 playerPosition = player.GetTrackingData(VRCPlayerApi.TrackingDataType.Head).position;
Quaternion playerRotation = player.GetTrackingData(VRCPlayerApi.TrackingDataType.Head).rotation;
// Canvasをプレイヤーの目の前に移動
Vector3 forwardPosition = playerRotation * Vector3.forward * displayDistance;
canvas.transform.position = playerPosition + playerRotation * Vector3.forward * displayDistance;
canvas.transform.rotation = playerRotation * Quaternion.Euler(0, 180, 0);
}
}
private void DamageUISetOwner(VRCPlayerApi player, GameObject canvas)
{
Networking.SetOwner(player, canvas);
}
private void GloveSetOwner(VRCPlayerApi player, GameObject glove1, GameObject glove2)
{
Networking.SetOwner(player, glove1);
Networking.SetOwner(player, glove2);
}
public void UpdateGlobePosition()
{
if (!InTrigger) { return; }
VRCPlayerApi[] players = VRCPlayerApi.GetPlayers(new VRCPlayerApi[2]);
if (players.Length >= 1)
{
playersTrigger[0] = players[0];
if (Networking.IsOwner(playersTrigger[0], FollowObjectTransformLeft))
{
FollowObjectTransformLeft.transform.position = Networking.LocalPlayer.GetBonePosition(HumanBodyBones.LeftHand);
FollowObjectTransformLeft.transform.rotation = Networking.LocalPlayer.GetBoneRotation(HumanBodyBones.LeftHand);
}
if (Networking.IsOwner(playersTrigger[0], FollowObjectTransformRight))
{
FollowObjectTransformRight.transform.position = Networking.LocalPlayer.GetBonePosition(HumanBodyBones.RightHand);
FollowObjectTransformRight.transform.rotation = Networking.LocalPlayer.GetBoneRotation(HumanBodyBones.RightHand);
}
}
if (players.Length >= 2)
{
playersTrigger[1] = players[1];
if (Networking.IsOwner(playersTrigger[1], FollowObjectTransformLeft2))
{
FollowObjectTransformLeft2.transform.position = Networking.LocalPlayer.GetBonePosition(HumanBodyBones.LeftHand);
FollowObjectTransformLeft2.transform.rotation = Networking.LocalPlayer.GetBoneRotation(HumanBodyBones.LeftHand);
}
if (Networking.IsOwner(playersTrigger[1], FollowObjectTransformRight2))
{
FollowObjectTransformRight2.transform.position = Networking.LocalPlayer.GetBonePosition(HumanBodyBones.RightHand);
FollowObjectTransformRight2.transform.rotation = Networking.LocalPlayer.GetBoneRotation(HumanBodyBones.RightHand);
}
}
}
public void danageUIPosition()
{
if (!InTrigger) { return; }
VRCPlayerApi[] players = VRCPlayerApi.GetPlayers(new VRCPlayerApi[2]);
if (players.Length >= 1)
{
playersTrigger[0] = players[0];
if (Networking.IsOwner(vrc_player[0], DamageUI1))
{
Vector3 playerPosition = playersTrigger[0].GetTrackingData(VRCPlayerApi.TrackingDataType.Head).position; //Vector3:Positionを決めるための変数の型、プレイヤーの頭の位置を検出しplayerPosition変数に格納
Quaternion playerRotation = playersTrigger[0].GetTrackingData(VRCPlayerApi.TrackingDataType.Head).rotation; //Quaternion:Rotationを決めるための変数の型、プレイヤーの頭の位置を検出しplayerRotation変数に格納
Vector3 forwardPosition = playerRotation * Vector3.forward * displayDistance + offset; // Canvasをプレイヤーの目の前に移動
DamageUI1.transform.position = playerPosition + playerRotation * Vector3.forward * displayDistance;
DamageUI1.transform.position = playerPosition + forwardPosition;
DamageUI1.transform.rotation = Quaternion.LookRotation(DamageUI1.transform.position - playerPosition); //UIがプレイヤーを向くように調整
}
}
if (players.Length >= 2)
{
playersTrigger[1] = players[1];
if (Networking.IsOwner(vrc_player[1], DamageUI2))
{
Vector3 playerPosition = playersTrigger[1].GetTrackingData(VRCPlayerApi.TrackingDataType.Head).position;
Quaternion playerRotation = playersTrigger[1].GetTrackingData(VRCPlayerApi.TrackingDataType.Head).rotation;
Vector3 forwardPosition = playerRotation * Vector3.forward * displayDistance + offset;
DamageUI2.transform.position = playerPosition + playerRotation * Vector3.forward * displayDistance;
DamageUI2.transform.position = playerPosition + forwardPosition;
DamageUI2.transform.rotation = Quaternion.LookRotation(DamageUI2.transform.position - playerPosition);
}
}
}
public void AddDamage(VRCPlayerApi player, int damage)
{
if (player == playersTrigger[0])
{
player1currentDamage += damage;
DamageUIScript.Damage(player1currentDamage);
RequestSerialization();
}
if (player == playersTrigger[1])
{
player2currentDamage += damage;
DamageUI2Script.Damage(player2currentDamage);
RequestSerialization();
}
}
public void startCountDown()
{
foreach (var canvas in canvases)
{
if (canvas != null)
{
TextMeshProUGUI messageText = canvas.GetComponentInChildren<TextMeshProUGUI>();
if (messageText != null)
{
messageText.text = currentSecond.ToString("0");
}
}
}
SendCustomEventDelayedSeconds(nameof(hakkeChangeText), 3.0f);
}
public void hakkeChangeText()
{
foreach (var canvas in canvases) //foreach:配列やlistのコレクションの要素にアクセスする際に使用するとfor文より簡潔に書ける。
{
if (canvas != null)
{
TextMeshProUGUI messageText = canvas.GetComponentInChildren<TextMeshProUGUI>();
if (messageText != null)
{
messageText.text = "八卦よーい...";
}
}
}
SendCustomEventDelayedSeconds(nameof(nokottaChangeText), 2.0f);
}
public void nokottaChangeText()
{
foreach (var canvas in canvases)
{
if (canvas != null)
{
TextMeshProUGUI messageText = canvas.GetComponentInChildren<TextMeshProUGUI>();
{
messageText.text = "のこった!!";
}
}
}
if (nokottaAudioSource != null)
{
nokottaAudioSource.PlayOneShot(nokottaAudioSource.clip);
}
SendCustomEventDelayedSeconds(nameof(HideCanvas), 1.5f);
}
}
ダメージを与えるための手の当たり判定「Glove.cs」
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
public class Glove : UdonSharpBehaviour
{
private float velocityMultiplier = 1.0f; // 吹き飛ばし速度の倍率*/
private bool IsKnockPlayer = true;
private float knockbackForce = 500;
plivate float knockbackForce = 5.0f; // ノックバックの基本強さ
public float currentDamage; // 現在のダメージ蓄積量
public int damage = 5;
float knockbackStrength = 0;
private Rigidbody _RigidBody;
private ParticleSystem _ParticleSystem;
private AudioSource _AudioSource;
private Vector3 PrevPos, Velocity;
private playerStartTrigger playerStartTriggerScript;
private DamageUI DamageUIScript;
private VRCPlayerApi gloveLOwner;
void Start()
{
_RigidBody = GetComponent<Rigidbody>();
_ParticleSystem = GetComponent<ParticleSystem>();
_AudioSource = GetComponent<AudioSource>();
//playerStartTriggerScript = GameObject.Find("startzone").GetComponent<playerStartTrigger>();
DamageUIScript = GameObject.Find("DamageUI1").GetComponent<DamageUI>();
}
private void Update()
{
Velocity = (Velocity + ((transform.position - PrevPos) * Time.deltaTime)) / 2;
PrevPos = transform.position;
//UpdateGlobePosition();
}
public override void OnPlayerTriggerEnter(VRCPlayerApi player)
{
if (player == null) return;
if (Networking.IsOwner(gameObject)) return;
if (Networking.LocalPlayer.playerId == player.playerId)
{
knockbackStrength = knockbackForce + damage;
if (IsKnockPlayer) player.SetVelocity(Velocity * knockbackStrength);
SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, "PlayHitEffect");
//playerStartTriggerScript.AddDamage(player, damage);
DamageUIScript.Damage(damage);
}
}
public void PlayHitEffect()
{
_AudioSource.Play();
_ParticleSystem.Play();
}
public void UpdateOwnerInformation()
{
gloveLOwner = Networking.GetOwner(this.gameObject);
if (Networking.GetOwner(gameObject).playerId != gloveLOwner.playerId)
{
Networking.SetOwner(gloveLOwner, gameObject);
}
}
ダメージを表示するためのキャンバス「DamageUI.cs」
using UdonSharp;
using UnityEngine;
using TMPro;
using VRC.SDKBase;
public class DamageUI : UdonSharpBehaviour
{
[SerializeField] private TextMeshProUGUI damageText; // ダメージ表示用のUI
[UdonSynced(UdonSyncMode.None)] private int damage = 0; // 初期値を設定
private VRCPlayerApi CanvasOwner;
private float displayDistance = 2.0f;
private Vector3 offset = new Vector3(0, 0.7f, 0);
[SerializeField] private TextMeshProUGUI damageText;
[UdonSynced(UdonSyncMode.None)] private int damage = 0;
void Start()
{
CanvasOwner = Networking.LocalPlayer;
UpdateDamageUI();
}
// ダメージを設定する外部呼び出し用メソッド
public void Damage(int Damage)
{
if (Networking.IsOwner(CanvasOwner, gameObject))
{
damage += Damage;
RequestSerialization();
UpdateDamageUI();
}
}
// 内部の UI 更新メソッド
private void UpdateDamageUI()
{
if (damageText != null)
{
damageText.text = damage + "%";
}
}
public override void OnDeserialization()
{
UpdateDamageUI();
}
}
自分で試したこと
・ダメージを表示するキャンバス、グローブにVRC_Object_Syncコンポーネントをつけてみた。
・SetOwnerでのオーナー設定をstartzoneオブジェクト、ダメージキャンバス、グローブそれぞれにつけて試したが駄目だった。
0 likes