카테고리 없음

유니티에서 ZeroMQ 사용하기

Wood Pecker 2021. 8. 11. 21:00

1. 개요

유니티와 외부 프로그램 사이에 데이터를 교환하기 위하여 ZeroMQ는 매우 유용하다.

 

2. 유니티에 ZeroMQ 설치하기

윈도우즈의 Visual Studio 프로젝트에 ZeroMQ 라이브러리는 Nuget을 이용하여 설치할 수 있다.

그러나 유니티에서는 이를 지원하지 않는 관계로 다른 프로젝트에 설치된 dll 파일을 유니티 프로젝트의 플러그 인 폴더에 복사하는 방식으로 설치를 할 수 있다. 그러므로 아래 샘플 프로젝트를 다운받고 필요한 파일을 내 프로젝트에 복사한다. 이 곳( https://www.nuget.org/packages/NetMQ/ ) 에서 받은 파일 압축을 해제하고, 2개의 파일
NetMQ.dll AsyncIo.dll 위 파일을 자신의 프로젝트 Assets/Plugins에 복사한다.

 

3. 파이썬 프로그램( publisher )
유니티가 데이터를 받아 볼 수 있도록 파이썬 publisher 를 만든다. 

import zmq
import sys
import random
import time

if __name__ == '__main__':
    print("Current libzmq version is %s" % zmq.zmq_version())  # 4.2.5 at time of writing
    print("Current  pyzmq version is %s" % zmq.__version__)  # 17.1.2 at time of writing
    context = zmq.Context()
    socket = context.socket(zmq.PUB)  # PUB/SUB Model
    socket.bind("tcp://*:1961")
    seq=0
    while True:
        message = str(random.uniform(-1.0, 1.0)) + " " + str(random.uniform(-1.0, 1.0))
        message="hello "+str(seq)+" {"+message+"}"
        print(message)
        socket.send_string(message)
        time.sleep(1)
        seq=((seq+1) % 100000)
    pass

 

 

4. 유니티 프로그램 (Subscriber)

using UnityEngine;
using NetMQ;
using NetMQ.Sockets;
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Threading;
using System.Text;
using Newtonsoft.Json.Linq;

public class MyUnityZeroMQ : MonoBehaviour
{
    public enum ClientStatus
    {
        Inactive,
        Activating,
        Active,
        Deactivating
    }
    public string sub_to_ip = "127.0.0.1";
    public string sub_to_port = "1961";
    private Listener _listener;
    private ClientStatus _clientStatus = ClientStatus.Inactive;

    private void Start()
    {
        _listener = new Listener(sub_to_ip, sub_to_port, HandleMessage);
        _listener.Start();
        _clientStatus = ClientStatus.Active;
        Debug.Log("Client started!");
    }

    private void Update()
    {
        if (_clientStatus == ClientStatus.Active)
            _listener.DigestMessage();
    }

    private void OnDestroy()
    {
        if (_clientStatus != ClientStatus.Inactive)
            OnStopClient();
    }

    private void HandleMessage(string message)
    {
        Debug.Log(message);
    }

    private void OnStopClient()
    {
        Debug.Log("Stopping client...");
        _clientStatus = ClientStatus.Deactivating;
        _listener.Stop();
        Debug.Log("Client stopped!");
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////
    public class Listener
    {
        private Thread _clientThread;
        private readonly string _host;
        private readonly string _port;
        private readonly Action<string> _messageCallback;
        private bool _clientCancelled;
        private readonly ConcurrentQueue<string> _messageQueue = new ConcurrentQueue<string>();

        public Listener(string host, string port, Action<string> messageCallback)
        {
            _host = host;
            _port = port;
            _messageCallback = messageCallback;
        }

        public void Start()
        {
            _clientCancelled = false;
            _clientThread = new Thread(ListenerWork);
            _clientThread.Start();
        }

        public void Stop()
        {
            _clientCancelled = true;
            _clientThread?.Join();
            _clientThread = null;
        }

        private void ListenerWork()
        {
            AsyncIO.ForceDotNet.Force();
            using (var subSocket = new SubscriberSocket())
            {
                subSocket.Options.ReceiveHighWatermark = 1000;
                subSocket.Connect($"tcp://{_host}:{_port}");
                subSocket.SubscribeToAnyTopic();
                while (!_clientCancelled)
                {
                    if (!subSocket.TryReceiveFrameString(out var message)) continue;
                    _messageQueue.Enqueue(message);
                }
                subSocket.Close();
            }
            NetMQConfig.Cleanup();
        }

        public void DigestMessage()
        {
            while (!_messageQueue.IsEmpty)
            {
                if (_messageQueue.TryDequeue(out var message))
                    _messageCallback(message);
                else
                    break;
            }
        }
    }
    /////////////////////////////////////////////////////////////////////////////////////////////////
}

 

5. 립싱크 구현 

 

  파이썬에서 mediapipe를 이용하여 얼굴의 표정 정보를 유니티에 보내주고 유니티에서는 이를 이용하여 blend Shapes를 제어하는 방식으로 아바타 제어 프로그램을 작성할  수 있다.  파이썬 mediapipe의 Face Mesh는 눈 깜박동작을 인식하지 못한다. 그러나 입 모양이나 눈썹의 제어는 양호한 성능을 보였다. 

반응형