これを実現するためには
・ PythonからUnityへの通信
・ Unityでのパラメータ設定
が必要となる
まずは通信だが、今回はUDPの非同期通信でおこなった
同期だとプロセスがそこでフリーズして、まばたきや呼吸をしなくなったため
またUDPを選んだのは簡単そうだというだけで、深い意味はない
まずはPython側(赤い部分が該当)
とりあえず5秒周期で表情パラメータを自動的に変えるようにしている
実際にはここはFeeの会話文章内の文末に(嬉)のように追記することで制御させることになる
from fileinput import close
from flask import Flask, request
from flask_cors import CORS
import json
import CeVIO_control
import Fee_AI
import time
# UDP通信 python → Unity
# python送信側
import socket
import random
HOST = '127.0.0.1'
PORT = 50007
a = 0
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
a = (a + 1) % 8
result = str(a)
print(a)
client.sendto(result.encode('utf-8'),(HOST,PORT))
time.sleep(5.0)
app= Flask(__name__)
CORS(app)
talk_from_client = ""
data_json_str = ""
CeVIO_control.CeVIO_init()
@app.route('/room/talk/data', methods= ['GET', 'POST'])
def get_json():
# read json + reply
data= request.get_data()
data_json_str =data.decode('utf-8')
# 会話データ取り出し
data_json = json.loads(data_json_str)
talk_from_client = data_json["client_talk_data"]
talk_to_client = data_json["Fee_talk_data"]
talk_to_client_countrol = data_json["Fee_talk_control"]
#state = data_json["state"]
Fee_peerId = data_json["Fee_peerId"]
talker_peerId = data_json["talker_peerId"]
client_peerId = data_json["client_peerId"]
#client_name = data_json["client_name"]
print(talk_from_client)
result = Fee_AI.top(talk_from_client,talk_to_client,talk_to_client_countrol)
respose_talk_to_Fee = result[0]
respose_talk_to_Fee_control = result[1]
#会話相手の名前確定
response_data_json = {
"state":Fee_AI.state,
"Fee_peerId":Fee_peerId,
"talker_peerId":talker_peerId,
"client_peerId":client_peerId,
"client_name":Fee_AI.client_name,
"client_talk_data":talk_from_client,
"Fee_talk_data":respose_talk_to_Fee,
"Fee_talk_control":respose_talk_to_Fee_control
}
print(response_data_json)
return response_data_json
if __name__== '__main__':
app.run()
unity側
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Live2D.Cubism.Core;
using Live2D.Cubism.Framework;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System;
public class Fee_expression : MonoBehaviour
{
// UDP受信用
static UdpClient udp;
IPEndPoint remoteEP = null;
// UDP受信用 終了
// Fee 表情制御
private CubismModel _model;
// 表情は、笑い、嬉しい、哀しい、怒り、困る、恥ずかしい、の6種類とする
// パラメータは、まぶた左右、まゆ左右、口の形と開き、ほほのチーク、の7種類とする
int[] index = new int[] {5,7,11,12,14,15,13};
float[] Cur_value = new float[] {1f,1f,0f,0f,1f,0f,0f};
float[] value = new float[] {1f,1f,0f,0f,1f,0f,0f};
int expression_state = 0;
float[,] Set_value = new float[,] {
{1f,1f,0f,0f,1f,0f,0f}, // Default
{0f,0f,0f,0f,1f,1f,0f}, // 笑い
{0.5f,0.5f,0f,0f,1f,0f,0f}, // 嬉しい
{0.2f,0.2f,-1f,-1f,-1f,0f,0f}, // 哀しい
{0.5f,0.5f,1f,1f,-1f,0f,0f}, // 怒り
{1f,1f,-0.4f,-1f,-1f,0.5f,0f}, // 困る
{0.7f,0.7f,-0.6f,-0.6f,1f,0f,1f}, // 恥ずかしい
{1f,1f,0f,0f,0f,1f,0f}, // 驚き
};
int initial_flag;
int frame_count;
int transient_flag;
private void Start()
{
// UDP受信
//UdpClientを作成し、指定したポート番号にバインドする
IPEndPoint localEP = new IPEndPoint(System.Net.IPAddress.Any, 50007);
udp = new UdpClient(localEP);
//非同期的なデータ受信を開始する
udp.BeginReceive(ReceiveCallback, udp);
// Fee表情制御
_model = this.FindCubismModel();
frame_count = 0;
transient_flag = -1;
int i;
for (i=0; i<7; i++){
var parameter = _model.Parameters[index[i]];
parameter.BlendToValue(CubismParameterBlendMode.Override, value[i]);
}
initial_flag = 1;
}
//データを受信した時
private void ReceiveCallback(IAsyncResult ar)
{
UdpClient udp =
(UdpClient)ar.AsyncState;
//非同期受信を終了する
IPEndPoint remoteEP = null;
byte[] rcvBytes;
try
{
rcvBytes = udp.EndReceive(ar, ref remoteEP);
}
catch(SocketException ex)
{
Debug.Log("受信エラー" + ex.Message + "/" + ex.ErrorCode);
return;
}
catch (ObjectDisposedException ex)
{
//すでに閉じている時は終了
Debug.Log("Socketは閉じられています");
return;
}
//データを文字列に変換する
string rcvMsg = System.Text.Encoding.UTF8.GetString(rcvBytes);
Debug.Log("データ非同期受信 expression_state = " + rcvMsg);
expression_state = Convert.ToInt16(rcvMsg);
//再びデータ受信を開始する
udp.BeginReceive(ReceiveCallback, udp);
}
private void Update()
{
int i;
// LateUpdateのたびに設定しないとダメらしい
for (i=0; i<7; i++){
if (Set_value[expression_state,i] >= Cur_value[i]){
if (value[i] >= Set_value[expression_state,i]){
value[i] = Set_value[expression_state,i];
Cur_value[i] = Set_value[expression_state,i];
}else{
value[i] += (Set_value[expression_state,i] - Cur_value[i])* Time.deltaTime * 2.0f;
}
}else{
if (value[i] <= Set_value[expression_state,i]){
value[i] = Set_value[expression_state,i];
Cur_value[i] = Set_value[expression_state,i];
}else{
value[i] += (Set_value[expression_state,i] - Cur_value[i])* Time.deltaTime * 2.0f;
}
}
var parameter = _model.Parameters[index[i]];
parameter.BlendToValue(CubismParameterBlendMode.Override, value[i]);
}
}
}
まず、LateUpdateではなくUpdateを使用している
口パクやまばたきなどでLateUpdateを使用しているらしく、それらとバッティングしてうまく動作しない
口パクやまばたきなどの制御の前に表情を確定させてしまうのは正しいはずだ
またパラメータ設定はUpdate(つまり毎フレームごと)に設定しないとDefaultに戻ってしまう
きっとそういう仕様なんだろう
これで通信と表情設定が可能となった
あとはFeeの会話中に表情パラメータを入れ込み、これをデコードする部分を作ればいい
参考:
PlantUMLの状態遷移を示したテキストを読み込んで、遷移されるように変更、
(怒)などの感情表現をCeVIOの発声に反映、
PythonからUnityへUDPで通信して、表情パラメータを渡す
という一連の改良が完成!
やったね
Comments