用Unet改造Tanks! Tutorial。
设计
第一步是在项目中创建 NetworkManager 对象,Game Object -> Create Empty,添加NetworkManager组件来管理游戏的网络状态,添加NetworkManagerHUD组件得到了控制网络状态的用户界面。
给坦克预制加入相机作为子对象,并先在组件面板将相机对象的勾选取消使其失效。
在坦克预制加上NetworkIdentity组件,用于标识服务器和客户端之间的对象,将 NetworkIdentity 上的 “Local Player Authority” 复选框设置为 true允许客户端控制玩家对象的移动,加上NetworkTransform组件使对象在网络中同步它的位置。
在NetworkManager的NetworkManager组件面板打开 “Spawn Info” 折叠找到 “Player Prefab” 插槽,将坦克预制件拖入。
给坦克移动代码加上using UnityEngine.Networking;
,将MonoBehaviour
更改为NetworkBehaviour
,在Update()
中加入
if (!isLocalPlayer)
return;
以便只有本地程序处理输入。添加
public override void OnStartLocalPlayer()
{
// Get all of the renderers of the tank.
MeshRenderer[] renderers = GetComponentsInChildren<MeshRenderer> ();
// Go through all the renderers...
for (int i = 0; i < renderers.Length; i++)
{
// ... set their material color to the color specific to this tank.
renderers[i].material.color = Color.red;
}
playerCamera.SetActive (true);
}
识别玩家使本地玩家变红并使玩家自己的相机生效。
给子弹预制加上NetworkIdentity组件和NetworkTransform组件,将NetworkTransform组件将发送速率设置为零。子弹在射击后不会改变方向或速度,因此不需要发送移动更新。
在NetworkManager的NetworkManager组件面板打开 “Spawn Info” 折叠用 Add 按钮添加一个新的spawn预制件将子弹预制拖入。
改写坦克射击代码。
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
namespace Complete
{
public class TankShooting : NetworkBehaviour
{
...
public GameObject m_Shell; // Prefab of the shell.
...
private void Update ()
{
if (!isLocalPlayer)
return;
// The slider should have a default value of the minimum launch force.
m_AimSlider.value = m_MinLaunchForce;
// If the max force has been exceeded and the shell hasn't yet been launched...
if (m_CurrentLaunchForce >= m_MaxLaunchForce && !m_Fired)
{
// ... use the max force and launch the shell.
m_CurrentLaunchForce = m_MaxLaunchForce;
CmdFire ();
}
// Otherwise, if the fire button has just started being pressed...
else if (Input.GetButtonDown (m_FireButton))
{
// ... reset the fired flag and reset the launch force.
m_Fired = false;
m_CurrentLaunchForce = m_MinLaunchForce;
// Change the clip to the charging clip and start it playing.
m_ShootingAudio.clip = m_ChargingClip;
m_ShootingAudio.Play ();
}
// Otherwise, if the fire button is being held and the shell hasn't been launched yet...
else if (Input.GetButton (m_FireButton) && !m_Fired)
{
// Increment the launch force and update the slider.
m_CurrentLaunchForce += m_ChargeSpeed * Time.deltaTime;
m_AimSlider.value = m_CurrentLaunchForce;
}
// Otherwise, if the fire button is released and the shell hasn't been launched yet...
else if (Input.GetButtonUp (m_FireButton) && !m_Fired)
{
// ... launch the shell.
CmdFire ();
}
}
[Command]
private void CmdFire ()
{
// Set the fired flag so only Fire is only called once.
m_Fired = true;
// Create an instance of the shell and store a reference to it's rigidbody.
GameObject shellInstance =
Instantiate (m_Shell, m_FireTransform.position, m_FireTransform.rotation);
// Set the shell's velocity to the launch force in the fire position's forward direction.
shellInstance.GetComponent<Rigidbody>().velocity = m_CurrentLaunchForce * m_FireTransform.forward;
// Spawn the bullet on the clients.
NetworkServer.Spawn (shellInstance);
// When the bullet is destroyed on the server it will automaticaly be destroyed on clients.
Destroy(shellInstance, 2.0f);
// Change the clip to the firing clip and play it.
m_ShootingAudio.clip = m_FireClip;
m_ShootingAudio.Play ();
// Reset the launch force. This is a precaution in case of missing button events.
m_CurrentLaunchForce = m_MinLaunchForce;
}
}
}
改写坦克健康代码。
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
namespace Complete
{
public class TankHealth : NetworkBehaviour
{
...
[SyncVar]
private float m_CurrentHealth; // How much health the tank currently has.
public void TakeDamage (float amount)
{
if (!isServer)
return;
...
if (m_CurrentHealth <= 0f && !m_Dead)
{
RpcOnDeath ();
}
}
private void Update ()
{
// Set the slider's value appropriately.
m_Slider.value = m_CurrentHealth;
// Interpolate the color of the bar between the choosen colours based on the current percentage of the starting health.
m_FillImage.color = Color.Lerp (m_ZeroHealthColor, m_FullHealthColor, m_CurrentHealth / m_StartingHealth);
}
[ClientRpc]
private void RpcOnDeath ()
{
// Set the flag so that this function is only called once.
m_Dead = true;
...
Destroy(gameObject);
}
}
}
相关代码
https://github.com/Ernie1/unity3d/tree/hw10/hw10/Assets/_Completed-Assets/Scripts/Tank
要点
- [Command] :本地玩家对象执行的代码,调用服务器执行的函数
- [ClientRpc] :服务器执行的代码,调用所有客户端执行函数
- [SyncVar]:服务器授权变量,Spwan 自动同步到客户端