Unity 一起看+弹幕 Socket通信
效果演示
实现功能
- 发送弹幕
- 连接通信
- 共同更换视频
服务端
实现通信功能,我选择的是Scoket通信。
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace ChatRoomService
{
class Service
{
Socket socketSevice ;
List<Socket> userList;//用户组
public Service()
{
socketSevice = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
userList = new List<Socket>();
}
public void Start()
{
socketSevice.Bind(new IPEndPoint(IPAddress.Any,10000));//这里设置任何ip都可以连接
socketSevice.Listen(10);
Console.WriteLine("服务器启动成功");
//开启接受连接,用多线程
Thread accThread = new Thread(Accept);
accThread.IsBackground = true;
accThread.Start();
}
private void Accept()
{
//接受连接
Socket clientSocket = socketSevice.Accept();
userList.Add(clientSocket);
//打印已经连接IP地址
Console.WriteLine(IPToAddress(clientSocket)+"连接进来了");
//
Thread RecvThread = new Thread(ReceMessage);
RecvThread.IsBackground = true;
RecvThread.Start(clientSocket);
Accept();//递归
}
//接收客户端信息
private void ReceMessage(Object obj)
{
Socket client = obj as Socket;
byte[] strByte = new byte[1024];//设定接受字符的长度
string str = "";
try
{
int len = client.Receive(strByte);//接受用户发送的内容
str = Encoding.UTF8.GetString(strByte, 0, len);
if (str != "")
{
Broadcast(str,client);//广播给用户
Console.WriteLine(str);
}
}
catch (Exception e)
{
Console.WriteLine(IPToAddress(client)+"退出");
userList.Remove(client);
Thread.CurrentThread.Abort();//退出时掐死线程,不然递归反弹
}
ReceMessage(client); //使用递归
}
/// <summary>
/// 广播信息
/// </summary>
/// <param name="useStr">传入收到的传输的内容</param>
/// <param name="obj">传送信息的客户</param>
private void Broadcast(string userStr,object obj)
{
Socket clientSend = obj as Socket; //当前发送信息的客户
foreach (Socket client in userList)
{
if (client != clientSend)//将信息广播给其他用户
{
client.Send(Encoding.UTF8.GetBytes(userStr));
}
}
}
//转换出连来客户的IP地址
private string IPToAddress(Socket soket)
{
return (soket.RemoteEndPoint as IPEndPoint).Address.ToString();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChatRoomService
{
class Program
{
static void Main(string[] args)
{
Service ss = new Service();
ss.Start();
Console.ReadLine();
}
}
}
客户端
UI界面
Control
我将UI都放在Control空物体下,方便添加脚本统一管理。
RawIamge:用来播放视频,添加VideoPlayer组件
这里的TargetTexture是一个RenderTexture
记住这里的RenderTexture和RawImage大小设置成一样
SendMessage :是一个空物体,子物体是一个InputFiled用来读取输入的信息,还有一个Button用来发送信息
Last:上一集按钮
Next:下一集按钮
Together:一起看按钮
Panel
我们发送弹幕,弹幕都是在视频的上端,所以我就加一个Panel让在RawImage的上端位置,并且弹幕是从头出来才显示,到达另一端逐渐消失,所以为了达到这个效果
我的想法是每发送一条弹幕,我就生成一个Text从Panel右到左移动,所以Panel我家一个Mask组件达到遮罩效果,并且将生成Text做一个对象池,减小性能的消耗。并且Panel的Corol的透明度需要设置透明,但是不能为0,因为我要将Text成为Panel的子物体,方便管理,如果设置为0,Text就看不到了。这里必须将Text设置成Panel的子物体,因为Mask组件只能遮罩子物体。
我将Text设置成了预制体。
脚本
MessageFire
这个脚本最简单,我们给message这个预制体
我这个Message初始化位置就在这里,这样Message向左移动直接就会显示出来
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class MessageFire : MonoBehaviour
{
private int StartPosX = 1300;//初始化Message的位置,当然这个需要你根据你的项目自行设置
private int EndPosX = -1300;//同理这个是消失位置,x坐标达到-1300就会返回对象池中
private float speed = 150f;//设置的速度,自定义
void Start()
{
transform.localPosition=new Vector3(StartPosX,0,0);//当我们从对象池拿到后初始化他的位置
}
// Update is called once per frame
void Update()
{
//Message移动,到终点返回对象池
transform.localPosition=new Vector3(transform.localPosition.x-speed*Time.deltaTime,0,0);
if(transform.localPosition.x<EndPosX)
BarrageManager.GetInstace().RecyleMessageText(gameObject.GetComponent<Text>());
}
}
对象池BarrageManager
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class BarrageManager : MonoBehaviour
{
private int index = 0;//返回对对象池中的第index个对象
private GameObject message;//预制体
private List<GameObject> messageLists=new List<GameObject>();
private static BarrageManager Instance;//单例模式
public static BarrageManager GetInstace
{
get
{
return Instance;
}
}
private void Awake()
{
Instance = this;
message = Resources.Load<GameObject>("Prefab/Message");//获取预制体
}
private void Start()
{
InitiMessageText();//初始化对象池
}
/// <summary>
/// 返回一个Message
/// </summary>
/// <returns></returns>
public GameObject GetMessageText()
{
//这里我是按照对象池顺序,每次返回一个index就自加,这样就会每次判断对象池的下一个,这样我基本只会遍历判断一次就可以拿到没有显示的Message
//不是从头遍历,这样太消耗性能
for (int i = index% messageLists.Count; i < messageLists.Count; i++)
{
if (!messageLists[i].activeSelf)
{
//这里是判断i是否和index相等,按道理我们的i和index相同,但是有可能对象池没有禁用的,没有可以拿的,
//但是我们已经将index设置为0,但是对象池添加了10个,
//我们拿到的是第11个,下标i=10,所以导致i!=index,所以当不相等时我们将index=i
if (index != i)
index = i;
index++;
if (index == messageLists.Count)//默认一般情况当index等于对象池的个数就从头开始
index = 0;
return messageLists[i];
}
}
InitiMessageText();
return GetMessageText();//我们采用递归方式来进行返回一个Message
}
/// <summary>
/// 回收Message
/// </summary>
/// <param name="messagetext">我们的回收的是一个Text</param>
public void RecyleMessageText(Text messagetext)
{
messagetext.gameObject.transform.localPosition=Vector3.zero;
messagetext.gameObject.SetActive(false);
}
/// <summary>
/// 初始化10个MessageText做话框
/// </summary>
void InitiMessageText()
{
for (int i = 0; i < 10; i++)
{
GameObject text=Instantiate(message);
text.transform.SetParent(transform);
text.SetActive(false);
messageLists.Add(text);
}
}
}
ChatRoom客户端连接
//这里我们还没有连接,我们需要生成一个ChatRoom才会连接,所以我们当点击一起看按钮就会实现连接功能
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
namespace PXZ
{
public class ChatRoom
{
public string message = "";//接收到的信息,方便我们判断是否更换视频
public Socket clientSocket;//当我们关闭程序关闭连接
public ChatRoom()
{
clientSocket=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
}
public void Connected(string IP, int port)
{
clientSocket.Connect(IP, port);
Thread RecThread=new Thread(RecvMessage);
RecThread.IsBackground = true;
RecThread.Start();
}
//发送信息
public void Send(string str)
{
if(str!="")//防止发空信息
clientSocket.Send(Encoding.UTF8.GetBytes(str));
}
//接受信息
private void RecvMessage()
{
try
{
byte[] strByte=new byte[1024];
int len = clientSocket.Receive(strByte);
message = Encoding.UTF8.GetString(strByte, 0, len);
}
catch (Exception e)
{
Console.WriteLine(e);
Thread.CurrentThread.Abort();
throw;
}
RecvMessage();
}
}
}
ViewVideo
using System;
using System.Collections;
using System.Collections.Generic;
using PXZ;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;
using UnityEngine.Video;
public class ViewVideo : MonoBehaviour
{
#region 单例模式
private static ViewVideo Instance;
public static ViewVideo GetInstance
{
get
{
return Instance;
}
}
#endregion
//添加更换视频的片段
public List<VideoClip> clips=new List<VideoClip>();
public bool connect = false;//查看时候连接,因为连接和不连接我们的按钮发送的信息不同
private int index = 0;//视频下标
//都是Control的子物体获取组件
private VideoPlayer videoPlayer;
private Button last;
private Button next;
private Button send;
private Button togerher;
public ChatRoom client;
private void Awake()
{
Instance = this;
videoPlayer = transform.GetChild(0).GetComponent<VideoPlayer>();
last = transform.GetChild(2).GetComponent<Button>();
next = transform.GetChild(3).GetComponent<Button>();
togerher = transform.GetChild(4).GetComponent<Button>();
last.onClick.AddListener(LastButtonOnclik);
next.onClick.AddListener(NextButtonOnclik);
togerher.onClick.AddListener(TogetherButtonOnclik);
}
private void Update()
{
//当我们连接后才执行
if (connect)
{
//判断我们接受的信息不空也不是1和0我们就认为是一条弹幕,直接发送弹幕函数
if (client.message != "" && client.message!="1"&& client.message!="0")
{
SendMessageFire.Instace.TextFire(client.message);
}
//这里我们收到的信息是1和0后我们就默认是更换视频
if (client.message == "1" || client.message=="0")
{
ViewVideo.GetInstance.ViewPlayerChange(Convert.ToInt32(client.message));
}
client.message = "";//再讲我们收到的信息设置为为空,防止两个判断一致发生
}
}
private void OnDestroy()
{
//关闭连接
if (client != null)
{
client.clientSocket.Close();
}
}
/// <summary>
/// 我们这里更换视频有两种情况,当我们点击一起看连接后,和一种自己看
/// 当连接后我们发送一条1,接收方收到后就会根据信息来判断是不是更换视频了
/// 不连接啧index+1并更换视频
/// </summary>
private void NextButtonOnclik()
{
if (connect)
{
client.Send("1");
}
index++;
if (index == clips.Count)
index = 0;
videoPlayer.clip = clips[index];
}
private void LastButtonOnclik()
{
if(connect)
{
client.Send("0");
}
index--;
if (index < 0)
index = clips.Count - 1;
videoPlayer.clip = clips[index];
}
/// <summary>
/// 我们需要判断是client是否为空,防止点击多次Together按钮生成多个client;
/// </summary>
private void TogetherButtonOnclik()
{
if (client == null)
{
client=new ChatRoom();
client.Connected("127.0.0.1",10000);
connect = true;
}
}
/// <summary>
/// 这个是连接成功后更换视频的函数
/// </summary>
/// <param name="x">判断接收的是1还是0</param>
public void ViewPlayerChange(int x)
{
if (x == 1)
{
index++;
if (index == clips.Count)
index = 0;
}
if (x == 0)
{
index--;
if (index < 0)
index = clips.Count - 1;
}
videoPlayer.clip = clips[index];
}
}
SendMessageFire
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SendMessageFire : MonoBehaviour
{
#region 单例模式
private static SendMessageFire instace;
public static SendMessageFire Instace
{
get
{
return instace;
}
}
#endregion
//获取组件
private InputField messageInput;
private Button sendButton;
private string message;
private void Awake()
{
instace = this;
messageInput = transform.GetChild(0).GetComponent<InputField>();
sendButton = transform.GetChild(1).GetComponent<Button>();
sendButton.onClick.AddListener(SendButtonOnclik);
}
/// <summary>
/// 判断是否连接服务器,连接的话就给服务器发送信息,没有就直接发送一条弹幕
/// </summary>
void SendButtonOnclik()
{
message = messageInput.text;
messageInput.text = "";
if (message != "")
{
if (ViewVideo.GetInstance.connect)
{
ViewVideo.GetInstance.client.Send(message);
}
TextFire(message);
}
}
/// <summary>
/// 发送弹幕函数
/// </summary>
/// <param name="str">发送的信息</param>
public void TextFire(string str)
{
GameObject messageObject = BarrageManager.GetInstace.GetMessageText();
Text messageText= messageObject.GetComponent<Text>();
messageText.text = str;
messageObject.SetActive(true);
}
}
脚本添加
Control添加ViewVideo脚本,并给Clips初始化添加视频
SendMessage
Panel
问题
- 当两人观看视频不一致的时候,两者点击一起看,再点击上一集或下一集,但是无法保持观看视频一致
- 当两人连接一起看后,一旦一方断开连接关闭程序,服务器就会崩坏。
- 两人一起看,两人都可以更换视频,没有做到只有管理员才能更换视频。没有管理员功能
但是总体功能都已经实现