通过6D持久性设置多人游戏体验

如何使用6D现实平台制作多人示例应用程序

当AR让我们以前所未有的新颖,吸引人的方式进行交流和共享时,它将真正影响我们的日常生活,这种交流需要实时的人与人互动。 多人游戏体验可以将AR提升到共享,玩耍和社交的全新境界。借助6D现实平台,人们几乎可以从任何位置立即加入体验,从而使AR多人游戏毫不费力。

本教程将添加我们的持久性教程。 如果尚未完成该教程,则应在开始本教程之前完成。 我们将在同一坐标空间中添加多个用户,以便他们可以一起共享实时体验,以及一起共享相同的持久性内容。 有许多可用于管理多人网络的工具,在这种情况下,我们将使用Photon Engine。

对于本教程,我们将允许用户在增强现实中将球体放置在世界中,并允许两个用户在相同位置看到球体。 我们还将具有持久性,这意味着任何玩家都可以保存在一个会话中创建的球体,并在以后的另一个会话中检索这些球体。

注意:我们还 为该教程 创建了一个 公开的Google文档 ,供那些希望扩展图像的共享输入的人使用。

在本教程中,我们将使用Photon Unity Networking(PUN v1)管理应用程序的多人网络。 您需要做的第一件事是使用Photon(www.photonengine.com)创建一个免费帐户,然后从Photon仪表板复制您的App ID。 您可能需要单击App ID来展开并复制它:

拥有AppId之后,就可以将PUN包导入Unity中了。 在Unity中,打开资产商店[ 窗口>常规>资产商店 ]并导入PUN v1 classic:

导入完成后,您的Assets文件夹中应该有一个Photon文件夹。 在Photon文件夹中是一个名为Resources的文件夹,其中包含PhotonServerSettings.asset 。 选择此资产将打开“光子设置”。 您的“光子设置”应与下面的图片匹配,除了应粘贴在先前复制的“光子应用ID”中的所有“ AppId”字段外:

我们要做的第一件事是制作我们的启动脚本。 该脚本将允许多个用户加入同一大厅,以便他们可以一起在同一服务器中。

  1. 创建一个空对象[ GameObject>空 ],并将其命名为Launcher。
  2. 选择启动器对象,然后添加一个新脚本作为组件。 [ 添加组件按钮>新建脚本 ]。
  3. 将新脚本命名为Launcher 。 新脚本Launcher.cs将自动保存到项目的Assets文件夹中。
  4. 在您喜欢的文本编辑中打开Launcher.cs并粘贴以下代码:
 使用UnityEngine; 
使用SixDegrees;
使用系统;
公共课发射器:Photon.PunBehaviour
{
公共字节MaxPlayersPerRoom = 4;
公共GameObject playerPrefab;
字符串_gameVersion =“ 1”;
公共SDKController sdkController;
公共布尔加载;
节省公共开支;
无效Awake()
{
加载=假;
保存=假;
sdkController.OnSaveSucceededEvent + = StartGameSave;
sdkController.OnLoadSucceededEvent + = StartGameLoad;
}
无效的StartGameSave()
{
如果(!PhotonNetwork.inRoom)
{
PhotonNetwork.autoJoinLobby = false;
PhotonNetwork.automaticallySyncScene = true;
Saving = true;
Connect();
}
}
无效的StartGameLoad()
{
如果(!PhotonNetwork.inRoom)
{
PhotonNetwork.autoJoinLobby = false;
PhotonNetwork.automaticallySyncScene = true;
加载=真;
Connect();
}
}
无效的OnDestroy()
{
sdkController.OnSaveSucceededEvent-= StartGameSave;
sdkController.OnLoadSucceededEvent-= StartGameLoad;
}
公共无效Connect()
{
如果(PhotonNetwork.connected)
{
PhotonNetwork.JoinOrCreateRoom(SDPlugin.LocationID,new RoomOptions(){MaxPlayers = MaxPlayersPerRoom},null);
}
其他
{
PhotonNetwork.ConnectUsingSettings(_gameVersion);
}
}
公共重写void OnConnectedToMaster()
{
Debug.Log(“ DemoAnimator / Launcher:PUN调用OnConnectedToMaster()”);
PhotonNetwork.JoinOrCreateRoom(SDPlugin.LocationID,new RoomOptions(){MaxPlayers = MaxPlayersPerRoom},null);
}
公共重写void OnDisconnectedFromPhoton()
{
Debug.LogWarning(“ DemoAnimator / Launcher:OnDisconnectedFromPhoton()由PUN调用”);
}
公共重写void OnJoinedRoom()
{
Debug.Log(“ DemoAnimator / Launcher:PUN调用的OnJoinedRoom()。现在此客户端在房间里。”);
GameObject temp = PhotonNetwork.Instantiate(playerPrefab.name,Vector3.zero,
Quaternion.identity,0);
如果(加载== true)
{
temp.GetComponent ()。loaded = true;
}
如果(保存==真)
{
temp.GetComponent ()。saving = true;
}
}
}

让我们详细了解一下此脚本的操作:

  1. 有对sdkController的引用。 这是必需的,因为对于用户而言,创建大厅需要首先保存6D地图。 这为我们提供了locationID,我们将其用作大厅/房间的名称。
  2. 接下来,要让其他用户加入该大厅,他们需要加载该地图并成功地重新定位。 这将为他们提供与我们用来确定要加入的大厅/房间相同的locationID。
  3. 您将看到sdkController.savesdkController.load有两个回调。 这就是我们知道是否应该创建大厅或加入大厅的方式。
  4. 您还将看到最后一个函数OnJoinedRoom具有对GameController脚本的引用以及对playerPrefab的引用。
  5. playerPrefab是代表体验中每个玩家的预制件。 接下来,我们必须做一个。

现在,我们需要进行一些更改,以确保Photon可以访问场景中的资产。

每当我们调用函数PhotonNetwork.Instantiate() (如在Launcher脚本中)时,该调用就会在Resources目录中查找预制件。 因此,我们需要创建该目录,并将几个预制件放入其中。 继续,在Assets下创建一个新文件夹,并将其命名为Resources

现在,让我们将PlayerCamera预制。 首先在层次结构[ GameObject> 3DObject> Cube ]中创建一个标准立方体。 将比例设置为( 0.1,0.1,0.2 )并将其命名为PlayerCamera 。 将这个新的PlayerCamera对象从层次结构中拖到 Resources文件夹,然后删除该对象在层次结构中的副本。

我们还需要移动并稍加修改上一个教程中创建的Sphere预制件。 将Sphere预制件从Assets / 6D SDK / Prefabs移到Assets / Resources中 。 移动后,选择它以显示其属性。 单击添加组件>光子网络>光子视图 这样可以确保一旦建立房间ID后就可以生成球体,并且两个玩家都可以看到它们。

因此,您的项目应如下所示:

在窗口底部,您可能会注意到GameController中的错误 没有加载或保存的定义。

我们将不再使用GameController ,因此让我们删除层次结构中的对象(无需删除脚本)。 但是,我们将创建一个名为GameControllerPhoton的新脚本,供PlayerCamera预制件使用。

  1. 右键单击Assets文件夹,然后选择Create> C#Script 将脚本命名为GameControllerPhoton。
  2. 新的脚本GameControllerPhoton.cs将自动保存到项目的Assets文件夹中。 在您喜欢的文本编辑器中打开GameControllerPhoton.cs并粘贴以下代码:
使用UnityEngine; 
使用UnityEngine.EventSystems;
使用System.Collections.Generic;
使用System.IO;
使用System.Runtime.InteropServices;
使用System.Text;
使用SixDegrees;
公共类GameControllerPhoton:Photon.MonoBehaviour
{
#if UNITY_IOS
[DllImport(“ __ Internal”)]
公共静态外部无效GetAPIKey(StringBuilder apiKey,int bufferSize);
#其他
公共静态无效的GetAPIKey(StringBuilder apiKey,int bufferSize){}
#万一
私有静态GameControllerPhoton _LocalPlayerInstance = null;
私有静态List 控制器=新List ();
公共静态GameControllerPhoton LocalPlayerInstance
{
得到
{
返回_LocalPlayerInstance;
}
}
私人相机ARCamera;
public GameObject ballReference;
私人GameObject球;
私有FileControl fileControl;
私有SDKController sdkController;
公共List 球;
私有静态字符串apiKey =“”;
私有字符串文件名;
公共布尔加载;
节省公共开支;
无效Start()
{
ARCamera = GameObject.FindObjectOfType ()。GetComponent ();
fileControl = GameObject.FindObjectOfType ();
sdkController = GameObject.FindObjectOfType ();
如果(photonView.isMine)
{
_LocalPlayerInstance = this;
sdkController.OnSaveSucceededEvent + = SaveCSV;
sdkController.OnLoadSucceededEvent + = RetrieveFile;
如果(保存==真)
{
SaveCSV();
}
如果(已加载== true)
{
RetrieveFile();
}
}
controllers.Add(this);
}
无效Update()
{
如果(Input.touchCount> 0)
{
LaunchBall();
}
}
无效LaunchBall()
{
触摸touch = Input.GetTouch(0);
如果(EventSystem.current.currentSelectedGameObject == null)
{
如果(touch.phase == TouchPhase.Began)
{
如果(photonView.isMine)
{
Vector3 position = new Vector3(Input.mousePosition.x,Input.mousePosition.y,.5f);
position = ARCamera.ScreenToWorldPoint(position);
SpawnBall(position);
}
}
}
}
[PunRPC]
无效SpawnBall(Vector3位置)
{
ball =(GameObject)PhotonNetwork.Instantiate(ballReference.name,position,Quaternion.identity,0);
ball.Add(球);
PhotonView photonView = PhotonView.Get(this);
如果(photonView.isMine)
photonView.RPC(“ SpawnBall”,PhotonTargets.OthersBuffered,position);
}
无效的OnDestroy()
{
controllers.Remove(this);
如果(photonView.isMine)
{
sdkController.OnSaveSucceededEvent-= SaveCSV;
sdkController.OnLoadSucceededEvent-= RetrieveFile;
}
}
私有void GetFilename()
{
如果(string.IsNullOrEmpty(apiKey))
{
StringBuilder sb =新的StringBuilder(32);
GetAPIKey(sb,32);
apiKey = sb.ToString();
}
如果(string.IsNullOrEmpty(apiKey))
{
Debug.Log(“找不到API密钥”);
filename =“”;
}
如果(string.IsNullOrEmpty(SDPlugin.LocationID))
{
Debug.Log(“缺少位置ID”);
filename =“”;
}
文件名= apiKey +“-” + SDPlugin.LocationID;
}
公共无效SaveCSV()
{
GetFilename();
如果(string.IsNullOrEmpty(filename))
{
Debug.Log(“评估文件名时出错,将不保存CSV内容”);
返回;
}
字符串filePath = GetPath();
StreamWriter writer =新的StreamWriter(filePath);
writer.WriteLine(controllers.Count);
for(int j = 0; j <controllers.Count; j ++)
{
writer.WriteLine(controllers [j] .balls.Count);
为(int i = 0; i <ball.Count; i ++)
{
writer.WriteLine(controllers [j] .balls [i] .transform.position.x +“,” +控制器[j] .balls [i] .transform.position.y +“,” +控制器[j] .balls [i] .transform.position.z);
}
}
writer.Flush();
writer.Close();
StartCoroutine(fileControl.UploadFileCoroutine(filename));
}
公共无效ReadTextFile(csv字符串)
{
StringReader reader =新的StringReader(csv);
字符串行= reader.ReadLine();
int controllersCount = int.Parse(line);
for(int k = 0; k <controllersCount; k ++)
{
行= reader.ReadLine();
int ballCount = int.Parse(line);
为(int i = 0; i <ballCount; i ++)
{
行= reader.ReadLine();
字符串[]部分= line.Split(',');
Vector3 ballPosition =新的Vector3();
ballPosition.x = float.Parse(parts [0]);
ballPosition.y = float.Parse(parts [1]);
ballPosition.z = float.Parse(parts [2]);
SpawnBall(ballPosition);
}
}
reader.Close();
}
公共字符串GetPath()
{
返回Application.persistentDataPath +“ /” + SDPlugin.LocationID +“ .csv”;
}
公共无效RetrieveFile()
{
GetFilename();
如果(string.IsNullOrEmpty(filename))
{
Debug.Log(“评估文件名时出错,将不会加载CSV内容”);
返回;
}
StartCoroutine(fileControl.GetTextCoroutine(filename));
}
}

现在,我们已经创建了一个新的GameController ,我们需要重新访问Launcher脚本并进行一些更改。 在启动器脚本中找到以下部分:

 公共重写void OnJoinedRoom() 
{
Debug.Log(“ DemoAnimator / Launcher:PUN调用的OnJoinedRoom()。现在此客户端在房间里。”);
GameObject temp = PhotonNetwork.Instantiate(playerPrefab.name,Vector3.zero,
Quaternion.identity,0);
如果(加载== true)
{
temp.GetComponent ()。loaded = true;
}
如果(保存==真)
{
temp.GetComponent ()。saving = true;
}
}
}

在本部分中,将GameController的所有实例更改GameControllerPhoton

我们还需要添加一些代码来管理每个玩家的摄像机位置。 右键单击Assets文件夹,然后选择“ 创建”>“ C#脚本”以创建新脚本。 将新脚本命名为PlayerCameraPositionPhoton 。 在您首选的编辑器中打开脚本并将以下代码放入其中:

 使用System.Collections; 
使用System.Collections.Generic;
使用UnityEngine;
使用SixDegrees;
使用光子
公共类PlayerCameraPositionPhoton:PunBehaviour
{
私人GameObject mSDCamera;
无效Start()
{
SDCamera sdCamera = FindObjectOfType ();
如果(!sdCamera)
{
Debug.LogWarning(“找不到SDCamera!”);
返回;
}
mSDCamera = sdCamera.gameObject;
}
无效Update()
{
如果(mSDCamera)
{
transform.SetPositionAndRotation(mSDCamera.transform.position,mSDCamera.transform.rotation);
}
}
}

现在,我们已经创建了应用程序所需的大多数代码,但是我们需要为我们拥有的对象添加一些组件和引用。 首先,转到“ 资产”>“资源”文件夹,然后选择我们之前制作的PlayerCamera预制件。 我们应该附加一些组件:

  1. PlayerCameraPositionPhoton ,我们刚刚创建的脚本。 这会将摄像机预制件直接放在每个电话的每个摄像机上。 这样一来,每个玩家都可以看到其他人的相机,并且还可以对重新定位的准确性有所了解。 要将脚本添加到预制件,只需将其从项目中的文件夹中拖放到PlayerCamera预制件的Inspector属性中即可
  2. GameControllerPhoton脚本。 现在,我们将不再使用场景中的单个GameController对象,而是使用Launcher游戏对象来生成它们。 将此脚本从项目中的文件夹拖放到PlayerCamera的Inspector属性中
    在“ 球形参考”字段中,确保插入“ 球形”预制件。
  3. PhotonView组件。 该组件由Photon提供。 要添加它,请单击添加组件按钮,然后选择光子网络>光子视图
  4. 光子变换视图组件。 与Photon View组件类似, Photon也提供此组件。 要添加它,请单击添加组件 按钮然后选择“ 光子网络>光子变换视图”
  5. 创建光子变换视图后 ,我们要将其拖动到“ 光子视图”组件的“ 观察组件”字段中。 您还需要选中“ 同步位置”和“ 同步旋转 ”框,如下图所示:

最后,我们需要对Launcher对象进行一些更改。 在层次结构中选择启动器对象,然后更改其属性以匹配下图:

差不多好了! 我们需要对FileControl脚本进行一些小的更改,然后就可以开始使用了。

更改:

  GameController.ReadTextFile(csv); 

至:

  GameControllerPhoton.LocalPlayerInstance.ReadTextFile(csv); 

另外,更改:

 字符串localFileName = GameController.GetPath(); 

至:

 字符串localFileName = GameControllerPhoton.LocalPlayerInstance.GetPath(); 

在构建和测试之前,有一些建议标记:

  • 这是一个简单的教程。 没有UI可以让用户知道保存或重新本地化是否成功。 建议至少一台设备保持与计算机的连接,以便您可以在Xcode中监视控制台。
  • 用于测试的两个设备都应连接到相同的wifi网络。
  • 请记住,只有在玩家保存地图(从而在光子中创建房间ID)后,您才能生成球体。

好的,现在是时候构建并运行该项目了。 如果您在构建6D应用程序时需要复习,可以在这里找到演练。

以下是如何使用该应用程序的流程:

  1. 播放器1启动该应用程序,扫描区域,然后单击“保存”按钮以保存地图。 这将跳过创建大厅的过程,并自动在Photon中创建一个以locationID作为房间名称的房间。
  2. 播放器2启动该应用程序,单击“加载”按钮,然后扫描区域以重新定位。 由于玩家1共享相同的locationID,因此这会自动加入玩家1的房间。
  3. 重新定位成功后,两个玩家都应该能够看到彼此连接到每个手机的PlayerCamera预制件。 玩家应了解预制件如何与每个手机的动作同步。
  4. 既然已经创建了房间,那么任何一个玩者都应该能够在世界上放置球体,并且两个玩者都可以看到它们的放置位置,因为它们共享相同的坐标空间。
  5. 玩家1或玩家2可以再次保存,这将保存自上次保存以来创建的所有新球体。
  6. 领域应该是持久的内容。 要进行验证,请在两个设备上关闭该应用程序。 在设备上再次打开应用程序,然后单击“加载”按钮以加载先前会话中的内容。 现在,玩家应该能够看到在上一个会话中放置的球体,并且可以根据需要添加更多的球体。

恭喜,您已完成基本的6D Multiplayer教程应用程序。 希望当您开始使用6D Reality平台建立多用户体验时,这将对您有所帮助。

如果您对重新定位,持久性或其他6D特定功能有疑问,请确保查看我们的开发人员懈怠和开发人员仪表板。 另外,您可以在此处找到有关Photon Unity Networking的更多信息。

建设愉快!