• Unity之PUN2插件实现多人联机射击游戏


    目录

    📖一、准备工作

    📺二、UI界面处理 

    📱2.1 登录UI并连接PUN2服务器

    📱2.2 游戏大厅界面UI

    📱2.3 创建房间UI

    📱2.4 进入房间UI 

    📱2.5 玩家准备状态

    📱2.6 加载战斗场景

    📱2.7 死亡UI界面和复活按钮

    🎮三、角色控制器

    💣3.1 生成角色

    💣3.2 角色控制和战斗系统

    💣3.3 枪的脚本

    ⚒️四、项目打包导出 


    前两天我突发奇想想做联机游戏,就去找教程,肝了一天终于做出来了。


    做的这个实例是通过PUN2实现的,看一下效果:


    先说一下搜寻资料过程中找到的实现游戏联机的方式:暂时就记录了这11个。

    1. Unity自带的UNET(Unity Networking)
    2. PUN(Photon Unity Networking)
    3. Mirror:Mirror是UNET的现代替代品
    4. 自定义网络解决方案
    5. Socket编程:系统级的API,通过调用这些API就可以实现网络通讯
    6. WebSocket:是一种在单个TCP连接上进行双工通信的协议,可用于实现多人联机游戏的数据传输和实时通信。
    7. MirrorLite:MirrorLite是Mirror的轻量级版本
    8. 树莓派及LAN连接
    9. UnityMultiplayer
    10. 自建基于TCP/IP的服务器
    11. WebRTC

    视频教程是小破站Up主:独立开发者C酱,个人感觉这套模型和这个教程泰裤辣,能跟着做完这个游戏Demo也是很开心的,下面依然以博客的形式记录实现这个游戏的过程。

    一、准备工作

    首先新建一个U3D项目导入素材包。

    https://download.csdn.net/download/qq_48512649/88858525icon-default.png?t=N7T8https://download.csdn.net/download/qq_48512649/88858525去Unity官方资源商店下载PUN2插件导入到项目中

    要去PUN2官网申请PUN2账号获取AppID,获取AppID教程参考下面这篇文章:不同的是Photon Type要改为PUN

    PUN-注册账号以及创建应用(1)_photon 注册不了-CSDN博客文章浏览阅读898次。PUN注册账号及创建应用_photon 注册不了https://blog.csdn.net/weixin_38484443/article/details/125629797

    photon pun2 设置中国区_photon中国区-CSDN博客文章浏览阅读1.9k次,点赞2次,收藏14次。pun2 中国区设置_photon中国区https://blog.csdn.net/qq_37350725/article/details/124657623?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522170865675116800225534042%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=170865675116800225534042&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~baidu_landing_v2~default-1-124657623-null-null.142%5Ev99%5Epc_search_result_base3&utm_term=PUN2%E8%AE%BE%E7%BD%AE%E6%88%90%E4%B8%AD%E5%9B%BD%E5%8C%BA&spm=1018.2226.3001.4187      哎,小编的国区申请还没回应,目前我只测试了在局域网内的联机。

    生成AppID后把它复制到插件中去

    粘贴生成好的AppID: 

    二、UI界面处理 

    2.1 登录UI并连接PUN2服务器

    玩家登录场景是login,战斗场景是game

    双击登录场景,编写Game脚本挂载到Game上

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Photon.Pun;
    5. public class Game : MonoBehaviour
    6. {
    7. public static UIManager uiManager;
    8. public static bool isLoaded = false;
    9. private void Awake()
    10. {
    11. if (isLoaded == true)
    12. {
    13. Destroy(gameObject);
    14. }
    15. else
    16. {
    17. isLoaded = true;
    18. DontDestroyOnLoad(gameObject); //跳转场景当前游戏物体不删除
    19. uiManager = new UIManager();
    20. uiManager.Init();
    21. //设置发送 接收消息频率 降低延迟
    22. PhotonNetwork.SendRate = 50;
    23. PhotonNetwork.SerializationRate = 50;
    24. }
    25. }
    26. // Start is called before the first frame update
    27. void Start()
    28. {
    29. //显示登录界面
    30. uiManager.ShowUI("LoginUI");
    31. }
    32. // Update is called once per frame
    33. void Update()
    34. {
    35. }
    36. }

     LoginUI脚本,对开始游戏退出游戏按键的处理

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. using Photon.Pun;
    6. using Photon.Realtime;
    7. //登录界面
    8. public class LoginUI : MonoBehaviour,IConnectionCallbacks
    9. {
    10. // Start is called before the first frame update
    11. void Start()
    12. {
    13. transform.Find("startBtn").GetComponent
    14. transform.Find("quitBtn").GetComponent
    15. }
    16. private void OnEnable()
    17. {
    18. PhotonNetwork.AddCallbackTarget(this); //注册pun2事件
    19. }
    20. private void OnDisable()
    21. {
    22. PhotonNetwork.RemoveCallbackTarget(this); //注销pun2事件
    23. }
    24. public void onStartBtn()
    25. {
    26. Game.uiManager.ShowUI("MaskUI").ShowMsg("正在连接服务器...");
    27. //连接pun2服务器
    28. PhotonNetwork.ConnectUsingSettings(); //成功后会执行OnConnectedToMaster函数
    29. }
    30. public void onQuitBtn()
    31. {
    32. Application.Quit();
    33. }
    34. public void OnConnected()
    35. {
    36. }
    37. //连接成功后执行的函数
    38. public void OnConnectedToMaster()
    39. {
    40. //关闭所有界面
    41. Game.uiManager.CloseAllUI();
    42. Debug.Log("连接成功");
    43. //显示大厅界面
    44. Game.uiManager.ShowUI("LobbyUI");
    45. }
    46. //断开服务器执行的函数
    47. public void OnDisconnected(DisconnectCause cause)
    48. {
    49. Game.uiManager.CloseUI("MaskUI");
    50. }
    51. public void OnRegionListReceived(RegionHandler regionHandler)
    52. {
    53. }
    54. public void OnCustomAuthenticationResponse(Dictionary<string, object> data)
    55. {
    56. }
    57. public void OnCustomAuthenticationFailed(string debugMessage)
    58. {
    59. }
    60. }

    MaskUI脚本提供遮罩界面文字显示的公共调用方法

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. //遮罩界面
    6. public class MaskUI : MonoBehaviour
    7. {
    8. // Start is called before the first frame update
    9. void Start()
    10. {
    11. }
    12. public void ShowMsg(string msg)
    13. {
    14. transform.Find("msg/bg/Text").GetComponent().text = msg;
    15. }
    16. }

    连接服务器成功可以看到控制台打印并输出

    2.2 游戏大厅界面UI

    编写脚本  LobbyUI  处理游戏大厅界面

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Photon.Pun;
    5. using Photon.Realtime;
    6. using UnityEngine.UI;
    7. //大厅界面
    8. public class LobbyUI : MonoBehaviourPunCallbacks
    9. {
    10. private TypedLobby lobby; //大厅对象
    11. private Transform contentTf;
    12. private GameObject roomPrefab;
    13. void Start()
    14. {
    15. //关闭按钮
    16. transform.Find("content/title/closeBtn").GetComponent
    17. //创建房间按钮
    18. transform.Find("content/createBtn").GetComponent
    19. //刷新按钮
    20. transform.Find("content/updateBtn").GetComponent
    21. contentTf = transform.Find("content/Scroll View/Viewport/Content");
    22. roomPrefab = transform.Find("content/Scroll View/Viewport/item").gameObject;
    23. lobby = new TypedLobby("fpsLobby", LobbyType.SqlLobby); //1.大厅名字 2.大厅类型(可搜索)
    24. //进入大厅
    25. PhotonNetwork.JoinLobby(lobby);
    26. }
    27. //进入大厅回调
    28. public override void OnJoinedLobby()
    29. {
    30. Debug.Log("进入大厅...");
    31. }
    32. //创建房间
    33. public void onCreateRoomBtn()
    34. {
    35. Game.uiManager.ShowUI("CreateRoomUI");
    36. }
    37. //关闭大厅界面
    38. public void onCloseBtn()
    39. {
    40. //断开连接
    41. PhotonNetwork.Disconnect();
    42. Game.uiManager.CloseUI(gameObject.name);
    43. //显示登录界面
    44. Game.uiManager.ShowUI("LoginUI");
    45. }
    46. //刷新房间列表
    47. public void onUpdateRoomBtn()
    48. {
    49. Game.uiManager.ShowUI("MaskUI").ShowMsg("刷新中...");
    50. PhotonNetwork.GetCustomRoomList(lobby, "1"); //执行该方法后会触发OnRoomListUpdate回调
    51. }
    52. //清除已经存在的房间物体
    53. private void ClearRoomList()
    54. {
    55. while (contentTf.childCount != 0)
    56. {
    57. DestroyImmediate(contentTf.GetChild(0).gameObject);
    58. }
    59. }
    60. //刷新房间后的回调
    61. public override void OnRoomListUpdate(List roomList)
    62. {
    63. Game.uiManager.CloseUI("MaskUI");
    64. Debug.Log("房间刷新");
    65. ClearRoomList();
    66. for (int i = 0; i < roomList.Count; i++)
    67. {
    68. GameObject obj = Instantiate(roomPrefab, contentTf);
    69. obj.SetActive(true);
    70. string roomName = roomList[i].Name; //房间名称
    71. obj.transform.Find("roomName").GetComponent().text = roomName;
    72. obj.transform.Find("joinBtn").GetComponent
    73. {
    74. Debug.Log(roomName);
    75. //加入房间
    76. Game.uiManager.ShowUI("MaskUI").ShowMsg("加入中...");
    77. PhotonNetwork.JoinRoom(roomName); //加入房间
    78. });
    79. }
    80. }
    81. public override void OnJoinedRoom()
    82. {
    83. //加入房间回调
    84. Game.uiManager.CloseAllUI();
    85. Game.uiManager.ShowUI("RoomUI");
    86. }
    87. public override void OnJoinRoomFailed(short returnCode, string message)
    88. {
    89. //加入房间失败
    90. Game.uiManager.CloseUI("MaskUI");
    91. }
    92. }

    2.3 创建房间UI

    创建房间脚本 CreateRoomUI

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Photon.Pun;
    5. using Photon.Realtime;
    6. using UnityEngine.UI;
    7. public class CreateRoomUI : MonoBehaviourPunCallbacks
    8. {
    9. private InputField roomNameInput; //房间名称
    10. void Start()
    11. {
    12. transform.Find("bg/title/closeBtn").GetComponent
    13. transform.Find("bg/okBtn").GetComponent
    14. roomNameInput = transform.Find("bg/InputField").GetComponent();
    15. //随机一个房间名称
    16. roomNameInput.text = "room_" + Random.Range(1, 9999);
    17. }
    18. //创建房间
    19. public void onCreateBtn()
    20. {
    21. Game.uiManager.ShowUI("MaskUI").ShowMsg("创建中...");
    22. RoomOptions room = new RoomOptions();
    23. room.MaxPlayers = 8; //房间最大玩家数
    24. PhotonNetwork.CreateRoom(roomNameInput.text, room); //1.房间名称 2.房间的对象参数
    25. }
    26. //关闭按钮
    27. public void onCloseBtn()
    28. {
    29. Game.uiManager.CloseUI(gameObject.name);
    30. }
    31. //创建成功后回调
    32. public override void OnCreatedRoom()
    33. {
    34. Debug.Log("创建成功");
    35. Game.uiManager.CloseAllUI();
    36. //显示房间UI
    37. Game.uiManager.ShowUI("RoomUI");
    38. }
    39. //创建失败
    40. public override void OnCreateRoomFailed(short returnCode, string message)
    41. {
    42. Game.uiManager.CloseUI("MaskUI");
    43. }
    44. }

    2.4 进入房间UI 

    创建房间完成后会进入到房间里  编写RoomUI脚本

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Photon.Pun;
    5. using Photon.Realtime;
    6. using UnityEngine.UI;
    7. public class RoomUI : MonoBehaviour,IInRoomCallbacks
    8. {
    9. Transform startTf;
    10. Transform contentTf;
    11. GameObject roomPrefab;
    12. public List roomList;
    13. private void Awake()
    14. {
    15. roomList = new List();
    16. contentTf = transform.Find("bg/Content");
    17. roomPrefab = transform.Find("bg/roomItem").gameObject;
    18. transform.Find("bg/title/closeBtn").GetComponent
    19. startTf = transform.Find("bg/startBtn");
    20. startTf.GetComponent
    21. PhotonNetwork.AutomaticallySyncScene = true; //执行PhotonNetwork.LoadLevel加载场景的时候 其他玩家也跳转相同的场景
    22. }
    23. void Start()
    24. {
    25. //生成房间里的玩家项
    26. for (int i = 0; i < PhotonNetwork.PlayerList.Length; i++)
    27. {
    28. Player p = PhotonNetwork.PlayerList[i];
    29. CreateRoomItem(p);
    30. }
    31. }
    32. private void OnEnable()
    33. {
    34. PhotonNetwork.AddCallbackTarget(this);
    35. }
    36. private void OnDisable()
    37. {
    38. PhotonNetwork.RemoveCallbackTarget(this);
    39. }
    40. //生成玩家
    41. public void CreateRoomItem(Player p)
    42. {
    43. GameObject obj = Instantiate(roomPrefab, contentTf);
    44. obj.SetActive(true);
    45. RoomItem item = obj.AddComponent();
    46. item.owerId = p.ActorNumber; //玩家编号
    47. roomList.Add(item);
    48. object val;
    49. if (p.CustomProperties.TryGetValue("IsReady", out val))
    50. {
    51. item.IsReady = (bool)val;
    52. }
    53. }
    54. //删除离开房间的玩家
    55. public void DeleteRoomItem(Player p)
    56. {
    57. RoomItem item = roomList.Find((RoomItem _item) => { return p.ActorNumber == _item.owerId; });
    58. if (item != null)
    59. {
    60. Destroy(item.gameObject);
    61. roomList.Remove(item);
    62. }
    63. }
    64. //关闭
    65. void onCloseBtn()
    66. {
    67. //断开连接
    68. PhotonNetwork.Disconnect();
    69. Game.uiManager.CloseUI(gameObject.name);
    70. Game.uiManager.ShowUI("LoginUI");
    71. }
    72. //开始游戏
    73. void onStartBtn()
    74. {
    75. //加载场景 让房间里的玩家也加载场景
    76. PhotonNetwork.LoadLevel("game");
    77. }
    78. //新玩家进入房间
    79. public void OnPlayerEnteredRoom(Player newPlayer)
    80. {
    81. CreateRoomItem(newPlayer);
    82. }
    83. //房间里的其他玩家离开房间
    84. public void OnPlayerLeftRoom(Player otherPlayer)
    85. {
    86. DeleteRoomItem(otherPlayer);
    87. }
    88. public void OnRoomPropertiesUpdate(ExitGames.Client.Photon.Hashtable propertiesThatChanged)
    89. {
    90. }
    91. //玩家自定义参数更新回调
    92. public void OnPlayerPropertiesUpdate(Player targetPlayer, ExitGames.Client.Photon.Hashtable changedProps)
    93. {
    94. RoomItem item = roomList.Find((_item) => { return _item.owerId == targetPlayer.ActorNumber; });
    95. if (item != null)
    96. {
    97. item.IsReady = (bool)changedProps["IsReady"];
    98. item.ChangeReady(item.IsReady);
    99. }
    100. //如果是主机玩家判断所有玩家的准备状态
    101. if (PhotonNetwork.IsMasterClient)
    102. {
    103. bool isAllReady = true;
    104. for (int i = 0; i < roomList.Count; i++)
    105. {
    106. if (roomList[i].IsReady == false)
    107. {
    108. isAllReady = false;
    109. break;
    110. }
    111. }
    112. startTf.gameObject.SetActive(isAllReady); //开始按钮是否显示
    113. }
    114. }
    115. public void OnMasterClientSwitched(Player newMasterClient)
    116. {
    117. }
    118. }

    2.5 玩家准备状态

    玩家进入房间后会显示信息和准备状况,编写RoomItem脚本实现。只有房间内所有玩家都处于准备状态房主才能开始游戏。

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Photon.Pun;
    5. using Photon.Realtime;
    6. using UnityEngine.UI;
    7. public class RoomItem : MonoBehaviour
    8. {
    9. public int owerId; //玩家编号
    10. public bool IsReady = false; //是否准备
    11. void Start()
    12. {
    13. if (owerId == PhotonNetwork.LocalPlayer.ActorNumber)
    14. {
    15. transform.Find("Button").GetComponent
    16. }
    17. else
    18. {
    19. transform.Find("Button").GetComponent().color = Color.black;
    20. }
    21. ChangeReady(IsReady);
    22. }
    23. public void OnReadyBtn()
    24. {
    25. IsReady = !IsReady;
    26. ExitGames.Client.Photon.Hashtable table = new ExitGames.Client.Photon.Hashtable();
    27. table.Add("IsReady", IsReady);
    28. PhotonNetwork.LocalPlayer.SetCustomProperties(table); //设置自定义参数
    29. ChangeReady(IsReady);
    30. }
    31. public void ChangeReady(bool isReady)
    32. {
    33. transform.Find("Button/Text").GetComponent().text = isReady == true ? "已准备" : "未准备";
    34. }
    35. }

     2.6 加载战斗场景

    双击切换到战斗场景game中,编写FightManager脚本挂载到fight

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Photon.Pun;
    5. public class FightManager : MonoBehaviour
    6. {
    7. private void Awake()
    8. {
    9. //隐藏鼠标
    10. Cursor.lockState = CursorLockMode.Locked;
    11. Cursor.visible = false;
    12. //关闭所有界面
    13. Game.uiManager.CloseAllUI();
    14. //显示战斗界面
    15. Game.uiManager.ShowUI("FightUI");
    16. Transform pointTf = GameObject.Find("Point").transform;
    17. Vector3 pos = pointTf.GetChild(Random.Range(0, pointTf.childCount)).position;
    18. //实例化角色
    19. PhotonNetwork.Instantiate("Player", pos, Quaternion.identity); //实例化的资源要放在Resources文件夹
    20. }
    21. }

    编写战斗场景UI界面处理脚本FightUI

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. public class FightUI : MonoBehaviour
    6. {
    7. private Image bloodImg;
    8. void Start()
    9. {
    10. bloodImg = transform.Find("blood").GetComponent();
    11. }
    12. //更新子弹个数显示
    13. public void UpdateBulletCount(int count)
    14. {
    15. transform.Find("bullet/Text").GetComponent().text = count.ToString();
    16. }
    17. //更新血量
    18. public void UpdateHp(float cur, float max)
    19. {
    20. transform.Find("hp/fill").GetComponent().fillAmount = cur / max;
    21. transform.Find("hp/Text").GetComponent().text = cur + "/" + max;
    22. }
    23. public void UpdateBlood()
    24. {
    25. StopAllCoroutines();
    26. StartCoroutine(UpdateBloodCo());
    27. }
    28. public IEnumerator UpdateBloodCo()
    29. {
    30. bloodImg.color = Color.white;
    31. Color color = bloodImg.color;
    32. float t = 0.35f;
    33. while (t >= 0)
    34. {
    35. t -= Time.deltaTime;
    36. color.a = Mathf.Abs(Mathf.Sin(Time.realtimeSinceStartup));
    37. bloodImg.color = color;
    38. yield return null;
    39. }
    40. color.a = 0;
    41. bloodImg.color = color;
    42. }
    43. }

    2.7 死亡UI界面和复活按钮

    编写脚本 LossUI

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. public class LossUI : MonoBehaviour
    6. {
    7. public System.Action onClickCallBack;
    8. // Start is called before the first frame update
    9. void Start()
    10. {
    11. transform.Find("resetBtn").GetComponent
    12. }
    13. public void OnClickBtn()
    14. {
    15. if (onClickCallBack != null)
    16. {
    17. onClickCallBack();
    18. }
    19. Game.uiManager.CloseUI(gameObject.name);
    20. }
    21. }

    三、角色控制器

    3.1 生成角色

    给角色Player挂载Photon View组件,实例化生成玩家的代码我们已经在FightManager脚本中实现了。

    3.2 角色控制和战斗系统

    编写角色控制脚本PlayerController挂载到角色上,战斗系统的逻辑也在这个脚本里,其中的参数数值可以参考下图:

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Photon.Pun;
    5. using Photon.Realtime;
    6. //角色控制器
    7. public class PlayerController : MonoBehaviourPun,IPunObservable
    8. {
    9. //组件
    10. public Animator ani;
    11. public Rigidbody body;
    12. public Transform camTf; //跟随的相机
    13. //数值
    14. public int CurHp = 10;
    15. public int MaxHp = 10;
    16. public float MoveSpeed = 5f;
    17. public float H; //水平值
    18. public float V; //垂直值
    19. public Vector3 dir; //移动方向
    20. public Vector3 offset; //摄像机与角色之间的偏移值
    21. public float Mouse_X; //鼠标偏移值
    22. public float Mouse_Y;
    23. public float scroll; //鼠标滚轮值
    24. public float Angle_X; //x轴的旋转角度
    25. public float Angle_Y; //y轴的旋转角度
    26. public Quaternion camRotation; //摄像机旋转的四元数
    27. public Gun gun; //枪的脚本
    28. //声音
    29. public AudioClip reloadClip;
    30. public AudioClip shootClip;
    31. public bool isDie = false;
    32. public Vector3 currentPos;
    33. public Quaternion currentRotation;
    34. void Start()
    35. {
    36. Angle_X = transform.eulerAngles.x;
    37. Angle_Y = transform.eulerAngles.y;
    38. ani = GetComponent();
    39. body = GetComponent();
    40. gun = GetComponentInChildren();
    41. camTf = Camera.main.transform;
    42. currentPos = transform.position;
    43. currentRotation = transform.rotation;
    44. if (photonView.IsMine)
    45. {
    46. Game.uiManager.GetUI("FightUI").UpdateHp(CurHp, MaxHp);
    47. }
    48. }
    49. void Update()
    50. {
    51. //判断是否是本机玩家 只能操作本机角色
    52. if (photonView.IsMine)
    53. {
    54. if (isDie == true)
    55. {
    56. return;
    57. }
    58. UpdatePosition();
    59. UpdateRotation();
    60. InputCtl();
    61. }
    62. else
    63. {
    64. UpdateLogic();
    65. }
    66. }
    67. //其他角色更新发送过来的数据(位置 旋转)
    68. public void UpdateLogic()
    69. {
    70. transform.position = Vector3.Lerp(transform.position, currentPos, Time.deltaTime * MoveSpeed * 10);
    71. transform.rotation = Quaternion.Slerp(transform.rotation, currentRotation, Time.deltaTime * 500);
    72. }
    73. private void LateUpdate()
    74. {
    75. ani.SetFloat("Horizontal", H);
    76. ani.SetFloat("Vertical", V);
    77. ani.SetBool("isDie", isDie);
    78. }
    79. //更新位置
    80. public void UpdatePosition()
    81. {
    82. H = Input.GetAxisRaw("Horizontal");
    83. V = Input.GetAxisRaw("Vertical");
    84. dir = camTf.forward * V + camTf.right * H;
    85. body.MovePosition(transform.position + dir * Time.deltaTime * MoveSpeed);
    86. }
    87. //更新旋转(同时设置摄像机的位置的旋转值)
    88. public void UpdateRotation()
    89. {
    90. Mouse_X = Input.GetAxisRaw("Mouse X");
    91. Mouse_Y = Input.GetAxisRaw("Mouse Y");
    92. scroll = Input.GetAxis("Mouse ScrollWheel");
    93. Angle_X = Angle_X - Mouse_Y;
    94. Angle_Y = Angle_Y + Mouse_X;
    95. Angle_X = ClampAngle(Angle_X, -60, 60);
    96. Angle_Y = ClampAngle(Angle_Y, -360, 360);
    97. camRotation = Quaternion.Euler(Angle_X, Angle_Y, 0);
    98. camTf.rotation = camRotation;
    99. offset.z += scroll;
    100. camTf.position = transform.position + camTf.rotation * offset;
    101. transform.eulerAngles = new Vector3(0, camTf.eulerAngles.y, 0);
    102. }
    103. //角色操作
    104. public void InputCtl()
    105. {
    106. if (Input.GetMouseButtonDown(0))
    107. {
    108. //判断子弹个数
    109. if (gun.BulletCount > 0)
    110. {
    111. //如果正在播放填充子弹的动作不能开枪
    112. if (ani.GetCurrentAnimatorStateInfo(1).IsName("Reload"))
    113. {
    114. return;
    115. }
    116. gun.BulletCount--;
    117. Game.uiManager.GetUI("FightUI").UpdateBulletCount(gun.BulletCount);
    118. //播放开火动画
    119. ani.Play("Fire", 1, 0);
    120. StopAllCoroutines();
    121. StartCoroutine(AttackCo());
    122. }
    123. }
    124. if (Input.GetKeyDown(KeyCode.R))
    125. {
    126. //填充子弹
    127. AudioSource.PlayClipAtPoint(reloadClip, transform.position); //播放填充子弹的声音
    128. ani.Play("Reload");
    129. gun.BulletCount = 10;
    130. Game.uiManager.GetUI("FightUI").UpdateBulletCount(gun.BulletCount);
    131. }
    132. }
    133. //攻击协同程序
    134. IEnumerator AttackCo()
    135. {
    136. //延迟0.1秒才发射子弹
    137. yield return new WaitForSeconds(0.1f);
    138. //播放射击音效
    139. AudioSource.PlayClipAtPoint(shootClip, transform.position);
    140. //射线检测 鼠标中心点发送射线
    141. Ray ray = Camera.main.ScreenPointToRay(new Vector3(Screen.width * 0.5f, Screen.height * 0.5f,Input.mousePosition.z));
    142. //射线可以改成在枪口位置为起始点 发送,避免射线射到自身
    143. RaycastHit hit;
    144. if (Physics.Raycast(ray, out hit, 10000, LayerMask.GetMask("Player")))
    145. {
    146. Debug.Log("射到角色");
    147. hit.transform.GetComponent().GetHit();
    148. }
    149. photonView.RPC("AttackRpc", RpcTarget.All); //所有玩家执行 AttackRpc 函数
    150. }
    151. [PunRPC]
    152. public void AttackRpc()
    153. {
    154. gun.Attack();
    155. }
    156. //受伤
    157. public void GetHit()
    158. {
    159. if (isDie == true)
    160. {
    161. return;
    162. }
    163. //同步所有角色受伤
    164. photonView.RPC("GetHitRPC", RpcTarget.All);
    165. }
    166. [PunRPC]
    167. public void GetHitRPC()
    168. {
    169. CurHp -= 1; //扣一滴血
    170. if (CurHp <= 0)
    171. {
    172. CurHp = 0;
    173. isDie = true;
    174. }
    175. if (photonView.IsMine)
    176. {
    177. Game.uiManager.GetUI("FightUI").UpdateHp(CurHp, MaxHp);
    178. Game.uiManager.GetUI("FightUI").UpdateBlood();
    179. if (CurHp == 0)
    180. {
    181. Invoke("gameOver", 3); //3秒后显示失败界面
    182. }
    183. }
    184. }
    185. private void gameOver()
    186. {
    187. //显示鼠标
    188. Cursor.visible = true;
    189. Cursor.lockState = CursorLockMode.None;
    190. //显示失败界面
    191. Game.uiManager.ShowUI("LossUI").onClickCallBack = OnReset;
    192. }
    193. //复活
    194. public void OnReset()
    195. {
    196. //隐藏鼠标
    197. Cursor.visible = false;
    198. Cursor.lockState = CursorLockMode.Locked;
    199. photonView.RPC("OnResetRPC", RpcTarget.All);
    200. }
    201. [PunRPC]
    202. public void OnResetRPC()
    203. {
    204. isDie = false;
    205. CurHp = MaxHp;
    206. if (photonView.IsMine)
    207. {
    208. Game.uiManager.GetUI("FightUI").UpdateHp(CurHp, MaxHp);
    209. }
    210. }
    211. //限制角度在-360 到 360之间
    212. public float ClampAngle(float val, float min, float max)
    213. {
    214. if (val > 360)
    215. {
    216. val -= 360;
    217. }
    218. if (val < -360)
    219. {
    220. val += 360;
    221. }
    222. return Mathf.Clamp(val, min, max);
    223. }
    224. private void OnAnimatorIK(int layerIndex)
    225. {
    226. if (ani != null)
    227. {
    228. Vector3 angle = ani.GetBoneTransform(HumanBodyBones.Chest).localEulerAngles;
    229. angle.x = Angle_X;
    230. ani.SetBoneLocalRotation(HumanBodyBones.Chest, Quaternion.Euler(angle));
    231. }
    232. }
    233. public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    234. {
    235. if (stream.IsWriting)
    236. {
    237. //发送数据
    238. stream.SendNext(H);
    239. stream.SendNext(V);
    240. stream.SendNext(Angle_X);
    241. stream.SendNext(transform.position);
    242. stream.SendNext(transform.rotation);
    243. }
    244. else
    245. {
    246. //接收数据
    247. H = (float)stream.ReceiveNext();
    248. V = (float)stream.ReceiveNext();
    249. Angle_X = (float)stream.ReceiveNext();
    250. currentPos = (Vector3)stream.ReceiveNext();
    251. currentRotation = (Quaternion)stream.ReceiveNext();
    252. }
    253. }
    254. }

    3.3 枪的脚本

    编写枪的脚本Gun,挂载到Assault_Rifle_02上,并按下图把参数填充好

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. //枪的脚本
    5. public class Gun : MonoBehaviour
    6. {
    7. public int BulletCount = 10;
    8. public GameObject bulletPrefab;
    9. public GameObject casingPreafab;
    10. public Transform bulletTf;
    11. public Transform casingTf;
    12. void Start()
    13. {
    14. }
    15. public void Attack()
    16. {
    17. GameObject bulletObj = Instantiate(bulletPrefab);
    18. bulletObj.transform.position = bulletTf.transform.position;
    19. bulletObj.GetComponent().AddForce(transform.forward * 500, ForceMode.Impulse); //子弹速度 让中心点跟枪口位置可自行调整摄像机的偏移值
    20. GameObject casingObj = Instantiate(casingPreafab);
    21. casingObj.transform.position = casingTf.transform.position;
    22. }
    23. }

    四、项目打包导出 

    1. 文件 ——》 生成设置 

    2. 点击生成选择文件夹打包

    3. 打包好后也可以发给自己的小伙伴,双击直接可以运行

  • 相关阅读:
    新人必看!手把手教你如何使用浏览器表格插件(上)
    Jenkins持续集成
    R语言使用lightgbm包构建二分类的LightGBM模型、使用predict函数和训练模型进行预测推理,预测测试数据的概率值并转化为标签
    2、计算机图形学——视图变换
    Java,常用类与API,String类
    C++基础——new和delete动态开辟
    【JavaScript 20】String对象 构造函数 工具方法 静态方法 实例属性 实例方法
    c++ 11 多线程支持 (std::future)
    Suquel Pro连接最新版本Mysql
    PostgreSQL Array 数组类型与 FreeSql 打出一套【组合拳】
  • 原文地址:https://blog.csdn.net/qq_48512649/article/details/136249522