局域网游戏
最近看了下关于unity的网络模块netwrok即将淘汰,思索了下准备自己用socket去封装个局域网的通讯插件
看了一下关于同步问题
https://www.jianshu.com/p/fbd8eda9df62
然后局域网游戏,外挂基本上靠的是玩家自觉,而且也不需要自己搞个服务器
-
房间系统:房主是服务器也是客户端
-
随机数的生成保持一至:统一的随机种子(并且要保持种子的调用次数一至)
-
连接后玩家操作通讯只发送操作指令
-
每隔一段时间进行状态同步(针对每个需要同步的组件数据写一个组件)
实现步骤:
1、实现连接与通讯
2、制作根据业务需求的封装
首先第一步:实现连接与通讯
该组件能做最基础的房间系统和聊天室,稍微封装可以满足各种需求
当然胡来也是会有bug的,因为没有真正去用过
组件GameNetWorkManager作为Unity访问的核心类
//连接相关API
ServerDataList:可以获取局域网内的所有服务器数据
CreateServer:传进服务器名字和最大连接数,可以创建一个服务器
ConnectServer:传进服务器数据,可以连接服务器
//通讯相关API
onreceive(事件):绑定该事件后,服务器接受到消息时将获取该消息,即收到消息时回调
SendData:发送给服务器的内的所有客户端
代码:
一共两个文件
GameNetWorkManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameNetWorkManager : MonoBehaviour
{
private GameClient gameClient;
private ServerData serverData;
private GameServer gameServer;
private ServerDataList serverDataList;
public ServerData ServerData { get => serverData; }
public GameClient GameClient { get => gameClient; }
public GameServer GameServer { get => gameServer; }
public ServerDataList ServerDataList { get => serverDataList; }
/// <summary>
/// 当收到服务器消息时
/// </summary>
public event System.Action<string> OnReceive;
/// <summary>
/// 创建服务器
/// </summary>
/// <param name="serverName"></param>
/// <param name="maxNumber"></param>
public void CreateServer(string serverName, int maxNumber)
{
serverData = new ServerData(serverName, maxNumber);
gameServer = new GameServer(serverData);
gameClient.ConnectServer(serverData, (ServerData ServerData) =>
{
//初始化随机种子
Random.InitState(ServerData.randomSeed);
},
(string message) =>
{
OnReceive(message);
});
}
/// <summary>
/// 连接服务器
/// </summary>
/// <param name="serverData"></param>
public void ConnectServer(ServerData serverData)
{
gameClient.ConnectServer(serverData, (ServerData ServerData) =>
{
//初始化随机种子
Random.InitState(ServerData.randomSeed);
},
(string message) =>
{
OnReceive(message);
});
}
public void SendData(string message)
{
gameClient.SendMessageData(message);
}
private void Awake()
{
//创建客户端
gameClient = new GameClient();
//扫描服务器(即收到广播就进行数据更新)
GameClient.CheckOutServerList((ServerDataList serverDataList) =>
{
this.serverDataList = serverDataList;
});
}
private void OnDestroy()
{
if (gameClient != null)
{
gameClient.Close();
}
if (gameServer != null)
{
gameServer.Close();
}
}
}
GameNetWork.cs
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
[System.serializable]
public class ServerDataList
{
public List<ServerData> list = new List<ServerData>();
internal void Add(ServerData serverData)
{
list.Add(serverData);
}
internal void Remove(ServerData serverData)
{
list.RemoveAll((ServerData item) =>
{
return serverData.IP == item.IP && serverData.port == item.port;
});
}
internal void Update(ServerData serverData)
{
foreach (var item in list)
{
if (serverData.IP == item.IP && serverData.port == item.port)
{
item.name = serverData.name;
item.number = serverData.number;
item.maxNumber = serverData.maxNumber;
}
}
}
}
[System.Serializable]
public class ServerData
{
public string name;
public int number;
public int maxNumber;
public string IP;
public int port;
/// <summary>
/// 随机种子
/// </summary>
public int randomSeed;
public ServerData()
{
randomSeed = new System. Random().Next(0, 99999);
}
public ServerData(string name, int maxNumber)
{
this.name = name;
this.maxNumber = maxNumber;
randomSeed = new System.Random().Next(0, 99999);
}
}
/// <summary>
/// 游戏数据包
/// </summary>
[System.Serializable]
public struct GameNetPacket
{
public string name;
public string data;
}
public interface IGameNetWorkBase
{
void Close();
}
public interface IGameServer : IGameNetWorkBase
{
}
public interface IGameClient : IGameNetWorkBase
{
void CheckOutServerList(Action<ServerDataList> OnCheckOut);
void ConnectServer(ServerData serverData, Action<ServerData> OnConnect, Action<string> OnReceive);
void SendMessageData(string data);
}
/// <summary>
/// 游戏服务器
/// </summary>
public class GameServer : IGameServer
{
public ServerData serverData;
/// <summary>
/// 服务器套接字
/// </summary>
Socket ServerSocket;
/// <summary>
/// 客户端的套接字
/// </summary>
List<Socket> ClientSockets = new List<Socket>();
const int minPort = 2725;
const int maxPort = 2730;
const int radiateMinPort = 2730;
const int radiateMaxPort = 2735;
private bool open;
/// <summary>
/// 循环客户端的信息
/// </summary>
/// <param name="socket"></param>
private void LoopClientMessage(Socket socket)
{
ClientSockets.Add(socket);
//监听客户端发送信息,并且进行广播
new Thread(() =>
{
while (open)
{
try
{
byte[] receive = new byte[1024];
//等待获取信息
socket.Receive(receive);
//进行广播发送
foreach (var item in ClientSockets)
{
item.Send(receive);
}
}
catch (Exception)
{
Debug.Log("断开连接...");
Close();
}
}
}).Start();
}
public GameServer(ServerData serverData)
{
this.serverData = serverData;
open = true;
//服务器初始化
// 侦听所有网络客户接口的客活动
//使用指定的地址簇协议、套接字类型和通信协议
ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
BindServerSocket(minPort);
//设定最多玩家数量
ServerSocket.Listen(this.serverData.maxNumber);
// string ipStr = Ipaddress.ToString();
Debug.Log("服务器初始化完成");
SendServerDataToAll("UpdateServer");
//接受广播信息
Socket receiveSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPEndPoint sep = BindReceiverSocket(receiveSocket, radiateMinPort);
EndPoint ep = (EndPoint)sep;
//接收广播线程
new Thread(() =>
{
while (open)
{
byte[] message = new byte[1024];//设置缓冲数据流
receiveSocket.ReceiveFrom(message, ref ep);//接收数据,并确把数据设置到缓冲流里面
string data = Encoding.unicode.GetString(message).TrimEnd('\u0000');
GameNetPacket receiveData = JsonUtility.FromJson<GameNetPacket>(data);
switch (receiveData.name)
{
case "Is Room?":
GameNetPacket gameNetPacket = new GameNetPacket();
gameNetPacket.name = "NewServer";
gameNetPacket.data = JsonUtility.ToJson(serverData);
SendReceiveTo(JsonUtility.ToJson(gameNetPacket));
break;
default:
break;
}
}
}).Start();
//监听客户端连接线程
new Thread(() =>
{
while (open)
{
try
{
//与客户的排队建立连接(堵塞)
Socket socket = ServerSocket.Accept();
LoopClientMessage(socket);
Debug.Log("连接成功");
////进行广播更新房间信息
SendServerDataToAll("UpdateServer");
}
catch (Exception)
{
Debug.Log("停止等待客户端连接");
open = false;
}
}
}).Start();
}
private void SendServerDataToAll(string receiveType)
{
GameNetPacket gameNetPacket = new GameNetPacket();
gameNetPacket.name = receiveType;
gameNetPacket.data = JsonUtility.ToJson(serverData);
SendReceiveTo(JsonUtility.ToJson(gameNetPacket));
}
private static void SendReceiveTo(string data)
{
//发送广播等待传输回来房间信息
//初始化一个发送广播和指定端口的网络端口实例
Socket sendSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//设置该scoket实例的发送形式
sendSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
for (int i = radiateMinPort; i < radiateMaxPort; i++)
{
IPEndPoint iep = new IPEndPoint(IPAddress.Broadcast, i);
byte[] buffer = Encoding.Unicode.GetBytes(data);
//发送
sendSocket.SendTo(buffer, iep);
}
}
private static IPEndPoint BindReceiverSocket(Socket receiveSocket, int port)
{
IPEndPoint sep = null;
try
{
sep = new IPEndPoint(IPAddress.Any, port);//初始化一个侦听局域网内部所有IP和指定端口
receiveSocket.Bind(sep);//绑定这个实例
}
catch (Exception)
{
port++;
if (port < radiateMaxPort)
{
BindReceiverSocket(receiveSocket, port);
}
else
{
throw;
}
}
return sep;
}
private void BindServerSocket(int port)
{
try
{
IPEndPoint IPEndPoint = new IPEndPoint(IPAddress.Any, port);
//绑定IP地址和端口号
ServerSocket.Bind(IPEndPoint);
string hostName = Dns.GetHostName(); //获取本机名
IPAddress[] localhosts = Dns.GetHostAddresses(hostName);
foreach (var item in localhosts)
{
if (item.ToString().indexof("192.168") != -1)
{
serverData.IP = item.ToString();
break;
}
}
serverData.port = port;
}
catch (Exception)
{
port++;
if (port < maxPort)
{
BindServerSocket(port);
}
else
{
throw;
}
}
}
public void Close()
{
open = false;
//发送关闭房间信号
GameNetPacket gameNetPacket = new GameNetPacket();
gameNetPacket.name = "CloseServer";
gameNetPacket.data = JsonUtility.ToJson(serverData);
SendReceiveTo(JsonUtility.ToJson(gameNetPacket));
//关闭服务器socket
ServerSocket.Close();
//关闭所有客户端的socket
foreach (var item in ClientSockets)
{
item.Close();
}
}
}
/// <summary>
/// 客户端 封装部分
/// </summary>
public partial class GameClient
{
/// <summary>
/// 广播端口
/// </summary>
const int receiveMinPort = 2730;
const int receiveMaxPort = 2735;
/// <summary>
/// 广播接收socket
/// </summary>
private Socket radiateSocket;
/// <summary>
/// 客户端的socket
/// </summary>
private Socket clientSocket;
private EndPoint ep;
private bool openCheckOutServer = true;
/// <summary>
/// 广播发送一条信息
/// </summary>
/// <param name="data"></param>
private static void SendReceiveTo(string data)
{
//发送广播等待传输回来房间信息
//初始化一个发送广播和指定端口的网络端口实例
Socket sendSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//设置该scoket实例的发送形式
sendSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
for (int i = receiveMinPort; i < receiveMaxPort; i++)
{
IPEndPoint iep = new IPEndPoint(IPAddress.Broadcast, i);
byte[] buffer = Encoding.Unicode.GetBytes(data);
//发送
sendSocket.SendTo(buffer, iep);
}
}
private void CloseCheckOutServer()
{
openCheckOutServer = false;
radiateSocket.Close();
radiateSocket = null;
}
private static IPEndPoint BindReceiverSocket(Socket receiveSocket, int port)
{
IPEndPoint sep = null;
try
{
sep = new IPEndPoint(IPAddress.Any, port);//初始化一个侦听局域网内部所有IP和指定端口
receiveSocket.Bind(sep);//绑定这个实例
}
catch (Exception)
{
port++;
if (port < receiveMaxPort)
{
BindReceiverSocket(receiveSocket, port);
}
else
{
throw;
}
}
return sep;
}
}
/// <summary>
/// 客户端 接口实现部分
/// </summary>
public partial class GameClient : IGameClient
{
private bool connecting;
public GameClient()
{
radiateSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);//初始化一个Scoket协议
IPEndPoint sep = BindReceiverSocket(radiateSocket, receiveMinPort);//初始化一个侦听局域网内部所有IP和指定端口
ep = (EndPoint)sep;//绑定这个实例
}
/// <summary>
/// 查看服务器列表
/// </summary>
/// <param name="OnCheckOut"></param>
public void CheckOutServerList(Action<ServerDataList> OnCheckOut)
{
openCheckOutServer = true;
Debug.Log("查找房间");
//广播发送一个数据
SendReceiveTo(JsonUtility.ToJson(new GameNetPacket() { name = "Is Room?", data = "" }));
ServerDataList serverDataList = new ServerDataList();
new Thread(() =>
{
while (openCheckOutServer)
{
try
{
byte[] message = new byte[1024];//设置缓冲数据流
radiateSocket.ReceiveFrom(message, ref ep);//接收数据,并确把数据设置到缓冲流里面
string json = Encoding.Unicode.GetString(message).TrimEnd('\u0000');
GameNetPacket gameNetPacket = JsonUtility.FromJson<GameNetPacket>(json);
switch (gameNetPacket.name)
{
case "NewServer":
serverDataList.Add(JsonUtility.FromJson<ServerData>(gameNetPacket.data));
break;
case "CloseServer":
serverDataList.Remove(JsonUtility.FromJson<ServerData>(gameNetPacket.data));
break;
case "UpdateServer":
serverDataList.Update(JsonUtility.FromJson<ServerData>(gameNetPacket.data));
break;
default:
break;
}
OnCheckOut(serverDataList);
}
catch (Exception)
{
throw;
}
}
}).Start();
}
/// <summary>
/// 关闭客户端
/// </summary>
public void Close()
{
if (openCheckOutServer)
{
CloseCheckOutServer();
}
if (connecting)
{
CloseConnecting();
}
}
private void CloseConnecting()
{
connecting = false;
clientSocket.Close();
}
public void SendMessageData(string data)
{
if (clientSocket == null)
{
Debug.Log("未连接服务器");
return;
}
byte[] message = Encoding.UTF8.GetBytes(data); //通信时实际发送的是字节数组,所以要将发送消息转换字节
clientSocket.Send(message);
}
/// <summary>
/// 连接至服务器
/// </summary>
/// <param name="serverData"></param>
/// <param name="OnConnect"></param>
/// <param name="OnReceive"></param>
public void ConnectServer(ServerData serverData, Action<ServerData> OnConnect, Action<string> OnReceive)
{
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//使用指定的地址簇协议、套接字类型和通信协议
IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(serverData.IP), serverData.port); // 用指定的ip和端口号初始化IPEndPoint实例
clientSocket.Connect(serverEndPoint);
new Thread(() =>
{
connecting = true;
byte[] receive = new byte[1024];
while (connecting)
{
Debug.Log("等待消息");
int length = clientSocket.Receive(receive); // length 接收字节数组长度
OnReceive(Encoding.UTF8.GetString(receive));
}
}).Start();
}
}
相关阅读
三子棋:在3*3的棋盘中,谁先用自己的棋走出一条直线(横着三个/竖着三个/对角线三个),谁就算赢!下面我们就来一起编写一下这个小游戏吧!!!首
观察了蚂蚁森林有一阵子,说说我的看法。过去毕竟总戴着有色眼镜看支付宝,所以这个产品刚出来时,没有体验的欲望。过了一阵子,身边大家
暂停前言功能实现展示前言 在游戏中设置游戏暂停,经常会想到 Time.timeScale = 0; 这种方法,但是 Time.timeScale 只是能暂停部分东
2019年春节,百度出了一款砸金猪积福气的AR小游戏吗,并获得了非常不错的互动数据以及用户的称赞。而本文就还原了百度AR团队如何做出
转自 http://blog.csdn.net/qq_32353771/article/details/53899167开源Java小游戏前言看到标题可能有人要笑我,用Java写游戏?没办