Skyrimはオフラインのアクション風のロールプレイングゲームなので、死んだらゲームオーバーです。Modで敵を追加したり難易度を上げていくと簡単に死んでしまうようになります。死んだらセーブしてあるデータをロードすることになります。セーブ前に倒した敵や手に入れたアイテム、ダンジョンの進行具合などはなかったことになるので、またやり直しです。すると、巻き戻るのが嫌なので、こまめにセーブするようになります。挙句の果てには、敵を1対倒しただけ、部屋を1つ進んだだけでセーブするようになります。
そこで、死んだらロードするのではなく、オンラインゲームのように復活地点に戻るようにしてみましょう。こういったModを敗北ペナルティと呼ぶようです。
プレイヤーを不死化するには
プレイヤーが死ぬとゲームオーバーとなり、設定された時間が経過した後に強制的に最後のセーブデータがロードされます。これはゲームエンジン側で制御されていて止められないようなので、死なないようにします。
StartDeferredKill関数を実行すると、体力が0以下になっても死ななくなります。まるで体力など関係ないゾンビのようです。この状態はキルムーブを受け付けます。でも死にません。普段はこの状態にしておきます。そして、プレイヤーの体力を監視して、0になったら死亡処理に移ります。
膝をつく演出が必要なら、Bleedoutのアニメーションを再生させます。
死亡の演出は、まず麻痺にさせます。次に死亡のアニメーションを再生させます。麻痺の状態でいる限り起き上がれません。Mfg fixなどを使って目を閉じます。これで死んでいるように見えます。
倒れていても攻撃され続けるので、何かしら対応が必要です。簡単なのはGame.DisablePlayerControls関数で移動を制限させることです。敵がプレイヤーを無視するようになります。あるいは、プレイヤーと友好になるFactionを用意して周囲のActorにばらまき、StopCombat関数で強制的に戦闘状態を解除させます。
NPCを不死化するには
NPCにプレイヤーと同じことをしようとすると、エリア移動をまたいだりロードを挟んだ時に挙動がおかしくなるので、本当に死んでもらいます。普段の状態はプレイヤーと同じです。
蘇生はResurrectですが、所持品がリセットされるので、コンテナを別に用意して退避させておきます。
ヴァニラフォロワーはDialogueFollowerというQuestのFollowerというReferenceAliasに入っています。そこでProtected属性がつくようになっているので、これは取り除いておきます。
プレイヤーが死んでいるときにカメラを動かすには
麻痺で倒れている状態(ラグドール状態)の時は、カメラを動かせません。Game.SetCameraTarget関数も見た目では効果がないように見えます。おそらく、ゲーム内部でカメラがロックされているのだと思います。
以下のコードを実行するとカメラのロックが解除されて、カメラを動かせるようになります。
ちなみに、寝ている時はMoveToで移動できませんので、起き上がらせてから移動です。画面を暗転させておくなどの演出が必要となるでしょう。
プレイヤーが死んでいる時にカメラを動かせるようにする (papyrus)
PlayerRef.GetActorBase().SetEssential(true)
PlayerRef.EndDeferredKill()
PlayerRef.StartDeferredKill()
PlayerRef.GetActorBase().SetEssential(false)
プレイヤーの死体を作る
足元に作るとKillの際に悲鳴が聞こえてしまうので、別の場所(死体安置所)で生成して準備が整ってから足元に移動させています。
プレイヤーの死体を作る (papyrus)
ObjectReference Property WIDeadBodyCleanupCellMarker Auto
Function CreateCorpse()
Actor kCorpse = WIDeadBodyCleanupCellMarker.PlaceActorAtMe( PlayerRef.GetActorBase() )
kCorpse.Kill()
kCorpse.RemoveAllItems() ; 死体を裸にする
; プレイヤーの所持品を死体に移動する
; 1つずつ移動させないとCTDすることがあるみたい
; 鍵など移動させない処理はここに追加する
Form[] kItemList = PlayerRef.GetContainerForms()
Int i = kItemList.Length
while i > 0
i -= 1
PlayerRef.RemoveItem(kItemList[i], kCorpse)
endwhile
kCorpse.MoveTo(PlayerRef)
EndFunction
死体を検索する
あらかじめ死体に死体検出用スペルを持たせておきます。死体を探す場面で、Cloakスペルを使って条件に「死体検出用スペルを持っている」を指定すれば死体だけにスペルをヒットさせられます。ちなみにスペルはActorにしかかかりません。
スペルを持たせておくというのは応用が効きます。スペルをかけておくのはエリア移動などでアンロードされる時に切れてしまうのでダメです。Actorの一覧を保持するのは一覧のメンテナンスが面倒で、整合性が取れなくなったときに困ります。ActorのActorValueに何か設定するのは他のModとの競合が心配です。というわけで、スペルを持たせるのがベストなのでしょう。
サンプルソースコード
必要最小限の処理を書いてみました。
敗北ペナルティのサンプルソースコード (papyrus)
Scriptname SampleScript Extends ReferenceAlias
Actor PlayerRef
Event OnInit()
PlayerRef = Game.GetPlayer()
PlayerRef.StartDeferredKill() ; 死の超越を開始(死ななくなる)
GoToState("ALIVE")
EndEvent
; 通常時
State ALIVE
Event OnBeginState()
RegisterForSingleUpdate(1.0)
EndEvent
Event OnUpdate()
if PlayerRef.GetActorValue("Health") 0.0
Debug.SendAnimation(PlayerRef, "BleedoutStop")
GoToState("")
else
GoToState("DEAD")
endif
EndEvent
EndState
; 死亡時
State DEAD
Event OnBeginState()
PlayerRef.SetActorValue("Paralysis", 1.0) ; 麻痺させる
Debug.SendAnimationEvent(PlayerRef, "DeathAnim") ; 死ぬアニメーション
Utility.Wait(2.0)
ResetCamera()
EndEvent
Event OnEndState()
PlayerRef.SetActorValue("Paralysis", 0.0)
EndEvent
EndState
Function ResetCamera()
PlayerRef.GetActorBase().SetEssential(true)
PlayerRef.EndDeferredKill()
PlayerRef.StartDeferredKill()
PlayerRef.GetActorBase().SetEssential(false)
EndFunction
実際はさらに処理が必要です。
- キルムーブを受けたときの処理
- 膝をついているときにStaggerを受けないようにする
- 万が一Bleedoutアニメーションが中断したら再開させる
- 蘇生のトリガーを用意する
- 蘇生中や蘇生後に敵が攻撃してこないようにする(敵が戦闘状態のままだとプレイヤーの蘇生後に付いてきてレスキルされる)