ノベルパートの追加開発をしている中で、キャラクターの会話と表情が微妙に合わない箇所が出てきました。
ほんとに微妙なのですが、若干の違和感を感じてしまいます。
当初書き上げた立ち絵だけで強行することもできますが、どうせなら今回実装しておこうと表情入れ替えシステムを導入してみました。
以前に実装した「視線変更」(気になる視線★【Unity6】)を改修すれば、「目」と「口」の両方を制御できそうだったので、その方向でやってみました。
システム全体概要と設計思想
本システムは、会話進行マネージャー(Sys_Messenger)、パーツ切り替え管理(Sys_CharacterExpression / Sys_PartSwitcher)、および各パーツのアニメーション制御(Sys_LipSync_Hybrid 等)を疎結合に連動させた構造です。
- 動的検出・カプセル化:Messenger側で特定のリップシンクスクリプトを直接アタッチして抱え込むのをやめ、mouthId(Normal, Surprise 等)の指定に応じて、アクティブになったパーツから動的にスクリプトを検出・操作します。
- 状態復元と安全性の担保:演出(モノローグ等)に応じたリップシンクの明示的なON/OFFに対応しつつ、会話終了・非アクティブ時にはパーツ本来のスプライト(idleSprite / SpriteRenderer.sprite)へ自動で安全復帰します。
要するに「目パチ」「口パク」部分を複数用意しておいて、任意で切り替えられるようにしよう、ということです。
キャラクターの設計配置図。表情毎に分けておきます。

Saito_nomal(プレハブ)
├── Saito_nomal_Gaze ← 視線差分(目パチ)非アクティブ
├── Saito_nomal_eye ← デフォルト視線(目パチ)
├── Saito_nomal_mo_Normal ← ノーマル表情用口パク(デフォルトアクティブ)
├── Saito_nomal_mo_Surprise ← 驚き表情用口パク(非アクティブ)
├── Saito_nomal_mo_Pucker ← すぼみ表情用口パク(非アクティブ)
├── Saito_nomal_mo_Smile ← 笑み表情用口パク(非アクティブ)
├── Saito_nomal_mo_Trouble ← 困り表情用口パク(非アクティブ)
├── Saito_nomal_mo_Setback ← 悔し表情用口パク(非アクティブ)
├── Saito_nomal_main ← 身体
└── Saito_N_Anchor ← 名前Plate用アンカー
このうちの「Normal」「Surprise」「Pucker」画像を、3枚の口パクアニメーションに割り当てます。
表情とアニメを共通化することでコスト削減です(*´ω`*)



「目」の方のバリエーションも増やしていけば、まさに「表情システム」に相応しくなりそうです。
今回は視線だけにとどまりそうですが、将来的には見据えて良さそうです。
各コンポーネントの役割と変更要点
新規・修正スクリプト

- Sys_PartSwitcher.cs(新規):切り替えパーツの登録
- Sys_CharacterExpression.cs(新規):「Sys_PartSwitcher.cs」の参照元を定義
- Sys_EyeSwitcher.cs(新規):元スクリプト「Sys_EyeToggle」の今回版
- Sys_Messenger.cs(修正):会話進行スクリプトに今回のシステムを導入
- Sys_LipSync_Hybrid.cs(修正):口パクアニメスクリプトに停止時の制御を追加
Sys_PartSwitcher.cs(切り替えパーツ登録)

各表情パーツをリストに登録していき、それぞれに「ID」を紐づけておきます。
このIDを呼ぶことで指定パーツに切り替えていく仕組みです。
- SetMouth(string mouthId):mouthSwitcher.ChangePart(mouthId) を呼び出して該当する口オブジェクトのみを SetActive(true) にし、現在アクティブになった GameObject を返す。
同様に目パチ用リストも別途用意しておきます。(スクリプトは同じSys_PartSwitcher.cs)

Sys_CharacterExpression.cs(参照元を定義)

キャラクター本体オブジェクトにアタッチします。
「目パチ用PartSwitcher」と「口パク用PartSwitcher」をアサインして、切り替えを定義しておきます。
これを行うことで「どのキャラクターのどのパーツを切り替えろ」という命令が可能になります。
Sys_EyeSwitcher.cs(視線切り替え)

以前の視線切り替え(Sys_EyeToggle)の今回バージョンです。
UIはほぼ変化なしで、キャラクターを参照させて、Modeタブで指定パーツと切り替えることができます。
Sys_Messenger.cs(会話スクリプトへの機能追加)

- CharacterExPression:パーツ切り替えリストが登録されているキャラクターを指定
- Mouth Id:この会話でアクティブにする口差分のIDを指定
- Enable Lip Sync:リップシンクアニメーションを実行するかどうかのフラグ
- ON(デフォルト):会話中、アクティブな口の Sys_LipSync_Hybrid を起動して口パクさせる
- OFF:心の声やモノローグ時、口を動かさずに会話を進行させる
DisplayMessageでの起動制御
・メッセージ表示(文字送りコルーチン)の開始直前に
characterExpression.SetMouth(mouthId) を呼び出し、口パーツを切り替える。
・切り替わったアクティブな口オブジェクトから Sys_LipSync_Hybrid を取得し、SetTalking(true) を実行。
・文字送り完了時・会話終了時に SetTalking(false) で停止。
Sys_LipSync_Hybrid.cs(口パクアニメーション制御)

- 会話中アニメーション:Sprite Open / Sprite Mid / Sprite Close の3コマ(または設定されている有効コマ)をランダムな速度(minFrameRate〜maxFrameRate)でループ表示。
- 待機時(非会話時)スプライト復帰 (idleSprite):
- idleSprite が Inspector で未設定(None)の場合、Awake 時点で SpriteRenderer.sprite にアタッチされている元画像を「待機時画像」として自動保持。
- SetTalking(false) または OnDisable 実行時、会話用スプライトからこの待機時画像へ確実にリセット。
新機能は「Idole Sprite」です。
従来のままだと、口パクが終了したときの基本画像が、1枚目の「Sprite Open」になっていました。
表情を追加したため、その表情を1枚目にするわけにもいかず、アニメが終了したら本来のSpriteか指定Spriteを表示するように改修しました。
SpriteRendererのSpriteをデフォルトで表示できるようにしました。

テストプレイでの比較



今回の実装で、表情立ち絵内の表情差分を行うことができるようになりました。
その分、設定の手間が増えたので、設定の自動化などができればよさそうです。
しかし自分はもともと手動アナログ設定が安心なところもあるので、これくらいならば許容範囲内かなとも思えます。
Aiと相談設計しているときに、
「ボイスの音量レベルに合わせて口の開き具合を変える(リアルタイムリップシンク)」
という魅力的な提案もあったので、ボイス導入のときは考えてみてもいいかもしれません。

コメント