会話とヘッドトラッキング

Modを作ろう

昔作ったTiny HeadtrackというModに関する技術的解説です。

ヘッドトラッキングの開始

5 (papyrus)

PlayerRef.SetHeadTracking(true)
PlayerRef.SetAnimationVariableBool("bHeadTrackSpine", false)
PlayerRef.SetAnimationVariableInt("IsNPC", 1)
PlayerRef.SetLookAt(akTarget)

ヘッドトラッキングの終了

414 (papyrus)

PlayerRef.ClearLookAt()
PlayerRef.SetAnimationVariableBool("bHeadTrackSpine", false)
PlayerRef.SetAnimationVariableInt("IsNPC", 0)
PlayerRef.SetHeadTracking(false)

会話を検知

会話の始まりを検知

ヘッドトラッキングを開始するのは会話の始まりです。

会話がはじまるとDialogue Menuという名前のHUD Menuが開かれます。これを検知する方法があります。

Event OnInit()
	RegisterForMenu("Dialogue Menu")
EndEvent
 
Event OnMenuOpen(String asMenuName)
	If asMenuName == "Dialogue Menu"
		Debug.Trace("会話が始まりました")
	EndIf
EndEvent

他にも、プレイヤーがNPCをアクティベートした時も会話が始まります。これはPerkを使ってアクティベートを捕捉します。Conditionに設定するアクティベートの対象はActorにします。

416 (papyrus)

Function Fragment_0(ObjectReference akTargetRef, Actor akActor)
;BEGIN CODE
    Utility.Wait(0.1)

    if (akTargetRef as Actor).IsInDialogueWithPlayer()
        (thtAliasPlayer as thtAliasPlayerScript).StartTalking(akTargetRef as Actor)
    endif
;END CODE
EndFunction

PerkにつけているParyrus Fragmentのコードです。念のため相手がプレイヤーと会話をしているかどうか確認しています。

会話の終わりを検知

ヘッドトラッキングを終了するのは、会話が終わったときです。会話が終わった瞬間を完璧に捕捉する方法が見つかりませんでした。そこで、いくつかトリガーを用意して終わらせることにしました。

会話の相手に魔法をかけて、会話の終了を検知します。スペルのConditionにIsInDialogueWithPlayer==1を使って、会話が終わったらエフェクトが切れるようにしました。ConditionのIsInDialogueWithPlayerは会話中のみ1になるようでした。ところがPapyrusにある同名のIsInDialogueWithPlayer関数は、なぜか会話が終わってもしばらく1を返し続け、使い物になりませんでした。これでほぼうまく動作します。問題は、スペルが効かない相手には使えないことです。例えばシェオゴラスにはスペルが通りません。CKで抵抗を無効にしてもダメでした。よくわかりませんが謎の力が働くのでしょう。

会話が始まったらプレイヤーの座標を調べて、移動していたら会話が終わったとみなすことにしました。これはさきほどの検知がうまくいかなかった時の保険です。相手は選ばないので、シェオゴラスが相手でも問題はなくなりました。そのかわり、フォロワーがプレイヤーに激突してプレイヤーが動かされても移動したことになってしまいます。

念には念をいれて、エリア移動とゲームのロード時にもヘッドトラッキングを終了させています。エリア移動はPlayerのReferenceAliasでOnLocationChangeイベントを捕捉します。ゲームのロードは同じくPlayerのReferenceAliasでOnPlayerLoadGameイベントです。

常時監視型

とても効率が悪いので、やめた方がいいです。

会話を検知(常時監視型) (papyrus)

Scriptname MySampleScript Extends RefefenceAlias

Float UpdateInterval = 1.0

Event OnInit()
    GoToState("NOT_IN_DIALOGUE")
EndEvent

State NOT_IN_DIALOGUE
    Event OnBeginState()
        RegisterForSingleUpdate(UpdateInterval)
    EndEvent

    Event OnUpdate()
        if UI.IsMenuOpen("Dialogue Menu")
            GoToState("IN_DIALOGUE")
        else
            RegisterForSingleUpdate(UpdateInterval)
        endif
    EndEvent
endif

State IN_DIALOGUE
    Event OnBeginState()
        RegisterForSingleUpdate(UpdateInterval)
    EndEvent

    Event OnUpdate()
        if !UI.IsMenuOpen("Dialogue Menu")
            GoToState("NOT_IN_DIALOGUE")
        else
            RegisterForSingleUpdate(UpdateInterval)
        endif
    EndEvent
endif

StateはAuto Stateにすれば初期Stateとして設定できるので、そうした方がコードがシンプルになると思いますが、OnBeginStateイベントが発火するのか不明だったので、こう書きました。

QuestとReferenceAliasを作って、ReferenceAliasにプレイヤーを入れて、上記のスクリプトを紐づければOKです。プレイヤーにたいしてのみループ処理しているので、負荷の面は気にしなくていいと思います。

この実装だとForceGreet(NPCの側から強制的に会話を始めるケース)でも検知できます。

タイトルとURLをコピーしました