- Papyrusスクリプトの記憶を記録に残すシリーズその10 -
寝るときには重い鎧を脱いで欲しい!
NPCおよびプレイヤーの自動脱衣・着衣機能を実装してみましょう。お風呂へ入る時に裸で入浴するようにしたり、ベッドで寝る時に重い鎧を脱ぐようにしたりすることができます。
※2020/07/14 新サイトでアップデート記事を書きました。→ 自動脱衣スクリプト2020 | 今もSkyrim
目次
1. NPCの自動脱衣・着衣
1-1. 脱衣させる場所にトリガーボックスを設定する
自動脱衣・着衣機能は、NPCとプレイヤーとで方法が違います。どちらもスクリプトを用いますが、プレイヤーの場合はSKSEが必要になります。簡単なNPCの場合から見ていきましょう。
まずは、この機能を付けたい部屋あるいは領域に、トリガーボックスを設定します。前回の NPCが室内ドアを閉めるようにする方法 と同様に、ブリーズホームのリディアさんの部屋を例にしてトリガーボックスを設置します。
Render Window にリディアさんの部屋を表示します。
ベッドを選択状態にしてツールバーの「T」ボタンを押し、Select Form ダイアログで「TriggerBox」を選んでトリガーボックスを設置します。
上図のように薄い色のボックスで囲まれたら、このボックスの Reference を開いて大きさと位置を調整します。
ベッドを大きく取り囲むようにして、寝る前に服を脱いでもらう作戦です。
これでトリガーボックスが出来ました。参考までに Reference の内容を出しておきます。
「Position」が位置、「Rotation」が回転、「Bounds」が大きさになります。
トリガーボックスで囲まれた範囲が脱衣ゾーンになりますので、場所と用途に合わせて適宜調節してください。
1-2. トリガーボックスにスクリプトを付ける
設置したトリガーボックスにスクリプトを付けましょう。単純な方法の1例を示します。
(今回はスクリプトの文字をやや小さくしています。ご了承ください。)
Scriptname EkaUndressScript01 extends ObjectReference ; NPC用簡易版 Armor Property armRing Auto ; JewelryRingGold を指定するプロパティ Actor actPasser ; 通った人 ;--------------------------------------------------入る時 Event OnTriggerEnter(ObjectReference akTriggerRef) actPasser = akTriggerRef as Actor If actPasser != Game.GetPlayer() && actPasser != None ; 通った人がプレイヤーでない時 actPasser.UnEquipAll() ; 全ての装備を外す actPasser = None EndIf EndEvent ;--------------------------------------------------出る時 Event OnTriggerLeave(ObjectReference akTriggerRef) actPasser = akTriggerRef as Actor If actPasser != Game.GetPlayer() && actPasser != None actPasser.AddItem(armRing, 1, true) ; Ringをインベントリに入れる actPasser.EquipItem(armRing, false, true) ; Ringを装備させる actPasser.RemoveItem(armRing, 1, true) ; Ringをインベントリから取り除く actPasser = None EndIf EndEvent
トリガーボックスに入る時に脱衣し、出る時に着衣します。OnTriggerEnter/Leave
でトリガーボックスから出入りするタイミングを取得して発動します。
脱衣の方は、プレイヤーの着衣を考慮しないなら「If actPasser != Game.GetPlayer()
」の条件を消せばプレイヤーにも適用できます。そうすると「NPCは脱衣・着衣機能付き、プレイヤーは脱衣機能のみ」のスクリプトとなります。
NPCの着衣は、指輪を渡して一時的に装備させてから取り除き、自動で最強装備をさせることで実装しています。渡すものは何でも良いのですが、金の指輪が描画も軽そうで目立たないので最適でしょう。
この金の指輪法は複数のMODで採用されていて、どのMODが発端なのかは分かりません。バニラのシステムを生かしたシンプルで素晴らしい方法だと思います。
上記のスクリプトですが、実は脱衣する「actPasser.UnEquipAll()
」のところでCTDすることがあります。特に、セーブデータをロードした直後でセルの移動を挟まずにトリガーボックスへ進入させた場合に起こります。セルのロードを挟めば問題ありません。
これについては海外のサイトで1件だけ質問が挙がっているのを見つけましたが、有効な回答は付いていませんでした。自分の環境ではほぼバニラの状態でも起こるため原因不明。
ということで、対策版スクリプトを示します。「入る時」の部分だけ変更しています。
Scriptname EkaUndressScript02 extends ObjectReference ; NPC用対策版 Armor Property armRing Auto ; JewelryRingGold を指定するプロパティ Actor actPasser ; 通った人 ;--------------------------------------------------入る時 Event OnTriggerEnter(ObjectReference akTriggerRef) Weapon wepTemp ; 武器情報格納用 actPasser = akTriggerRef as Actor If actPasser == Game.GetPlayer() || actPasser == None ; 通った人がプレイヤーの時は終了 actPasser = None Return EndIf wepTemp = actPasser.GetEquippedWeapon() ; 右手武器をチェック If wepTemp != None actPasser.UnequipItem(wepTemp) ; 武器を外す EndIf Utility.Wait(0.01) wepTemp = actPasser.GetEquippedWeapon(true) ; 左手武器をチェック If wepTemp != None actPasser.UnequipItem(wepTemp) ; 武器を外す EndIf Int i = 0 While i < 31 ; 装備スロット30から60まで繰り返す actPasser.UnequipItemSlot(30 + i) ; 防具を外す i += 1 EndWhile actPasser = None wepTemp = None EndEvent ;--------------------------------------------------出る時 Event OnTriggerLeave(ObjectReference akTriggerRef) actPasser = akTriggerRef as Actor If actPasser != Game.GetPlayer() && actPasser != None actPasser.AddItem(armRing, 1, true) ; Ringをインベントリに入れる actPasser.EquipItem(armRing, false, true) ; Ringを装備させる actPasser.RemoveItem(armRing, 1, true) ; Ringをインベントリから取り除く actPasser = None EndIf EndEvent
武器は右→左の順に GetEquippedWeapon
で武器情報を取得して外します。
防具は装備スロットを指定するだけで外せるので、一般に使われるであろう30~60番を全て外しています。ここで While ~ EndWhile
が出てきましたが、指定回数だけ行う単純なループ処理に良く使います。
スクリプトはどちらか問題無さそうな方を選んでください。処理内容としては最初の方がシンプルですが、対策版でも個人的には処理速度の差を感じません。CTDしないことの方が大事。
ちなみに、リディアさんは対策MODを入れないと自分のベッドで寝ません。拙作の Breeze Reform なら対策できますよ。
2. プレイヤーの自動脱衣・着衣(要SKSE)
2-1. 防具用リストの用意
プレイヤーの場合は少し複雑になります。というのも「着衣」させるための装備情報をどこかに保存しておく必要があるからです。
そこで始めに、防具の情報を保存するためのリスト枠を作成しておきます。武器は最大2つなのでリスト形式にせず、スクリプト内から情報を保存するようにします。
Object Window の Miscellaneous > FormList
を開き、右クリックして「New」を選ぶと新規にフォームリストを作成できます。
ここではIDに名前を入れるだけでOKです。これで防具リスト枠が用意できました。
2-2. プレイヤー用のスクリプト
次のようなスクリプトをトリガーボックスに付けます。武器の着脱や、防具情報を取得するためにSKSEの機能を使っています。
Scriptname EkaUndressScript03 extends ObjectReference ; プレイヤー用(要SKSE) FormList Property folArmor Auto ; 防具リストを指定するプロパティ Weapon Property wepL Auto ; 左手武器を格納するプロパティ、無指定 Weapon Property wepR Auto ; 右手武器を格納するプロパティ、無指定 Actor actPasser ; 通った人 Armor armTemp ; 防具情報格納用 Int i ; While用 ;--------------------------------------------------入る時 Event OnTriggerEnter(ObjectReference akTriggerRef) actPasser = akTriggerRef as Actor If actPasser != Game.GetPlayer() ; 通った人がプレイヤーでない時は終了 actPasser = None Return EndIf If folArmor.GetSize() > 0 folArmor.Revert() ; 万が一防具リストが空でない時は初期化 EndIf wepR = actPasser.GetEquippedWeapon() ; 右手武器を取得 If wepR != None actPasser.UnequipItemEx(wepR, 1) ; 右手武器を外す EndIf wepL = actPasser.GetEquippedWeapon(true) ; 左手武器を取得 If wepL != None actPasser.UnequipItemEx(wepL, 2) ; 左手武器を外す EndIf i = 0 While i < 31 ; 装備スロット30から60まで繰り返す armTemp = actPasser.GetWornForm(Math.LeftShift(1, i)) as Armor ; 防具情報を取得 If armTemp != None ; 対象スロットに装備していた時 folArmor.AddForm(armTemp) ; 防具情報をリストに加える actPasser.UnequipItemSlot(30 + i) ; 防具を外す armTemp = None EndIf i += 1 EndWhile actPasser = None EndEvent ;--------------------------------------------------出る時 Event OnTriggerLeave(ObjectReference akTriggerRef) actPasser = akTriggerRef as Actor If actPasser != Game.GetPlayer() actPasser = None Return EndIf If wepR != None && actPasser.GetItemCount(wepR) > 0 ; 右手武器を所持している時 actPasser.EquipItemEx(wepR, 1) ; 右手武器を装備 EndIf wepR = None If wepL != None && actPasser.GetItemCount(wepL) > 0 ; 左手武器を所持している時 actPasser.EquipItemEx(wepL, 2) ; 左手武器を装備 EndIf wepL = None Int intNum ; 登録されている防具数 intNum = folArmor.GetSize() i = 0 While i < intNum && i < 31 ; 登録数だけ繰り返す armTemp = folArmor.GetAt(i) as Armor ; リスト番号から防具情報を取得 If armTemp != None && actPasser.GetItemCount(armTemp) > 0 ; 防具を所持している時 actPasser.EquipItemEx(armTemp, 0, false, true) ; 防具を装備 EndIf armTemp = None i += 1 EndWhile folArmor.Revert() ; 防具リスト初期化 actPasser = None EndEvent
プロパティが3つありますが、folArmor
に先ほど作成した防具リスト枠を指定してください。他の2つは武器用で、スクリプト内から情報を保存できますので何も指定しなくて構いません。
スクリプトが長いので、コメントを入れて補足しておきました。少し分かりにくいかもしれない部分としては「actPasser.GetWornForm(Math.LeftShift(1, i))
」の所でしょうか。
GetWornForm
の括弧には装備スロットに対応した数字を入れるのですが、Slot Masks - Armor にそのリストがあります。簡単に言えば2の累乗なので、2の0乗から2の30乗まで順に入れて装備しているかどうか判定しています。
「Math.LeftShift(1, i)
」は「Math.Pow(2, i)
」と同義で、2のi乗の値を返します。体感速度に差はありませんが、LeftShift
の方が負荷が少なそうに思えるのと、見た目が格好良い(?)ので使っています。
また、プレイヤーの脱衣・着衣を自動で行わず、脱衣かごのようなアクティベーターを用意するのも1つの手です。部屋に入った時に勝手に脱いだりせず、ユーザーの意思で着替えるようにした方が雰囲気が出るような気がします。
アクティベーターにスクリプトを付けるときは、イベントの発動を Event OnActivate
1つだけに変更し、脱衣中かどうかを Bool
タイプのプロパティに保存して分岐すれば実装できます。
※分岐条件が違いますが作りましたのでご参考にどうぞ。→ Simple Clothes Basket
---【注意点】(解消済み)2018/03/10追記---
防具を装備する actPasser.EquipItem(armTemp, false, true)
の部分について、この方法で装備させた場合の重要な注意点を書いておきます。
それは「プレイヤーが付呪した装備品」をこのコードで装備させても、対象の付呪効果が得られないことです。装備はされるので気付きにくいのですが、「有効な効果」の欄に付呪効果が出ておらず、インベントリから再装備するときちんと効果を得られます。元から付呪されている装備品の場合はこの問題は起こりません。creationkit.com の該当ページ にも書いてあります。
この点については、種々の装備・アイテム系関数を検討してみたのですが解決できませんでした。SkyUIのお気に入りグループ機能で着衣させれば問題ないので、必要ならばそれを使ってもらうことになりそうです。お気に入り設定に介入したりする技術は無いし、今の所お手上げ状態…。すみません。
---2018/07/05追記---
actPasser.EquipItem(armTemp, false, true)
によりプレイヤー付呪が適用されない件は、
actPasser.EquipItemEx(armTemp, 0, false, true)
とすることにより解消しました。この変更により、プレイヤー付呪品を装備した際にも付呪効果が得られます。
記事内のスクリプトは修正済みです。
---追記以上---
3. NPCおよびプレイヤーの自動脱衣・着衣スクリプト統合版(要SKSE)
自動脱衣・着衣についてNPCとプレイヤーとで分けて説明しましたが、それらを合わせたスクリプトをコピー&ペースト用に挙げておきます。トリガーボックスに付けることで、NPCもプレイヤーも自動脱衣・着衣するようになります。
プロパティを2つ指定するだけで使用できます。armRing
には「JewelryRingGold
」を、folArmor
には作成した防具リスト枠を指定してください。
Scriptname EkaUndressScript04 extends ObjectReference ; NPC、プレイヤー統合版(要SKSE) Armor Property armRing Auto ; JewelryRingGold を指定するプロパティ FormList Property folArmor Auto ; 防具リストを指定するプロパティ Weapon Property wepL Auto Weapon Property wepR Auto Actor actPasser Armor armTemp Int i ;--------------------------------------------------入る時 Event OnTriggerEnter(ObjectReference akTriggerRef) Weapon wepTemp actPasser = akTriggerRef as Actor If actPasser == None Return ElseIf actPasser != Game.GetPlayer() wepTemp = actPasser.GetEquippedWeapon() If wepTemp != None actPasser.UnequipItemEx(wepTemp, 1) EndIf Utility.Wait(0.01) wepTemp = actPasser.GetEquippedWeapon(true) If wepTemp != None actPasser.UnequipItemEx(wepTemp, 2) wepTemp = None EndIf Else wepR = actPasser.GetEquippedWeapon() If wepR != None actPasser.UnequipItemEx(wepR, 1) EndIf wepL = actPasser.GetEquippedWeapon(true) If wepL != None actPasser.UnequipItemEx(wepL, 2) EndIf EndIf i = 0 If actPasser != Game.GetPlayer() While i < 31 actPasser.UnequipItemSlot(30 + i) i += 1 EndWhile Else If folArmor.GetSize() > 0 folArmor.Revert() EndIf While i < 31 armTemp = actPasser.GetWornForm(Math.LeftShift(1, i)) as Armor If armTemp != None folArmor.AddForm(armTemp) actPasser.UnequipItemSlot(30 + i) armTemp = None EndIf i += 1 EndWhile EndIf actPasser = None EndEvent ;--------------------------------------------------出る時 Event OnTriggerLeave(ObjectReference akTriggerRef) actPasser = akTriggerRef as Actor If actPasser == None Return ElseIf actPasser != Game.GetPlayer() actPasser.AddItem(armRing, 1, true) actPasser.EquipItem(armRing, false, true) actPasser.RemoveItem(armRing, 1, true) Else If wepR != None && actPasser.GetItemCount(wepR) > 0 actPasser.EquipItemEx(wepR, 1) EndIf wepR = None If wepL != None && actPasser.GetItemCount(wepL) > 0 actPasser.EquipItemEx(wepL, 2) EndIf wepL = None Int intNum = folArmor.GetSize() i = 0 While i < intNum && i < 31 armTemp = folArmor.GetAt(i) as Armor If armTemp != None && actPasser.GetItemCount(armTemp) > 0 actPasser.EquipItemEx(armTemp, 0, false, true) EndIf armTemp = None i += 1 EndWhile folArmor.Revert() EndIf actPasser = None EndEvent
補足:私はスクリプトを書くのは好きですが、専門家ではありません。内容は creationkit.com の情報と個人的な経験を基にして書いています。どうかご参考程度にご覧ください。
0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。