Unity学习笔记:联网游戏Pixel Adventure 1学习过程&纠错心得、
之前学校教了一些网络游戏编程的相关内容,跟着学完了感觉还是不会,但是做能联网的游戏又很重要,正好前不久刷到一个合适的教程,B站弹幕评论区一致好评讲的很好,用到的Photon技术学长也推荐,正好最近工程实践项目做得告一段落,于是决定把这个教程找来学学。
https://www.bilibili.com/video/BV1rJ411D7VJ?spm_id_from=333.999.0.0
这个帖子用来记录自己学习途中遇到的一些问题和解决办法,类似于纠错本。
用到的资源链接:
https://assetstore.unity.com/packages/2d/characters/pixel-adventure-1-155360
用到的Photon:
https://dashboard.photonengine.com/en-US
1.资源包加入到我的资源之后点击在Unity中打开没反应
参考链接:
https://blog.csdn.net/qq_15020543/article/details/83382527?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2aggregatepagefirst_rank_ecpm_v1~rank_v31_ecpm-1-83382527-null-null.pc_agg_new_rank&utm_term=%E5%9C%A8unity%E5%95%86%E5%BA%97%E6%89%93%E5%BC%80%E8%B5%84%E6%BA%90%E6%B2%A1%E5%8F%8D%E5%BA%94&spm=1000.2123.3001.4430
但是我就是用的Hub,所以对我来说没什么帮助。
最后我新建了一个空白项目,刚打开就发现可以自动出现自己的资源:
点击右下角,下载之后导入资源包:
(顺便提一句这个好像还是我们工程实践老师提到过的说什么Unity新的Package模式还是什么?我也有点记不太清了)
2.绑定App ID到Pun 2
把自己创建项目的AppID复制下来,粘贴到导入Photon2之后出现的窗口里。
![在这里插入图片描述](https://img-blog.csdnimg.cn/b2d76129db654c45ad3e0a5c2034bfe0.png
Setting这里版本号随便写个数字1
调整信息显示的类别,希望显示所有信息,比如网络连接情况
教程在这里找:
3.导入
如果不能拖拽添加观察,就手动加上对应脚本
Trigger要放在最后一个选项
进入一次房间要生成一个玩家,所以玩家需要做成Prefab,如果需要网络化,那么Prefab要添加到Photon文件夹中。
4.网页出现error无法正常加载
网页Bug,把error删掉就行
回车即可
第一集使用到的C#代码:
建一个空物体改名NetWorkLauncher,然后把这个脚本附加上去
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
public class Launcher : MonoBehaviourPunCallbacks//继承这个类可以得到服务器的反馈资料
{
// Start is called before the first frame update
void Start()
{
PhotonNetwork.ConnectUsingSettings();//链接服务器
}
public override void OnConnectedToMaster()//是否连接到游戏大厅,主服务器
{
base.OnConnectedToMaster();
Debug.Log("连接成功!");
//所有进入同一房间的玩家会在一个屏幕上显示
PhotonNetwork.JoinOrCreateRoom("Room", new Photon.Realtime.RoomOptions() { MaxPlayers = 4 }, default);
//MaxPlayer最大可20个,default可以换成其他房间属性
}
public override void OnJoinedRoom()
{
base.OnJoinedRoom();
PhotonNetwork.Instantiate("Player", new Vector3(1, 1, 0), Quaternion.identity, 0);
}
// Update is called once per frame
void Update()
{
}
}
挂在Player身上的代码(只有网络相关功能,没有玩家移动转身之类的内容)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
/// <summary>
/// 本脚本用于控制玩家的移动
/// </summary>
public class PlayerMovement : MonoBehaviourPun
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if(!photonView.IsMine&&PhotonNetwork.IsConnected)//如果观察的角色是当前客户端并且服务器在运行就继续反之return
{
return;
}
}
}
挂上这两脚本之后,应该能实现运行游戏自动生成玩家和Build Settings窗口和运行模式窗口的玩家同步
5.报错Can not Instantiate before the client joined/created a room. State: ConnectedToMasterServer
经过回看视频检查,发现是创建房间的函数绑定错误,绑成了之前那个应该在第二集开头就被删除的脚本(顺便,可以不删除脚本,把挂有这个脚本的物体删除就行了)
改成新写的JoinOrCreateRoom之后,问题解决
没有报错了。
之前报错是因为这个按钮点击调用了那个被移除的脚本里面的函数,相当于无效点击,自然也没有创建房间,所以报错Can not Instantiate before the client joined/created a room. State: ConnectedToMasterServer
6.角色翻转的时候,角色身上的名字也一起翻转
获取Sprite Renderer组件的Flip参数即可,不使用自己写的代码
因为我没写老师那么完善的代码,他这里面一些变量我也不太知道怎么定义的,就只大致填个框架了。
看弹幕有人说这么写因为没有Photon View Sprite Renderer,所以可能会造成无法同步的情况
我试着找了一下能View Sprite Renderer的脚本,但是并没有找到,又去百度了一下,还没有找到合适的答案
发现一位码友写的链接很有营养:
https://blog.csdn.net/ninesnow_c/article/details/106699608
第二集使用到的C#代码:
为了不报错,我把Flip的部分注释掉了:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using UnityEngine.UI;
/// <summary>
/// 本脚本用于控制玩家的移动
/// </summary>
public class PlayerMovement : MonoBehaviourPun
{
public Text nameText;
public SpriteRenderer sprite;
private void Awake()
{
sprite = GetComponent<SpriteRenderer>();
if(photonView.IsMine)//如果观察到的角色是自己,就把自己的名字给这个对象
{
nameText.text = PhotonNetwork.NickName;
}
else//把正主的名字给这个对象,看到的这个角色是谁就是谁的名字
{
nameText.text = photonView.Owner.NickName;
}
}
public void Flip()//翻转
{
//if(xVelocity<0)
//{
// sprite.flipX = true;
//}
//else if(xVelocity>0)
//{
// sprite.flipX = false;
//}
}
// Update is called once per frame
void Update()
{
if(!photonView.IsMine&&PhotonNetwork.IsConnected)//如果观察的角色是当前客户端并且服务器在运行就继续反之return
{
return;
}
}
}
挂在Level1场景的空物体改名GameManager上的脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
public class GameManager : MonoBehaviourPunCallbacks
{
public GameObject readyButton;
public void ReadyToPlay()
{
readyButton.SetActive(false);
PhotonNetwork.Instantiate("Player", new Vector3(1, 1, 0), Quaternion.identity, 0);
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
挂在Login场景中的NetworkLauncher(空物体改名字)上的脚本NetworkLauncher
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine.UI;
public class NetworkLauncher : MonoBehaviourPunCallbacks
{
public GameObject loginUI;
public GameObject nameUI;
public InputField roomName;
public InputField playerName;
// Start is called before the first frame update
void Start()
{
PhotonNetwork.ConnectUsingSettings();
}
public override void OnConnectedToMaster()
{
//base.OnConnectedToMaster();
nameUI.SetActive(true);//如果连接到服务器,登录UI就会显示出来
}
public void PlayButton()
{
nameUI.SetActive(false);
PhotonNetwork.NickName = playerName.text;//名字传输到网络当中
loginUI.SetActive(true);//启用登录UI
}
public void JoinOrCreateButton()
{
//如果没有名字,不能点击按钮
if(roomName.text.Length<2)
{
return;
}
loginUI.SetActive(false);
RoomOptions options = new RoomOptions { MaxPlayers = 4 };
PhotonNetwork.JoinOrCreateRoom(roomName.text, options, default);//有就加入,没有就创建
}
public override void OnJoinedRoom()
{
base.OnJoinedRoom();
PhotonNetwork.LoadLevel(1);
}
}
7.无法进入同一房间
这个物体是B站网友提出的,我自己倒是没有遇到。
修改Fixed Region,强制进入同一服务器
把下图最下方红框里面网速最快的国家代号填进Fixed Region,这样就能保证客户端都进入这个服务器
8.Ctrl+鼠标点击查看方法详情
9.指定位置生成预制体并设置父物体
这段代码比较有参考价值,我之前也一直不太会写这种,现在遇到了就专门记下来:
10.报错ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
百度了一下这句话,大部分是因为数组越界造成的。
但是我仔细检查了我那点少得可怜的关于数组的代码,好像并没有越界的风险?
而且多反复Build几次之后,出现了新的报错,是Player和RoomListNameButton没有把预制体拖给脚本,所以实例化失败,把这俩问题解决之后所有奇奇怪怪的问题都奇迹般地消失了。
运行效果:
第三集使用到的C#代码:
挂在Login场景里面空物体上RoomListManager的,关于更新房间列表的脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine.UI;
public class RoomListManager : MonoBehaviourPunCallbacks
{
public GameObject roomNamePreb;
public Transform gridLayout;//content
public override void OnRoomListUpdate(List<RoomInfo> roomList)
{
for(int i=0;i<gridLayout.childCount;i++)
{
//移除所有房间
if(gridLayout.GetChild(i).gameObject.GetComponentInChildren<Text>().text==roomList[i].Name)
{
Destroy(gridLayout.GetChild(i).gameObject);//移除的是UI按钮
//房间里面没人了需要移除房间
if(roomList[i].PlayerCount==0)
{
roomList.Remove(roomList[i]);//移除该房间
}
}
}
foreach(var room in roomList)//遍历房间列表
{
//对象,位置,旋转角度
GameObject newRoom = Instantiate(roomNamePreb, gridLayout.position, Quaternion.identity);
//按钮身上的Text改成房间名字
newRoom.GetComponentInChildren<Text>().text = room.Name +"("+room.PlayerCount+" 人)";//可以自由DIY房间需要显示的信息
//生成的预制体的父物体设置为Content
newRoom.transform.SetParent(gridLayout);
}
}
}
挂在主角行动,负责主角位置同步和主角移动(没有写,可根据需要自行添加主角左右移动之类的代码):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using UnityEngine.UI;
/// <summary>
/// 本脚本用于控制玩家的移动
/// </summary>
public class PlayerMovement : MonoBehaviourPun
{
public Text nameText;
public SpriteRenderer sprite;
private void Awake()
{
sprite = GetComponent<SpriteRenderer>();
if(photonView.IsMine)//如果观察到的角色是自己,就把自己的名字给这个对象
{
nameText.text = PhotonNetwork.NickName;
}
else//把正主的名字给这个对象,看到的这个角色是谁就是谁的名字
{
nameText.text = photonView.Owner.NickName;
}
}
public void Flip()//翻转
{
//if(xVelocity<0)
//{
// sprite.flipX = true;
//}
//else if(xVelocity>0)
//{
// sprite.flipX = false;
//}
}
// Update is called once per frame
void Update()
{
if(!photonView.IsMine&&PhotonNetwork.IsConnected)//如果观察的角色是当前客户端并且服务器在运行就继续反之return
{
return;
}
}
}
挂在Level1场景的空物体GameManager身上的脚本GameManager:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
public class GameManager : MonoBehaviourPunCallbacks
{
public GameObject readyButton;
public void ReadyToPlay()
{
readyButton.SetActive(false);
PhotonNetwork.Instantiate("Player", new Vector3(1, 1, 0), Quaternion.identity, 0);
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
挂在Login场景上的NetworkLauncher上的脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine.UI;
public class NetworkLauncher : MonoBehaviourPunCallbacks
{
public GameObject loginUI;
public GameObject nameUI;
public InputField roomName;
public InputField playerName;
public GameObject roomListUI;
// Start is called before the first frame update
void Start()
{
PhotonNetwork.ConnectUsingSettings();
}
public override void OnConnectedToMaster()
{
//base.OnConnectedToMaster();
nameUI.SetActive(true);//如果连接到服务器,登录UI就会显示出来
Debug.Log("Connecter to the Master");
PhotonNetwork.JoinLobby();//加入游戏大厅,这样才可以读取RoomList
}
public void PlayButton()
{
nameUI.SetActive(false);
PhotonNetwork.NickName = playerName.text;//名字传输到网络当中
loginUI.SetActive(true);//启用登录UI
//如果加入大厅,则显示列表UI
if(PhotonNetwork.InLobby)
{
roomListUI.SetActive(true);
}
}
public void JoinOrCreateButton()
{
//如果没有名字,不能点击按钮
if(roomName.text.Length<2)
{
return;
}
loginUI.SetActive(false);
RoomOptions options = new RoomOptions { MaxPlayers = 4 };
PhotonNetwork.JoinOrCreateRoom(roomName.text, options, default);//有就加入,没有就创建
}
public override void OnJoinedRoom()
{
base.OnJoinedRoom();
PhotonNetwork.LoadLevel(1);
}
}
层级关系如图:
Login场景:
UI层级:
Level1场景:
完整的项目我也会上传CSDN空间,非常简略,除了我提到的这些以外没别的功能。