HDS学部オープンキャンパス用に,Web心理テストをとりあえず2種類作成した。今後はBig5や刺激希求性,レジリエンスなどの尺度を追加予定。
エゴグラム
恋愛の類型


EyetribeはずっとProcessingで動かしてきたが,それが足かせとなり込み入った開発が難しい状況にあった。なにせ古いものなので調査も難航し,結局「そんな複雑なものじゃないので,ソケットでデータを受け取るプログラムを作っては?」と言われ,Python版ができた。んならUnity版も作ってもらおうてなことで,Unityへの移行が進んだ。Processing版はなにせパフォーマンスが芳しく無く,これ以上の開発は不毛な印象なので,同じ機能をUnity版に移植した。動画の読み込みが速い,動作がもたつかないなど,余裕がある感じで大変よろしい。オープンキャンパスなど,しばらくはこれで行こうと思う。実行ファイル3は,result内のCSVファイルの頭に「MGAZE_20250506_150602.csv」のようにMをつけることで,男性参加者のデータを青で表示することができる。
実行ファイル1 実行ファイル2 実行ファイル3



一方で,ファッション雑誌の静止画で視線測定をするのが訴求力あるという意見もあり,妙に納得である。実写は版権が難しいので,イラストにしてもらい測るのもありかなと。視線測定よりAIでイラスト生成のほうが楽しかったりして。






using UnityEngine;
using System;
using System.Net.Sockets;
using System.IO;
using System.Threading;
// JSON マッピング用クラス
[Serializable]
public class Avg { public float x; public float y; }
[Serializable]
public class Frame { public Avg avg; public bool fix; public int state; }
[Serializable]
public class Values { public Frame frame; }
[Serializable]
public class TrackerMessage { public Values values; }
public class EyeTribeTCP : MonoBehaviour
{
private const string HOST = "127.0.0.1";
private const int PORT = 6555;
private const string REQ_PUSH = "{\"category\":\"tracker\",\"request\":\"set\",\"values\":{\"push\":true,\"version\":1}}\n";
private const string REQ_HEARTBEAT = "{\"category\":\"heartbeat\",\"request\":null}\n";
private TcpClient client;
private NetworkStream stream;
private StreamReader reader;
private Thread heartbeatThread;
private Thread receiveThread;
private bool running;
[Header("=== Gaze Data (Read-Only) ===")]
[Tooltip("現在の注視座標X")]
public float GazeX;
[Tooltip("現在の注視座標Y")]
public float GazeY;
[Tooltip("現在の注視固定状態")]
public bool IsFixated;
[Tooltip("現在のトラッキング状態")]
public bool IsTracking;
[Tooltip("デバッグモード")]
public bool IsDebugmode;
void Start()
{
Connect();
}
void Connect()
{
try
{
client = new TcpClient(HOST, PORT);
stream = client.GetStream();
reader = new StreamReader(stream);
Send(REQ_PUSH);
running = true;
heartbeatThread = new Thread(() =>
{
while (running && stream != null)
{
Send(REQ_HEARTBEAT);
Thread.Sleep(250);
}
});
heartbeatThread.IsBackground = true;
heartbeatThread.Start();
receiveThread = new Thread(ReceiveLoop);
receiveThread.IsBackground = true;
receiveThread.Start();
Debug.Log("EyeTribe: 接続完了し、スレッドを開始しました。");
}
catch (Exception e)
{
Debug.LogError($"EyeTribe 接続失敗: {e.Message}");
}
}
private void Send(string message)
{
if (stream == null) return;
try
{
var buf = System.Text.Encoding.UTF8.GetBytes(message);
stream.Write(buf, 0, buf.Length);
}
catch (Exception e)
{
Debug.LogWarning($"Send エラー: {e.Message}");
}
}
void ReceiveLoop()
{
try
{
while (running && reader != null)
{
string line = reader.ReadLine();
if (string.IsNullOrEmpty(line))
continue;
try
{
var msg = JsonUtility.FromJson<TrackerMessage>(line);
if (msg?.values?.frame != null)
{
var f = msg.values.frame;
bool tracking = (f.state & 0x1) != 0;
// フィールドにセット(Inspector に反映される)
GazeX = f.avg.x;
GazeY = f.avg.y;
IsFixated = f.fix;
IsTracking = tracking;
if (IsDebugmode) {
Debug.Log($"x={GazeX:F3}, y={GazeY:F3}, fix={IsFixated}, tracking={IsTracking}");
}
}
}
catch (Exception ex)
{
Debug.LogWarning($"JSON 解析エラー: {ex.Message} | raw: {line}");
}
}
}
catch (Exception e)
{
Debug.LogWarning($"ReceiveLoop 終了: {e.Message}");
}
}
void OnDestroy()
{
running = false;
heartbeatThread?.Join(500);
receiveThread?.Join(500);
reader?.Close();
stream?.Close();
client?.Close();
Debug.Log("EyeTribe: 接続を閉じました。");
}
}