记录一下学习联机的过程
更新一下:这个方案太麻烦了,团结的UOS做的比unity好太多,如果可以的话换团结吧,我用得很爽
先看效果:一个unity一个打包程序联机,运行起来挺流畅的这里gif帧率低
使用的是Photon的fusion的server模式
以下AI分析:
联机方案(团结引擎好像有一种后面研究一下):
产品对比:
联机模式:
我觉得fusion的server更满足我的需求就选这个了(想要延迟低一点,响应快,竞技游戏)
先介绍一下,photon他们是自己有服务器的,他们有photon cloud和photon server(服务器模式),因为他们服务器毕竟在国外,如果国内连的话延迟会有点高,所以我想着能不能使用国内的云端服务器来降低时延(理论上是降低的,不过没试过,国内应该好一点),这个方法需要支付腾讯云服务器的费用和使用photon的费用(人数和流量),photon官方文档说了即使不使用他们的photon server也要有photon cloud 订阅(图片下面)
正好有腾讯云的cvm服务器的试用和photon的20个人以内免费,所以我就来试试了
思路:
就是把unity项目打包成一个无头模式(不用渲染画面之类的)的exe文件,放在腾讯云终端上面运行(作为服务端),然后unity和正常打包的exe就可以作为客户端连接到一个房间内,如果你按照这样做的话,后面还有两种选择,一个是云端上面运行一个exe同时存在有多个房间(一个进程多房间),或者运行多个exe,每一个房间对应一个exe(多进程多房间),各有优缺点,这个多房间我还没搞,我这里只讲如何连接上
操作:
1:host模式和server模式区别在服务端不能同时作为客户端没有写入能力,所以我们直接下载host模式的案例教程(每个正方体是一个玩家,可以发射子弹)
链接:Fusion 2 - Host Mode Overview | Photon Engine
2:把文件夹导入工程后,(先测试一下host案例模式是否能够正常运行),修改里面的BasicSpawner脚本,将里面的host换成server等(代码末尾给出)
3:点击菜单栏:工具-fusion-fusion hub,然后修改photon app setting配置
你的Appid:Your Photon Cloud | Photon Engine
4:按照官网所说,中国大陆比较特殊,需要发邮件申请(这步我不确定反正我做了,没多久就回了)
官方文档:Fusion 2 - 区域 |光子引擎 --- Fusion 2 - Regions | Photon Engine
邮件地址:hello@photonengine.com
我用网易邮箱发了个:
Based on your documents, I apply to unlock the appid for the Chinese mainland region. Are there any conditions required,This is my current appid:你的appid
然后回了两封,第一封说尽快
第二封说解锁
5:将修改好的项目,保存场景,将Game场景拖入项目设置当中打包成无头模式
6:按照腾讯云的规则把无头模式打包的所有文件拖入云端,然后控制台运行
.\NetTest.exe -batchmode -nographics -port 5055 -logFile server.log
netstat -ano | Select-String "5055"
7:成功监听端口后,在unity,点击播放的join就可以看到生成了一个预制体,如果你再运行一下正常打包程序的exe的join,又能看到一个
总结:
还是要多看官方文档,一开始没找到关于fusion和服务器对于中国大陆的限制,网络连接卡半天
好了结束了
觉得有用的话点个赞再走呗OvO
代码:
using System;
using System.Collections.Generic;
using Fusion;
using Fusion.Addons.Physics;
using Fusion.Sockets;
using UnityEngine;
using UnityEngine.SceneManagement;
///
/// 注:很多逻辑没有实现,但是可以连接,需要的话自己按官网改
///
public class BasicSpawner : MonoBehaviour, INetworkRunnerCallbacks
{
private NetworkRunner _runner;
private void Start()
{
Debug.Log("BasicSpawner.Start() called - batchmode=" + Application.isBatchMode);
//如果是无图形模式(云端运行的 exe)
if (Application.isBatchMode)
{
Debug.Log("Detected headless mode: starting as Host Server...");
StartGame(GameMode.Server, true);
}
}
private void OnGUI()
{
if (!Application.isBatchMode && _runner == null)
{
if (GUI.Button(new Rect(0, 0, 200, 40), "Host"))
StartGame(GameMode.Server);
if (GUI.Button(new Rect(0, 40, 200, 40), "Join"))
StartGame(GameMode.Client);
}
}
async void StartGame(GameMode mode, bool isDedicatedServer = false)
{
// Create the Fusion runner and let it know that we will be providing user input
_runner = gameObject.AddComponent
_runner.ProvideInput = !isDedicatedServer;
_runner.AddCallbacks(this);
var runnerSimulatePhysics3D = gameObject.AddComponent
runnerSimulatePhysics3D.ClientPhysicsSimulation = ClientPhysicsSimulation.SimulateAlways;
// Create the NetworkSceneInfo from the current scene
var scene = SceneRef.FromIndex(SceneManager.GetActiveScene().buildIndex);
var sceneInfo = new NetworkSceneInfo();
if (scene.IsValid)
{
sceneInfo.AddSceneRef(scene, LoadSceneMode.Additive);
}
var args = new StartGameArgs
{
GameMode = mode,
SessionName = "TestRoom",
SceneManager = gameObject.AddComponent
};
//如果是服务端,监听所有IP地址的5055端口
if (mode == GameMode.Server && isDedicatedServer)
{
args.Address = NetAddress.Any(5055);
args.Scene = scene;
Debug.Log("Starting Dedicated Server on port 5055...");
}
//客户端
if (mode == GameMode.Client)
{
// 改成你云主机的公网 IP
//args.Address = NetAddress.CreateFromIpPort("43.139.104.215", 5055);
Debug.Log("StartGame: client will connect to Address = " + (args.Address?.ToString() ?? "null"));
}
Debug.Log("StartGame: StartGameArgs: " + args);
await _runner.StartGame(args);
}
[SerializeField]
private NetworkPrefabRef _playerPrefab; // Character to spawn for a joining player
private Dictionary
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
if (runner.IsServer)
{
// Create a unique position for the player
Vector3 spawnPosition = new Vector3((player.RawEncoded % runner.Config.Simulation.PlayerCount) * 3, 1, 0);
NetworkObject networkPlayerObject = runner.Spawn(_playerPrefab, spawnPosition, Quaternion.identity, player);
// Keep track of the player avatars for easy access
_spawnedCharacters.Add(player, networkPlayerObject);
}
}
public void OnPlayerLeft(NetworkRunner runner, PlayerRef player)
{
if (_spawnedCharacters.TryGetValue(player, out NetworkObject networkObject))
{
runner.Despawn(networkObject);
_spawnedCharacters.Remove(player);
}
}
private bool _mouseButton0;
private bool _mouseButton1;
private void Update()
{
_mouseButton0 = _mouseButton0 || Input.GetMouseButton(0);
_mouseButton1 = _mouseButton1 || Input.GetMouseButton(1);
}
public void OnInput(NetworkRunner runner, NetworkInput input)
{
var data = new NetworkInputData();
if (Input.GetKey(KeyCode.W))
data.direction += Vector3.forward;
if (Input.GetKey(KeyCode.S))
data.direction += Vector3.back;
if (Input.GetKey(KeyCode.A))
data.direction += Vector3.left;
if (Input.GetKey(KeyCode.D))
data.direction += Vector3.right;
data.buttons.Set(NetworkInputData.MOUSEBUTTON0, _mouseButton0);
_mouseButton0 = false;
data.buttons.Set(NetworkInputData.MOUSEBUTTON1, _mouseButton1);
_mouseButton1 = false;
input.Set(data);
}
public void OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input){}
public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason){}
public void OnConnectedToServer(NetworkRunner runner){}
public void OnDisconnectedFromServer(NetworkRunner runner, NetDisconnectReason reason){Debug.LogWarning($"Disconnected: {reason}");}
public void OnConnectRequest(NetworkRunner runner, NetworkRunnerCallbackArgs.ConnectRequest request, byte[] token){}
public void OnConnectFailed(NetworkRunner runner, NetAddress remoteAddress, NetConnectFailedReason reason)
{Debug.LogError($"Connect failed to {remoteAddress}: {reason}"); }
public void OnUserSimulationMessage(NetworkRunner runner, SimulationMessagePtr message){}
public void OnSessionListUpdated(NetworkRunner runner, List
public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary
public void OnHostMigration(NetworkRunner runner, HostMigrationToken hostMigrationToken){}
public void OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ReliableKey key, ArraySegment
public void OnReliableDataProgress(NetworkRunner runner, PlayerRef player, ReliableKey key, float progress){}
public void OnSceneLoadDone(NetworkRunner runner){}
public void OnSceneLoadStart(NetworkRunner runner){}
public void OnObjectExitAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player) {}
public void OnObjectEnterAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player) {}
}