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는 눈 깜박동작을 인식하지 못한다. 그러나 입 모양이나 눈썹의 제어는 양호한 성능을 보였다.
반응형