Unity模拟弹幕效果

Posted 御雪妃舞

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity模拟弹幕效果相关的知识,希望对你有一定的参考价值。

最近看到好多平台都有弹幕,觉得挺有意思,就做的试试看,但也有不少问题。


1.制作弹幕字体预制

新建一个unity工程,新建了一个Canvas的Text,然后制作脚本,主要用来字体移动效果和文本输入,建立的脚本叫TextItem,脚本内容如下:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class TextItem : MonoBehaviour

    /// <summary>
    /// text控件显示文字
    /// </summary>
    public string text="";

    /// <summary>
    /// 当前脚本所在的text控件
    /// </summary>
    private Text currentText;

    /// <summary>
    /// 弹幕移动的速度
    /// </summary>
    public float speed;

    void Start()
    
        //初始化
        currentText = GetComponent<Text>();

        //设置显示字体以及随机颜色
        currentText.text = text;
        currentText.color = Random.ColorHSV();

        //获取屏幕范围内的y坐标随机,这里没做屏幕适配,free aspect举列
        float y = Random.Range(-200f, 220f);
        transform.localPosition = new Vector3(550f, y, 0);
    

    

    void Update()
    
        if (speed != 0)
        
            float x = transform.localPosition.x + speed * Time.deltaTime;
            transform.localPosition = new Vector3(x, transform.localPosition.y, 1);

            //出屏幕销毁
            if (transform.localPosition.x < -550f)
            
                Destroy(gameObject);
            
        
    

然后把Text控件挂上脚本拖入Project中成为预制。

2.建立输入框,输入弹幕内容,生成弹幕

Canvas下建立unity自带的ugui带的InputField控件,并且建立脚本叫InputControl,脚本内容如下:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class InputControl : MonoBehaviour

    /// <summary>
    /// text预制获取
    /// </summary>
    public TextItem item;
    
    /// <summary>
    /// 设立弹幕父物体
    /// </summary>
    public Transform parent;

    /// <summary>
    /// 当前输入框控件
    /// </summary>
    private InputField inputField;


    // Use this for initialization
    void Start()
    
        //初始化
        inputField = GetComponent<InputField>();
    

    /// <summary>
    /// 输入完成,创建输入内容的弹幕
    /// </summary>
    public void OnSumbit()
    
        CreateItem(inputField.text);
        inputField.text = "";
    

    /// <summary>
    /// 实例化弹幕
    /// </summary>
    /// <param name="text">弹幕内容</param>
    public void CreateItem(string text)
    
        var temp = Instantiate(item) as TextItem;
        temp.transform.SetParent(parent);
        temp.text = text;
    


把脚本挂到InputField控件上,并且在InputField的脚本上设置On End Exit执行OnSumbit方法,并且拖拽InputControl脚本的预制和父物体内容,具体效果如图:

3.单机的弹幕效果完成

按照前面两步后,运行,就可以看到自己在输入框中输入内容,弹幕就飘出来了,然后继续输入,会继续飘出来,效果如图:


中英文都可以输入

当然这只是单机的效果。


4.延伸到服务器

这里我们把当前的项目端当做服务器端,制作一个本机的服务器和客户端的交互,当然楼主对服务器内容知道的很少,遇到了不少问题,具体的后面再说。
首先我们新建一个服务器脚本叫TcpServer,设置ip为本机的:127.0.0.1,端口假设为是10050,具体脚本内容如下:

Tcp脚本:


using UnityEngine;
using System.Collections;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;

public class TcpServer : MonoBehaviour

    /// <summary>
    /// 接受客户端消息字段
    /// </summary>
    private static byte[] result = new byte[1024];
    
    /// <summary>
    /// 设定端口
    /// </summary>
    private static int myProt = 10050;

    /// <summary>
    /// Socket服务
    /// </summary>
    static Socket serverSocket;

    /// <summary>
    /// 接受消息的文本
    /// </summary>
    public static String Info="";

    /// <summary>
    /// 所有线程的队列
    /// </summary>
    public static List<Thread> allThreads;

    void Awake()
    
        //初始化线程队列
        allThreads = new List<Thread>();
        allThreads.Clear();
    


    void Start()
    
        //服务器IP地址
        IPAddress ip = IPAddress.Parse("127.0.0.1");
        serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        serverSocket.Bind(new IPEndPoint(ip, myProt));  //绑定IP地址:端口
        serverSocket.Listen(10);    //设定最多10个排队连接请求
        Debug.Log("启动监听" + "成功" + serverSocket.LocalEndPoint.ToString());
        //通过Clientsoket发送数据
        Thread myThread = new Thread(ListenClientConnect);
        myThread.Start();
        allThreads.Add(myThread);
    

    /// <summary>
    /// 监听客户端连接
    /// </summary>
    private static void ListenClientConnect()
    
        while (true)
        
            Debug.Log("My Thread is running");
            Socket clientSocket = serverSocket.Accept();
            clientSocket.Send(Encoding.ASCII.GetBytes("Server Say Hello"));
            Thread receiveThread = new Thread(ReceiveMessage);
            receiveThread.Start(clientSocket);
            allThreads.Add(receiveThread);
        
    

    

    /// <summary>
    /// 接收消息
    /// </summary>
    /// <param name="clientSocket"></param>
    private static void ReceiveMessage(object clientSocket)
    
        Socket myClientSocket = (Socket)clientSocket;

        while (true)
        
            try
            
                Debug.Log("receiveThread is running");
                //通过clientSocket接收数据
                int receiveNumber = myClientSocket.Receive(result);
                Debug.Log(String.Format("接收客户端0消息1", myClientSocket.RemoteEndPoint.ToString(), Encoding.ASCII.GetString(result, 0, receiveNumber)));
                Info = Encoding.ASCII.GetString(result, 0, receiveNumber);

                //调用主线程方法出错,后使用临时解决方案,在Update里执行
                InputControl.msgReturn = true;
            
            catch (Exception ex)
            
                Debug.Log(ex.Message);
                myClientSocket.Shutdown(SocketShutdown.Both);
                myClientSocket.Close();
                break;
            
        
    

    /// <summary>
    /// 关闭所有的线程,并关闭服务
    /// </summary>
    private static void ServerClosed()
    
        Debug.Log("总共有" + allThreads.Count + "个线程在运行");
        foreach (var item in allThreads)
        
            item.Abort();
            Debug.Log(item.Name + "   is stop");

        

        serverSocket.Close();
    

    void OnDestroy()
    
        Debug.Log("server closed");
        ServerClosed();
    



当然此时的InputControl有所改动,改后的内容如下:

InputControl脚本:


using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class InputControl : MonoBehaviour

    /// <summary>
    /// text预制获取
    /// </summary>
    public TextItem item;
    
    /// <summary>
    /// 设立弹幕父物体
    /// </summary>
    public Transform parent;

    /// <summary>
    /// 当前输入框控件
    /// </summary>
    private InputField inputField;

    /// <summary>
    /// 客户端是否有消息发送
    /// </summary>
    public static bool msgReturn = false;

    // Use this for initialization
    void Start()
    
        //初始化
        inputField = GetComponent<InputField>();
    

    

    void Update()
    
        //监测客户端消息发送,根据消息实例化弹幕
        if (msgReturn)
        
            msgReturn = false;
            CreateItem(TcpServer.Info);
        
    

    /// <summary>
    /// 输入完成,创建输入内容的弹幕
    /// </summary>
    public void OnSumbit()
    
        CreateItem(inputField.text);
        inputField.text = "";
    

    /// <summary>
    /// 实例化弹幕
    /// </summary>
    /// <param name="text">弹幕内容</param>
    public void CreateItem(string text)
    
        var temp = Instantiate(item) as TextItem;
        temp.transform.SetParent(parent);
        temp.text = text;
    

在场景中新建一个空物体,挂上TcpServer脚本,这里我们用了一个tcp分析工具模拟了一个客户端:

运行流程如下:


首先运行,发现服务器启动



然后运行分析器工具:


注意红框的地方设置好客户端,ip,端口后点击,建立连接
点击后,连接成功,服务器返回了消息,跟你说Hello



然后我们在下面的框中输入:Hello world,然后点击发送数据,如图:


客户端这边显示的是发送成功了。
我们再到Unity服务器端看下它是否接受到了:
看如下图片内容打印的日志,服务器是接受到了


Game界面也飘出了我们发送的字体弹幕如图:


到这里,我们小小的客户端和服务器的互动功能就完成了,你也可以不用Tcp工具,复制工程一份,写一个客户端的脚本,另一个工程当做客户端在输入框中输入消息,不仅客户端自己可以看到,而且服务器端的工程也能看到。
当然这里由于不是很懂Socket的原因也遇到了不少问题。


5.遇到的问题

a.线程问题

在服务器端的Receive线程中无法调用主线程的方法,比如我用InputControl的单列来调用它的方法就会报这样的错:


目前采用的是临时解决方案,根据布尔值在Update里调用:


知道 其它解决方案的大神,希望多多指教。

b.客户端发送中文乱码问题

比如,我在客户端工具中发送中文如图:

然后Untiy里接受和显示出来的都是问号:


c.线程停不掉的问题

当我tcp分析器,断开连接后,服务器端的Receive脚本的线程一直在每帧执行,即使unity停止运行了还是如此:
如图所示的脚本receive方法一直在执行:

但是结束的时候明明关闭了服务且停止了所有线程:

以上三个问题拜托会的人能交流解答下,谢谢!


工程源码下载

以上是关于Unity模拟弹幕效果的主要内容,如果未能解决你的问题,请参考以下文章

unity 怎么判断物体是不是在摄像机范围内

unity3d场景里面放个物体怎么看不见

如图,如何在Unity中判断一个物体自身的旋转是不是处于某个特定范围内?

unity实现框选效果

小松教你手游开发unity实用技能计算目标物体是否在自己的扇形视野范围

unity 如何知道物体是否在椭圆范围内?