1. Netcode for Gameobjects のインストール
Netcode for Gameobjects は、Unity が立ち上げた新しいオンライン ソリューションです. このソリューションは現在初期段階にあり、関連する機能は完璧ではありませんが、使用することは悪くありません. 関連する使い方を紹介します。
最初に Netcode for Gameobjects をダウンロードしてインストールします. 公式ウェブサイトはNetcode for Gameobjectsです. インストール方法も非常に簡単です. 新しい Unity プロジェクトを作成し、メニューバーで Window-Package Manager を開き, プラス記号をクリックして git から Package を追加します. URL、以下のアドレスを入力し、追加をクリックして待つ インストールは完了です。
com.unity.netcode.gameobjects
エラーが発生する場合は、まずバージョンの問題であり、別のバージョンの Unity に置き換える必要があります。
2. NetworkManager コンポーネント
新しい空のオブジェクトを作成し、NetworkManager という名前を付け、NetworkManager コンポーネントを追加します.
同時に、Select transport ドロップダウン オプションをクリックし、Unity Transport を選択します.
NetworkManager コンポーネントには、いくつかの重要なパラメーターがあります。
Player Prefab: プレーヤー プレハブ
Network Prefab: ネットワーク プレハブTickRate: データのコンパイルと送信の頻度。通常、デフォルトは 30 で、送信間隔はUnityTransport コンポーネントで
1/30 秒です。
アドレス
: IP アドレス
ポート: ネットワーク ポート番号
サーバー リッスン アドレス: サーバー リッスン アドレス
3. ログイン インターフェイスを作成する
Unity で次のインターフェイスを作成します。
EventSystem コンポーネントが欠落していないことに注意してください。
サーバーの作成: クライアントとしてではなく、サーバーとしてのみ使用されるゲーム サーバーを作成します.
サーバーに参加: 既存のサーバーにクライアントとして参加します.
入力ボックス: 参加する IP アドレスを入力します
4. シーンを作成し、
プレーヤーを作成します.シーンでの役割 地面といくつかの正方形だけ
Unity リソース ストアで、サードパーソンの無料リソース パッケージをインポートします
Assets フォルダーで次のリソース パッケージを見つけてシーンにドラッグし、2 つのプレハブの親オブジェクト Player を追加し、
2 つのプレハブのプレハブ関連付けを同時にキャンセルします。
Player 用のネットワーク コンポーネント NetworkObject を追加します。
Player を Prefabs フォルダーにドラッグしてプレハブを作成し、シーン内の Player を削除します。
プレハブ Player を NetworkManager の player プレハブにドラッグします。
オンライン接続をより面白くするために、弾丸を発射してプレイヤーの血を減らす効果を作成するため、下図に示すように、プレイヤーの頭の上に 3 次元の UI キャンバスを追加します。弾丸を発射するには、
弾丸が生成されてオブジェクトの方向に発射されるときに空のオブジェクトを 2 つ追加し、プレハブを保存します。
弾丸として球体を作成し、次のコンポーネントを球体に追加します。Bullet は作成された新しいスクリプトです。
MainCamera を削除し、新しいカメラを作成し、シーンで適切な視野角に調整してから、[カメラ] を選択し、[ビューに合わせる] をクリックして視野角を合わせます。カメラはサーバー側のカメラとして使用され、クライアント側のカメラの場合、カメラは非表示になります。
5. サーバーを作成してサーバーに参加する
最初にスクリプト NetMain を作成し、それを NetworkManager の下に吊るします。メイン コードは次のとおりです。
using System.Collections;
using System.Collections.Generic;
using Unity.Netcode;
using Unity.Netcode.Transports.UTP;
using UnityEngine;
using UnityEngine.UI;
public class NetMain : MonoBehaviour
{
/// <summary>
/// 单例
/// </summary>
public static NetMain instance;
/// <summary>
/// 创建服务器按钮
/// </summary>
public Button createServerBtn;
/// <summary>
/// 加入服务器按钮
/// </summary>
public Button joinServerBtn;
/// <summary>
/// 输入IP地址框
/// </summary>
public InputField ipinput;
//玩家生成点
public Transform playerSpawnPos;
//默认相机
public GameObject _camera;
/// <summary>
/// 测试面板
/// </summary>
public GameObject testPanel;
/// <summary>
/// 本地玩家
/// </summary>
public NetPlayer localPlayer;
private void Awake()
{
instance = this;
}
void Start()
{
//绑定按钮事件
createServerBtn.onClick.AddListener(CreateServerBtnClick);
joinServerBtn.onClick.AddListener(JoinServerBtnClick);
}
// Update is called once per frame
void Update()
{
}
/// <summary>
/// 创建服务器按钮绑定事件
/// </summary>
private void CreateServerBtnClick()
{
//获取Unity传输组件
UnityTransport unityTransport = NetworkManager.Singleton.GetComponent<UnityTransport>();
//设置ip地址和端口,0.0.0.0代表任意ip地址
unityTransport.SetConnectionData("0.0.0.0", 7777);
//启动服务器
NetworkManager.Singleton.StartServer();
//隐藏UI界面
createServerBtn.transform.parent.gameObject.SetActive(false);
testPanel.SetActive(true);
}
/// <summary>
/// 加入服务器按钮点击事件
/// </summary>
private void JoinServerBtnClick()
{
//获取输入的ip地址
string ip = ipinput.text;
//判断ip地址是否为空
if (ipinput.text.Equals(""))
{
//如果为空,默认ip地址为127.0.0.1
ip = "127.0.0.1";
print("未输入IP地址,使用默认地址");
}
//获取Unity传输组件
UnityTransport unityTransport = NetworkManager.Singleton.GetComponent<UnityTransport>();
//设置要连接的ip地址和端口
unityTransport.SetConnectionData(ip, 7777);
//启动连接到服务器,以客户端的身份
NetworkManager.Singleton.StartClient();
//隐藏UI界面
joinServerBtn.transform.parent.gameObject.SetActive(false);
//关闭消息调试面板
testPanel.SetActive(true);
}
}
6. プレーヤーと発射された弾丸を同期させる
新しい NetPlayerSync スクリプトを作成し、サードパーソン コントローラーのプレーヤーにハングさせます。メイン コードは次のとおりです。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;
using UnityEngine.UI;
using Invector.vCharacterController;
public class NetPlayerSync : NetworkBehaviour
{
/// <summary>
/// 动画组件
/// </summary>
private Animator _animator;
/// <summary>
/// 需要进行服务端更新的参数,包含发射和接收
/// </summary>
NetworkVariable<float> _SyncInputHorizontal = new NetworkVariable<float>();
NetworkVariable<float> _SyncInputVertical = new NetworkVariable<float>();
NetworkVariable<float> _SyncInputMagnitude = new NetworkVariable<float>();
NetworkVariable<bool> _SyncIsGrounded = new NetworkVariable<bool>();
NetworkVariable<bool> _SyncIsStrafing = new NetworkVariable<bool>();
NetworkVariable<bool> _SyncIsSprinting = new NetworkVariable<bool>();
NetworkVariable<float> _SyncGroundDistance = new NetworkVariable<float>();
NetworkVariable<Vector3> _SyncPosition = new NetworkVariable<Vector3>();
NetworkVariable<Quaternion> _SyncRotation = new NetworkVariable<Quaternion>();
NetworkVariable<float> _SyncHealth = new NetworkVariable<float>();
#region Variables
[Header("Controller Input")]
public string horizontalInput = "Horizontal";
public string verticallInput = "Vertical";
public KeyCode jumpInput = KeyCode.Space;
public KeyCode strafeInput = KeyCode.Tab;
public KeyCode sprintInput = KeyCode.LeftShift;
[Header("Camera Input")]
public string rotateCameraXInput = "Mouse X";
public string rotateCameraYInput = "Mouse Y";
[HideInInspector] public vThirdPersonController cc;
[HideInInspector] public vThirdPersonCamera tpCamera;
[HideInInspector] public Camera cameraMain;
#endregion
public Image fillImage;
public float currentHealth;
public float maxHealth = 100;
public NetPlayer mPlayer;
private void Start()
{
_animator = GetComponent<Animator>();
maxHealth = 100;
currentHealth = maxHealth;
mPlayer.playerSync = this;
InitilizeController();
InitializeTpCamera();
}
private void FixedUpdate()
{
if (IsLocalPlayer)
{
cc.UpdateMotor(); // updates the ThirdPersonMotor methods
cc.ControlLocomotionType(); // handle the controller locomotion type and movespeed
cc.ControlRotationType(); // handle the controller rotation type
}
}
private void Update()
{
if (IsLocalPlayer)
{
InputHandle(); // update the input methods
cc.UpdateAnimator(); // updates the Animator Parameters
UpdateAnimator();
SetHealthToServerRpc(currentHealth);
}
}
private void LateUpdate()
{
if (!IsLocalPlayer)
{
SyncInput();
UpdateOtherPlayerHealth();
}
UpdateHealthImage();
}
public virtual void OnAnimatorMove()
{
if (cc!=null)
{
cc.ControlAnimatorRootMotion(); // handle root motion animations
}
}
private void UpdateAnimator()
{
float h = _animator.GetFloat("InputHorizontal");
float v = _animator.GetFloat("InputVertical");
float m = _animator.GetFloat("InputMagnitude");
bool ground = _animator.GetBool("IsGrounded");
bool straf = _animator.GetBool("IsStrafing");
bool sprint = _animator.GetBool("IsSprinting");
float grounddistance = _animator.GetFloat("GroundDistance");
//上面先获取到当前参数,然后发给服务端,服务端再发给各个客户端进行同步
UpdatePlayerAnimatorServerRpc(h, v, m, ground, straf, sprint, grounddistance);
UpdatePlayerMovementServerRpc(transform.position, transform.rotation);
}
[ServerRpc]
private void UpdatePlayerAnimatorServerRpc(float h, float v, float m, bool ground, bool straf, bool sprint, float grounddistance)
{
_SyncInputHorizontal.Value = h;
_SyncInputVertical.Value = v;
_SyncInputMagnitude.Value = m;
_SyncIsGrounded.Value = ground;
_SyncIsStrafing.Value = straf;
_SyncIsSprinting.Value = sprint;
_SyncGroundDistance.Value = grounddistance;
}
[ServerRpc]
private void UpdatePlayerMovementServerRpc(Vector3 pos, Quaternion rot)
{
_SyncPosition.Value = pos;
_SyncRotation.Value = rot;
}
[ServerRpc]
private void SetHealthToServerRpc(float health)
{
_SyncHealth.Value = health;
}
/// <summary>
/// 如果不是本地玩家,只需要接收服务端更新的数据
/// </summary>
private void SyncInput()
{
_animator.SetFloat("InputHorizontal", _SyncInputHorizontal.Value);
_animator.SetFloat("InputVertical", _SyncInputVertical.Value);
_animator.SetFloat("InputMagnitude", _SyncInputMagnitude.Value);
_animator.SetBool("IsGrounded", _SyncIsGrounded.Value);
_animator.SetBool("IsStrafing", _SyncIsStrafing.Value);
_animator.SetBool("IsSprinting", _SyncIsSprinting.Value);
_animator.SetFloat("GroundDistance", _SyncGroundDistance.Value);
transform.position = _SyncPosition.Value;
transform.rotation = _SyncRotation.Value;
}
private void UpdateOtherPlayerHealth()
{
currentHealth = _SyncHealth.Value;
}
private void UpdateHealthImage()
{
fillImage.fillAmount = currentHealth / maxHealth;
}
#region Basic Locomotion Inputs
protected virtual void InitilizeController()
{
cc = GetComponent<vThirdPersonController>();
if (cc != null)
cc.Init();
}
protected virtual void InitializeTpCamera()
{
if (tpCamera == null)
{
tpCamera = FindObjectOfType<vThirdPersonCamera>();
if (tpCamera == null)
return;
if (tpCamera)
{
tpCamera.SetMainTarget(this.transform);
tpCamera.Init();
}
}
}
protected virtual void InputHandle()
{
MoveInput();
CameraInput();
SprintInput();
StrafeInput();
JumpInput();
}
public virtual void MoveInput()
{
cc.input.x = Input.GetAxis(horizontalInput);
cc.input.z = Input.GetAxis(verticallInput);
}
protected virtual void CameraInput()
{
if (!cameraMain)
{
if (!Camera.main) Debug.Log("Missing a Camera with the tag MainCamera, please add one.");
else
{
cameraMain = Camera.main;
cc.rotateTarget = cameraMain.transform;
}
}
if (cameraMain)
{
cc.UpdateMoveDirection(cameraMain.transform);
}
if (tpCamera == null)
return;
var Y = Input.GetAxis(rotateCameraYInput);
var X = Input.GetAxis(rotateCameraXInput);
tpCamera.RotateCamera(X, Y);
}
protected virtual void StrafeInput()
{
if (Input.GetKeyDown(strafeInput))
cc.Strafe();
}
protected virtual void SprintInput()
{
if (Input.GetKeyDown(sprintInput))
cc.Sprint(true);
else if (Input.GetKeyUp(sprintInput))
cc.Sprint(false);
}
/// <summary>
/// Conditions to trigger the Jump animation & behavior
/// </summary>
/// <returns></returns>
protected virtual bool JumpConditions()
{
return cc.isGrounded && cc.GroundAngle() < cc.slopeLimit && !cc.isJumping && !cc.stopMove;
}
/// <summary>
/// Input to trigger the Jump
/// </summary>
protected virtual void JumpInput()
{
if (Input.GetKeyDown(jumpInput) && JumpConditions())
cc.Jump();
}
#endregion
}
新しい NetPlayer スクリプトを作成し、それを Player プレハブに掛けます。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode;
using Invector.vCharacterController;
using Invector.Utils;
using UnityEngine.UI;
public class NetPlayer : NetworkBehaviour
{
/// <summary>
/// 子物体玩家
/// </summary>
public GameObject _player;
/// <summary>
/// 子物体第三人称相机
/// </summary>
public GameObject _camera;
//子弹出生点或者说生成点
public Transform spawnPoint;
//子弹发射方向
public Transform endpoint;
//子弹预制体
public GameObject bullet;
//玩家同步脚本
public NetPlayerSync playerSync;
/// <summary>
/// 当玩家生成时
/// </summary>
public override void OnNetworkSpawn()
{
//显示调试信息
NetMain.instance.testPanel.GetComponentInChildren<Text>().text += OwnerClientId;
//设置玩家名字
gameObject.name = OwnerClientId.ToString();
//如果是本地玩家
if (IsLocalPlayer)
{
//初始化参数
NetMain.instance.localPlayer = this;
NetMain.instance._camera.SetActive(false);
transform.position = NetMain.instance.playerSpawnPos.position;
}
//不是本地玩家
if (!IsLocalPlayer)
{
//删除部分组件
if (_player.GetComponent<vThirdPersonController>()!=null)
{
Destroy(_player.GetComponent<vThirdPersonController>());
}
if (_player.GetComponent<vComment>()!=null)
{
Destroy(_player.GetComponent<vComment>());
}
if (_player.GetComponent<vThirdPersonInput>()!=null)
{
Destroy(_player.GetComponent<vThirdPersonInput>());
}
if (_camera!=null)
{
Destroy(_camera);
}
}
}
private void Update()
{
//如果是本地玩家,才可以进行发射子弹,其他玩家收到服务器控制,不接受本地的控制
if (IsLocalPlayer)
{
TestAttack();
}
}
private void TestAttack()
{
if (Input.GetKeyDown(KeyCode.F))
{
SpawnBulletServerRpc();
}
}
/// <summary>
/// 客户端向服务器发射消息
/// </summary>
[ServerRpc]
private void SpawnBulletServerRpc()
{
SpawnBulletToClientRpc();
}
/// <summary>
/// 服务端向客户端发射消息
/// </summary>
[ClientRpc]
private void SpawnBulletToClientRpc()
{
if (IsLocalPlayer)
{
GameObject go = NetworkManager.Instantiate(bullet, spawnPoint.position, spawnPoint.rotation);
go.GetComponent<Bullet>().dir = endpoint.position - spawnPoint.position;
}
else
{
GameObject go = NetworkManager.Instantiate(bullet, spawnPoint.position, spawnPoint.rotation);
go.GetComponent<Bullet>().dir = endpoint.position - spawnPoint.position;
}
}
/// <summary>
/// 本地测试发射子弹
/// </summary>
private void LocalSpawnBullet()
{
GameObject go = NetworkManager.Instantiate(bullet, spawnPoint.position, spawnPoint.rotation);
go.GetComponent<Bullet>().dir = endpoint.position - spawnPoint.position;
}
/// <summary>
/// 向服务端发射消息:更新被射中的玩家的血量
/// </summary>
/// <param name="id"></param>
[ServerRpc]
public void SendHealthInfoToServerRpc(ulong id)
{
SetHealthChangeToClientRpc(id);
}
/// <summary>
/// 通知所有玩家,判断是否为射中的玩家,如果是减血量
/// </summary>
/// <param name="id"></param>
[ClientRpc]
public void SetHealthChangeToClientRpc(ulong id)
{
if (id == NetMain.instance.localPlayer.OwnerClientId)
{
NetMain.instance.testPanel.GetComponentInChildren<Text>().text += "\n当前的id为" + id;
NetMain.instance.localPlayer.playerSync.currentHealth -= 1;
}
}
}
新しい HealthImage スクリプトを作成し、プレーヤーのヘルス バーに掛けて、ヘルス バーが常にカメラに向くようにします。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HealthImage : MonoBehaviour
{
//朝向相机
private Transform Camtarget;
private void Update()
{
//不断查找所有的摄像机,判断哪个是已启用的,把已启用的摄像机设置为目标摄像机
GameObject[] gos = GameObject.FindGameObjectsWithTag("MainCamera");
for (int i = 0; i < gos.Length; i++)
{
if (gos[i].activeInHierarchy)
{
Camtarget = gos[i].transform;
break;
}
}
if (Camtarget!=null)
{
transform.rotation = Camtarget.rotation;
}
}
}
箇条書きコード:
using System.Collections;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.UI;
public class Bullet : NetworkBehaviour
{
public Vector3 dir;
Rigidbody rigid;
private void Start()
{
rigid = GetComponent<Rigidbody>();
Destroy(gameObject, 5);
}
private void FixedUpdate()
{
rigid.velocity = dir * Time.deltaTime * 5000;
}
private void OnCollisionEnter(Collision collision)
{
if (collision.collider.CompareTag("Player"))
{
NetPlayerSync mps = collision.collider.GetComponent<NetPlayerSync>();
if (!mps.mPlayer.IsLocalPlayer)
{
print(mps.OwnerClientId);
NetMain.instance.localPlayer.SendHealthInfoToServerRpc(mps.OwnerClientId);
Destroy(gameObject);
}
}
}
}
7. テストとまとめ
コードの完成後に手動でドラッグする必要があるゲーム オブジェクト:
効果は次のとおりです:
プロジェクト ソース ファイル、使用されるバージョンは 2020.3.28
リンク: https://pan.baidu.com/s/ 1d8zuQzzekIefJGgQvJKfXw
抽出コード:yrpc