1
/
5

【開発者ブログ#01】アバターで会議に出るようになった話

この記事は「HIKKY開発者ブログ」より移動されたリバイバル記事となっています!

------------------------------------------------------

HIKKYにジョインしました

はじめまして。HIKKYのUnityエンジニアの花です。
HIKKYにジョインして半年が経過しました(2022年9月時点)

この会社は出社してもしなくてもオッケーという会社です。
というか本名を名乗っても名乗らなくてもオッケーですし、
アバターで会議しようがリアルアバターだろうがオッケーです。
何十時間働こうが(労働基準法の範囲で)オッケーです。
ボイチェンしたまま仕事してる剛の者もいます 👩‍🎤 

ふむ…、そういう会社に入社してUnityエンジニアを名乗る以上、ミーティングにはアバターで参戦し変なギミックを用意して会議相手を困惑させねばなりません。自分自身の存在が既にアイスブレイク。そういう境地を目指します。

というわけで前説が長いですが、本記事はリアルアバターを捨てて仕事をするようになる漢の記録です。

アバターを用意

まずは3Dモデルが必要です。

理想は美少女や化け物ですが、プロに依頼すると数十万かかります。
いきなりその金額の投資をする勇気は私にはありません🐔
なので始めるならばミニマムから…という自分の信条の元、仕事で使っているキャラのアイコンを3Dにしてみることにしました。

「暴」の匂いのするエンジニア

ツテを頼ってモデラーさんに見積もりを依頼。金額にして一桁万円後半、工数3日でやるよーとのお返事をいただけました。ちょっとお財布に痛いですが、これも勉強です。お仕事をご依頼しました。

……そうして3日後に完成したのがこちら。

釘バットがキュート

モデラーのこだわりを感じるおしり

技術の話

まずはUnityから出力した映像をGoogleMeetで受け取るためにSpoutの準備をします。SpoutをDLし、Setup.pdfに従って設定。PCを再起動して、適当なGoogleMeetを起動しましょう。

SpoutCamが選べればオッケー

無事、SpoutCamを認識してくれています。
認識してくれなかったらChromeを再起動したりPC再起動しましょう。
あとはUnityから映像を出力すればいけます。ここのpackageを導入するため、Project/Assets/Packages/manifest.jsonに下記を追加。

{
"scopedRegistries": [
{
"name": "Keijiro",
"url": "https://registry.npmjs.com",
"scopes": [
"jp.keijiro"
]
}
],
"dependencies": {
"jp.keijiro.klak.spout":"2.0.3",
(以下略){
"scopedRegistries": [
{
"name": "Keijiro",
"url": "https://registry.npmjs.com",
"scopes": [
"jp.keijiro"
]
}
],
"dependencies": {
"jp.keijiro.klak.spout":"2.0.3",
(以下略)

copy

packageを無事プロジェクトにimportできれば、あとは簡単。
SpoutSenderのcomponentをテキトーなGameObjectにはりつければ勝手にSpoutCamへ映像を送りつけてくれます。

超カンタン

さて、ここからが本番です。兵は詭道なり。
会議で相手をイラつかせて、冷静さを失わせれば勝負はこちらのものです!イラつくモーションで常にチョロチョロ動くようにしたいですよね!

給料あげてくれないとヤダヤダヤダヤダ

ゲフッ……、残業が5000兆時間を超えちまった…

ヤッタァァァァボーナス出たァァァァッ

キーボードからモーション変更

モーションは集まりました。
あとは会議中にモーションをちゃかちゃか切り替えて、相手の集中力を奪う必要があります。キーボードから切り替えれるようにScriptを追加します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu]
public class Motion : ScriptableObject
{
public KeyCode KeyCode;
public AnimationClip Clip;
public float Speed = 1f;
}using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu]
public class Motion : ScriptableObject
{
public KeyCode KeyCode;
public AnimationClip Clip;
public float Speed = 1f;
}

copy

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;

[RequireComponent(typeof(Animator))]
public class MotionController : MonoBehaviour
{
[SerializeField]
Motion[] _motions;
[SerializeField]
string _idleClipName;

Animator _animator;
AnimatorOverrideController _overrideController;

void Start()
{
_animator = GetComponent<Animator>();
_overrideController = new AnimatorOverrideController();
_overrideController.runtimeAnimatorController = _animator.runtimeAnimatorController;
_animator.runtimeAnimatorController = _overrideController;
}

void Update()
{
foreach(var motion in _motions)
{
if(Input.GetKeyDown(motion.KeyCode))
{
SwitchMotion(motion);
return;
}
}
}

void SwitchMotion(Motion motion)
{
var info = new AnimatorStateInfo[_animator.layerCount];
for(var i = 0; i < _animator.layerCount; i++)
{
info[i] = _animator.GetCurrentAnimatorStateInfo(i);
}

_overrideController[_idleClipName] = motion.Clip;
_animator.speed = motion.Speed;
_animator.Update(0.0f);

// NOTE: 初期フレームに戻す
var initialFrame = 0;
for(var i = 0; i < _animator.layerCount; i++)
{
_animator.Play(info[i].nameHash, i, initialFrame);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;

[RequireComponent(typeof(Animator))]
public class MotionController : MonoBehaviour
{
[SerializeField]
Motion[] _motions;
[SerializeField]
string _idleClipName;

Animator _animator;
AnimatorOverrideController _overrideController;

void Start()
{
_animator = GetComponent<Animator>();
_overrideController = new AnimatorOverrideController();
_overrideController.runtimeAnimatorController = _animator.runtimeAnimatorController;
_animator.runtimeAnimatorController = _overrideController;
}

void Update()
{
foreach(var motion in _motions)
{
if(Input.GetKeyDown(motion.KeyCode))
{
SwitchMotion(motion);
return;
}
}
}

void SwitchMotion(Motion motion)
{
var info = new AnimatorStateInfo[_animator.layerCount];
for(var i = 0; i < _animator.layerCount; i++)
{
info[i] = _animator.GetCurrentAnimatorStateInfo(i);
}

_overrideController[_idleClipName] = motion.Clip;
_animator.speed = motion.Speed;
_animator.Update(0.0f);

// NOTE: 初期フレームに戻す
var initialFrame = 0;
for(var i = 0; i < _animator.layerCount; i++)
{
_animator.Play(info[i].nameHash, i, initialFrame);
}
}
}

copy

あとは右クリックメニューからMotionのScriptableObjectを作成し、反応して欲しいキーボードのキーとAnimationClipとアニメーション速度をInspectorから設定です。MotionControllerのMotionsのFieldにD&DすればOKです👆
おっと、_idleClipNameにはidle状態のAnimationCilpの名前を入れましょう。

いざ鎌倉

準備は整いました。会議にこのアバターで参戦です。
細やかに発言ごとにモーションを変えていき、会議参加者をエンゲージメントしていきます。俺の豊かな感情表現にモチベイトされちまうはず!積極的な発言が増えるはず!やるしかねえ!!!

同僚からの感想

  • 会議に集中できない
  • 嫌な気持ちになった
  • 何を目指してるんですか?


Invitation from 株式会社HIKKY
If this story triggered your interest, have a chat with the team?
株式会社HIKKY's job postings

Weekly ranking

Show other rankings
Like HIKKY 採用担当's Story
Let HIKKY 採用担当's company know you're interested in their content