- 进入游戏时删除按钮
进入NetworkManager里的NetworkManagerUI类
void Start()
{
hostBtn.onClick.AddListener(() =>
{
...
//点击任意一个按钮后删除
//后面几个函数同理
DestroyAllButtons();
});
...
}
//手动搓一个删除所有按钮的函数
private void DestroyAllButtons() {
//自带API,Destroy(),参数为GameObject
Destroy(hostBtn.gameObject);
Destroy(serverBtn.gameObject);
Destroy(clientBtn.gameObject);
}
- 解决当窗口缩小时按钮被遮挡
进入物体MenuUI下的三个按钮物体
修改锚点为右上方
- 判断射中物体是否是某一类(是否是player)
进入物体Player里
给物体添加标签
进入PlayerShooting里
public class PlayerShooting : NetworkBehaviour
{
...
//将标签名设为常量
private const string PLEYER_TAG = "Player";
...
private void Shoot() {
...
if (Physics.Raycast(cam.transform.position, cam.transform.forward, out hit, weapon.range, mask)) {
//GameObject.ocllider.tag就能访问物体的标签
if (hit.collider.tag == "Player") {
ShootServerRpc(hit.collider.name, weapon.damage);
}
}
}
...
private void ShootServerRpc(string name, int damage) {
}
}
*让服务器通过名字获取player的信息
进入GameManager类
public class GameManager : MonoBehaviour
{
//建立一个名字与Player一一对应的字典序(需要在物体Player里建一个Player类)
private Dictionary<string, Player> players = new Dictionary<string, Player>();
//渲染用户图形界面,OnGUI()每一帧都会自动渲染
private void OnGUI()
{
...
//可汗大点兵,将在花名册的名字打印出来看看
foreach (string name in players.Keys) {
//白色看不见,用GUI的api,GUI.color改一下颜色
GUI.color = Color.red;
GUILayout.Label(name);
}
...
}
}
- GameManager类整理成管理Player登录信息的类
先给GameManager实现一个单例模式
public class GameManager : MonoBehaviour
{
//暂时理解为Singleton为GameManager的唯一实例,
public static GameManager Singleton;
...
//Awake()是个初始化函数,一般用来给变量赋值
//初始化需要把当前的实例赋给Singleton(好像)
private void Awake()
{
Singleton = this;
}
//创建player并登记
public void RegisterPlayer(string name, Player player) {
player.transform.name = name;
players.Add(name, player);
}
//删除player
public void UnRegisterPlayer(string name) {
//可以仅仅通过player的前一个值删除整个player的信息
players.Remove(name);
}
...
}
进入PlayerSetup类里把SetPlayerName()删了,用GameManager里的API来正式的创建player
public class PlayerSetup : NetworkBehaviour
{
//将start()修改为OnNetworkSpawn()
//当当前游戏对象成功链接到网络会调用一次
public override void OnNetworkSpawn()
{
//这个好像不能漏掉
base.OnNetworkSpawn();
...
//记得调用
RegisterPlayer()
//SetPlayerName();
}
...
//把当前player的名字和类抓出来通过GameManager.Singleton来创建player
private void RegisterPlayer() {
string name = "Player" + GetComponent<NetworkObject>().NetworkObjectId.ToString();
Player player = GetComponent<Player>();
//用GameManager的Singleton不用实例GameManager,直接拿Singleton出来用就好了
GameManager.Singleton.RegisterPlayer(name, player);
}
//删了
//private void SetPlayerName() {
// //组件NetworkObject会赋予一个唯一的id,保证在各个窗口的主player不一样并且同一个player的名字又一样
// transform.name = "Player" + GetComponent<NetworkObject>().NetworkObjectId;
//}
...
}
- 在网络断开后删除player
在PlayerSetup类里
public class PlayerSetup : NetworkBehaviour
{
...
public override void OnNetworkDespawn()
{
base.OnNetworkDespawn();
//删除player,此时transform.name已经被附好值了,可以直接调用
GameManager.Singleton.UnRegisterPlayer(transform.name);
//把全局相机打开也放进OnNetworkDespawn()里
if (sceneCamera != null)
{
sceneCamera.gameObject.SetActive(true);
}
}
}
- 给角色设置血量
进入Player类
//继承NetworkBehaviour
public class Player : NetworkBehaviour
{
[SerializeField]
//最大生命值
private int maxHealth = 100;
//需要同步的当前血量,让服务器统一管理
//用到NetworkVariable<>, unity里可以帮助同步所有端口的值的api
private NetworkVariable<int> currentHealth = new NetworkVariable<int>();
//后序的PlayerSetup类可能要调用此函数,设置为public
public void Setup() {
SetDefault();
}
private void SetDefault() {
//只有服务器才能动currentHealth
if (IsServer) {
currentHealth.Value = maxHealth;
}
}
}
- 玩家受伤
在Player类里
public class Player : NetworkBehaviour
{
...
//掉血函数
public void TakeDamage(int damage) {
currentHealth.Value -= damage;
if (currentHealth.Value < 0) {
currentHealth.Value = 0;
}
}
}
去GameManager类里添加一个GetPlayer()
public class GameManager : MonoBehaviour
{
...
public Player GetPlayer(string name) {
return players[name];
}
...
}
进入PlayerShooting类里
public class PlayerShooting : NetworkBehaviour
{
...
private void Shoot() {
...
if (Physics.Raycast(cam.transform.position, cam.transform.forward, out hit, weapon.range, mask)) {
if (hit.collider.tag == "Player") {
//射线碰撞在客户端,而让受伤的逻辑在服务器上实现
ShootServerRpc(hit.collider.name, weapon.damage);
}
}
}
[ServerRpc]
private void ShootServerRpc(string name, int damage) {
Player player = GameManager.Singleton.GetPlayer(name);
player.TakeDamage(damage);
}
}
- 显示出玩家血量
在Player类先写一个GetHealth()
public class Player : NetworkBehaviour
{
...
//返回血量函数
public int GetHealth() {
return currentHealth.Value;
}
}
进入PlayerSetup类里
在player进入网络时初始化一下血量出来
public override void OnNetworkSpawn()
{
...
//方便要用player,把RegisterPlayer()删了,内容写道初始化这里
string name = "Player" + GetComponent<NetworkObject>().NetworkObjectId.ToString();
Player player = GetComponent<Player>();
//创建时给player附上血量
player.Setup();
GameManager.Singleton.RegisterPlayer(name, player);
}
最后在GameManager类里的OnGUI()里画出来
private void OnGUI()
{
...
//显示血量
foreach (string name in players.Keys) {
Player player = GetPlayer(name);
GUILayout.Label(name + "-" + player.GetHealth());
}
...
}
- 制作准星
先创造一个Images文件夹
把下载好的图片放进去
在物体Player里的物体Camera创建一个画面canvas,取名为UI
再在UI里创建为Raw image的UI,取名为CrossHair(准星)
在物体CrossHair把准星图片塞进去
并在里面调整大小
- 创建Die()
在Player类里
public class Player : NetworkBehaviour
{
...
//创建死亡状态的标记变量
private NetworkVariable<bool> isDied = new NetworkVariable<bool>();
...
private void SetDefault() {
if (IsServer) {
...
isDied.Value = false;
}
}
...
//广播给客户端的Die()
//ClientRpc,不会再服务端执行,所以要多写一个DieOnServer()
[ClientRpc]
private void DieClientRpc()
{
Die();
}
//让服务器窗口的这个Player执行Die()
private void DieOnServer()
{
Die();
}
//具体的死亡逻辑
private void Die()
{
}
...
}
- 实现角色死亡缴械行动
假设player2死后,所有窗口上的player2的“死亡部件”.enabled该被附为false
但player2重生时却不能简单的直接将“死亡部件”.enabled置成true
所以要用创一个bool[] componentsEnabled,来记录当前窗口player2的“死亡部件”原本的状态
如果此时在客户端1,那么player2的“死亡部件”一直都是false;
collider状态单独判断,因为player2 复活后在所有的窗口colliderEnabled都应该是true;
在Player类里
public class Player : NetworkBehaviour
{
...
//死亡后准备缴械的部件
[SerializeField]
private Behaviour[] componentsToDisable;
//记录在当前的窗口的这个Player原本部件的状态
private bool[] componentsEnabled;
//碰撞体的判断单独列出来,因为collider没有继承Behaviour
private bool colliderEnabled;
...
//后序的PlayerSetup类可能要调用此函数,设置为public
public void Setup() {
//给componentsEnabled开空间
componentsEnabled = new bool[componentsToDisable.Length];
for (int i = 0; i < componentsToDisable.Length; i++) {
componentsEnabled[i] = componentsToDisable[i].enabled;
}
Collider col = GetComponent<Collider>();
colliderEnabled = col.enabled;
SetDefault();
}
...
//具体的死亡逻辑,缴械
private void Die()
{
for (int i = 0; i < componentsToDisable.Length; i++) {
componentsToDisable[i].enabled = false;
}
Collider col = GetComponent<Collider>();
col.enabled = false;
}
...
}
- 重生
进入Player类
public class Player : NetworkBehaviour
{
...
private void SetDefault() {
//缴械的逆操作
for (int i = 0; i < componentsToDisable.Length; i++)
{
componentsToDisable[i].enabled = componentsEnabled[i];
}
Collider col = GetComponent<Collider>();
col.enabled = colliderEnabled;
...
}
...
//带有延时的重生函数
private IEnumerator Respawn() {
yield return new WaitForSeconds(3f);//单位秒
SetDefault();
//飞起来!
//只有本窗口的player才能飞起来,其他窗口被广播位置就好了
if (IsLocalPlayer)
{
transform.position = new Vector3(0f, 10f, 0f);
}
}
...
//具体的死亡逻辑,缴械
private void Die()
{
...
//另开一个线程复活,不知道什么意思
StartCoroutine(Respawn());
}
//掉血函数
public void TakeDamage(int damage) {
//死了就不能受击,刷新复活时间
if (isDied.Value == true) return;
...
}
...
}