スキンを変更する

Modを作ろう

ゲーム内でスキンを動的に変更する方法について調べたことのまとめです。

はじめに

スキンはボディ(胴体、手、足)とフェイス(顔)に分かれています。

手段は主に3通りあります。

  • SKSEのSetSkin/SetFaceTextureSet関数
  • RaceMenuのAddSkinOverride系関数
  • RaceMenuのAddNodeOverride系関数

SKSEの関数はプラグインでのWorn ArmorやTextureSetの指定をまるごと差し替えます。部分的に変更できないのと動作コストが高いのがデメリットです。

RaceMenuのAddSkinOverride系関数は部分的に変更できます。例えばGlossinessのみを変更することが可能です。コストも安く済みます。フェイスに対応していないらしく、そこがデメリットになります。RaceMenuを開くと失われるようです。

RaceMenuのAddNodeOverride系関数はフェイスにも対応しています。RaceMenuにおけるOverlayのことです。使い方にクセがあります。CTDのリスクがあるのもデメリットです。

ボディ

SKSEのSetSkin関数

ボディのスキンを切り替えます。スキンとはArmorのことです。

SetSkin関数の引数のArmorは、設定したいArmorを指定します。ActorのスキンのデフォルトはActorBaseで指定しているWorn Armorになりますが、これを上書きする形になるようです。

ActorBaseでWorn Armorが設定されていない場合は、RaceのSkinの指定が適用された状態になっていますが、Actorのスキンを設定することで上書きされます。

サンプルコードです。

Function UpdateSkin()
	Armor kNewSkin = none
	Int iNeckDelta = 0.5

	; (kNewSkinを設定する)

	; スキンを設定する
	Game.GetPlayer().GetActorBase().SetSkin(kNewSkin)

	; 表示を更新する
	Game.GetPlayer().QueueNiNodeUpdate()

	; 首の隙間を補正する
	Game.GetPlayer().UpdateWeight(iNeckDelta)
EndFunction

SetSkin関数を実行するとNeckDeltaがリセットされるようです。ActorのWeightを変更していて首の隙間を補正している場合、首の隙間が復活します。再度補正する必要があります。Actorが抜刀していると、抜刀アニメーションが発生します。

おそらく内部的にアニメーション処理が初期化されるのでしょう。そこで、抜刀しているのであれば、納刀まで補正を遅らせます。

Function UpdateSkin()
    ; (略)

    if Game.GetPlayer().IsWeaponDrawn()
        RegisterForAnimationEvent(Game.GetPlayer(), "weaponSheathe")
    else
        UnregisterForAnimationEvent(Game.GetPlayer(), "weaponSheathe")
        Game.GetPlayer().UpdateWeight(NeckDelta)
    endif
EndFunction

Event OnAnimationEvent(ObjectReference akSource, string asEventName)
    UnregisterForAnimationEvent(Game.GetPlayer(), "weaponSheathe")
    Game.GetPlayer().UpdateWeight(NeckDelta)
EndEvent

この関数はコストが高いらしく、Actorが初期化される関係でカクツキが発生するため、あまりオススメできません。

元に戻すには、元のArmorが必要になります。

RaceMenuのAddSkinOverride系関数

テクスチャの設定を切り替えます。

サンプルコードです。

Function UpdateSkin()
	TextureSet kNewBody = none
	TextureSet kNewHand = none

	; (TextureSetを設定する)

	NiOverride.AddSkinOverrideTextureSet(Game.GetPlayer(), true, false, 0x04, 6, -1, kNewBody, true)	; Body
	NiOverride.AddSkinOverrideTextureSet(Game.GetPlayer(), true, false, 0x80, 6, -1, kNewBody, true)	; Feet
	NiOverride.AddSkinOverrideTextureSet(Game.GetPlayer(), true, false, 0x08, 6, -1, kNewHand, true)	; Hands
EndFunction

第4引数でArmorのスロットを指定します。

第5引数でKeyを指定します。Keyの一覧はnioverride.pscに記述されています。6はShaderTextureSetになります。

Keyの型によって関数を使い分けます。

Keyの型関数
floatAddSkinOverrideFloat
intAddSkinOverrideInt
boolAddSkinOverrideBool
stringAddSkinOverrideString
TextureSetAddSkinOverrideTextureSet

第8引数のpersistにfalseを設定すると変更が一時的なものになり、Weightの変更やSkinの設定など初期化が行われるタイミングで元に戻ります。使い勝手が悪くなるのでtrueにするほうがいいでしょう。

こちらはActorが初期化されないらしく、カクツキがありません。

RaceMenuのAddNodeOverride系関数

解説は準備中です。

フェイス

SKSEのSetFaceTextureSet

フェイスのテクスチャを切り替えます。テクスチャとはTextureSetのことです。

サンプルコードです。

Function UpdateSkin()
	TextureSet kNewHead = none

	; (TextureSetを設定する)

	Game.GetPlayer().GetActorBase().SetFaceTextureSet(kNewHead)
EndFunction

元に戻すには、元のTextureSetが必要になります。

RaceMenuのAddSkinOverride系関数

AddSkinOverride系関数はフェイスには使えないようです。

第4引数でHEADのスロットを指定しても変化が見られないようです。

RaceMenuのAddNodeOverride系関数

第4引数でKeyを指定します。Keyの型によって関数を使い分けます。

Keyの型関数
floatAddNodeOverrideFloat
intAddNodeOverrideInt
boolAddNodeOverrideBool
stringAddNodeOverrideString
TextureSetAddNodeOverrideTextureSet

第3引数のNodeでメッシュのNodeらしきものを指定します。

ActorBaseで指定されているフェイスのNodeを取得するサンプルです。

String Function GetHeadNodeName(Actor akTarget)
	ActorBase kActorBase = akTarget.GetActorBase()
	Int i = kActorBase.GetNumHeadParts()

	while i > 0
		i -= 1
		String sNodeName = kActorBase.GetNthHeadPart(i).GetPartName()

		if StringUtil.Find(sNodeName, "Head") >= 0
			if NiOverride.GetNodePropertyString(akTarget, false, sNodeName, 9, 7)
				return sNodeName
			endif
		endif
	endwhile

	return ""
EndFunction

SKSEのStringUtil.Findで強引にそれらしいNodeを特定しています。

RaceMenuのOverlayのNodeを指定する場合は、以下になります。

部位NodeOverlayの数
Face[ovl0]NiOverride.GetNumFaceOverlays()
Body[ovl0]NiOverride.GetNumBodyOverlays()
Hands[ovl0]NiOverride.GetNumHandOverlays()
Feet[ovl0]NiOverride.GetNumFeetOverlays()

0の部分がOverlayの番号で、[ovl0]、[ovl1]、[ovl2]という感じで指定します。

Overlayの数は、顔ならGetNumFaceOverlaysで取得します。

NodeにActorBaseで指定されているNodeを指定する場合

見た目には問題はないようです。

致命的な不具合として、ShaderTintColorを変更している最中にRaceMenuを表示しようとするとCTDします。

CTDしない状態に戻すにはRemoveNodeOverrideを実行した後にWeightを変更するなどしてActorに反映させる必要がります。この反映時に稀にCTDします。

NodeにOverlayを指定する場合

ShaderTextureの指定が必須のようです。指定しないと変化が見られません。

SpellのEffectShaderが表示されない問題があります。

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