ここではスクリプトの小技やタメになる情報を扱います。
予約語self
予約語は役割が予め決まっており変数で使用できない語です。
selfはそのスクリプトをつけているオブジェクトそのものを指します。
例えばリディア(Actor)についているスクリプトの場合はselfが指すのはリディア(housecarlwhiterun)です。
わざわざプロパティ作ったり変数作ったりしなくていいのできれいにコードが書けます。
selfはそのスクリプトをつけているオブジェクトそのものを指します。
例えばリディア(Actor)についているスクリプトの場合はselfが指すのはリディア(housecarlwhiterun)です。
わざわざプロパティ作ったり変数作ったりしなくていいのできれいにコードが書けます。
self.GetDistance(player) ;リディアとプレイヤーとの距離を測ったり Debug.SendAnimationEvent(self,"attackStop") ;リディアに攻撃停止のモーションを送ったりできます
他にもActiveMagicEffectにつけたものでその魔法効果を消す場合は
self.dispel()
クエストにつけたものでそのクエストを停止させるには
self.stop()
このように幅広く使えます。
否定の "!"
スクリプト上で!をつけると~でないという否定の意味になります。
!Actor.IsSprinting() ;スプリント中でない、Actor.IsSprinting() == falseと同じ。
!(Actor.GetEquippedItemType(1) == 0) ; 右手の武器が素手ではない Actor.GetEquippedItemType(1) != 0と同じ
関数の処理について
関数の処理の仕方についておおまかに3つに分類します。
1.同期が必要なLatent Function
スクリプトは上から順に処理していきますが、関数の中でも処理が終わるまでスクリプトが止まるのがLatent Functionです。
代表的な例はWait()です。
Utilty.Wait(1.0)なら1秒経過するまでWaitの部分でスクリプトは待ってます。
次にCast関数です。
これも実際に画面上で魔法が放たれるまで待ってます。
しかし、ノーモーションで魔法が放たれるので、非常に処理が速いです。
スクリプトは上から順に処理していきますが、関数の中でも処理が終わるまでスクリプトが止まるのがLatent Functionです。
代表的な例はWait()です。
Utilty.Wait(1.0)なら1秒経過するまでWaitの部分でスクリプトは待ってます。
次にCast関数です。
これも実際に画面上で魔法が放たれるまで待ってます。
しかし、ノーモーションで魔法が放たれるので、非常に処理が速いです。
CK wiki内のリストには入ってませんがFind系の関数は値が返ってくるまで待ち、処理が遅いです。
2.非同期処理の関数
これは関数の実行が終わったかどうかは関係なく、処理の手続きをしたらさっさと次に進む関数です。
モーションを再生するPlayIdle()がそうです。
モーションが終わったかどうかは関係なく次に進みます。
SoundのPlay()なども同じく、処理の手続きすればすぐ次に行きます。
これと同じ機能で同期処理版がPlayAndWaitです。
スクリプトではなく実際の処理自体はフレームレートやPCの性能に左右されます。
手続された順に再生されるので画面上では遅延が起きるかもしれません。
※仮説上の話ですが、パピルスが言語として遅いのは画面と同期するために意図的に遅くしている可能性も。
これは関数の実行が終わったかどうかは関係なく、処理の手続きをしたらさっさと次に進む関数です。
モーションを再生するPlayIdle()がそうです。
モーションが終わったかどうかは関係なく次に進みます。
SoundのPlay()なども同じく、処理の手続きすればすぐ次に行きます。
これと同じ機能で同期処理版がPlayAndWaitです。
スクリプトではなく実際の処理自体はフレームレートやPCの性能に左右されます。
手続された順に再生されるので画面上では遅延が起きるかもしれません。
※仮説上の話ですが、パピルスが言語として遅いのは画面と同期するために意図的に遅くしている可能性も。
3.画面と同期する必要のないNon-delayed Native Function
画面で起こってることとは全く関係ない、MathやRegister系の関数などです。
これらの関数はフレームレートに左右されずに、常に高速で動きます。
画面で起こってることとは全く関係ない、MathやRegister系の関数などです。
これらの関数はフレームレートに左右されずに、常に高速で動きます。
3.以外は1.だから速いとか2.だから遅いというわけではなく、個々での関数で速度を勘定したほうがいいでしょう。
イベントの処理
同一のフォーム内のスクリプトではイベントのフラグは同時に受けとります。
たとえば、クエスト1に対してスクリプトA・スクリプトBをつけ、
たとえば、クエスト1に対してスクリプトA・スクリプトBをつけ、
;スクリプトA Event OnInit() RegisterForSingleUpdate(1) EndEvent Event OnUpdate() Debug.trace("Script A") EndEvent
;スクリプトB Event OnUpdate() Debug.trace("Script B") EndEvent
どっちのOnUpdateも動きます。
OnUpdateを別に動かしたい場合は、クエストを別にするか、Stateを使ってうまく振り分けましょう。
また、別のスクリプトが誤作動してしまうために、スクリプトをアクターにつけず、基本的に独立しているMagic Effect使います。
OnUpdateを別に動かしたい場合は、クエストを別にするか、Stateを使ってうまく振り分けましょう。
また、別のスクリプトが誤作動してしまうために、スクリプトをアクターにつけず、基本的に独立しているMagic Effect使います。
スクリプト最適化Tips
エラーの少なく、処理の早い書き方があります。
原則
イベントも関数も呼び出しが少ないほうがよい
イベントも関数も呼び出しが少ないほうがよい
なので重複処理を防止したり、繰り返しの処理をまとめたりが重要です。
重複処理をさせない→Stateを使う(スタックエラーの防止策)
敵から攻撃受けた時に両手武器の場合はスタミナに5ダメージという仕組みに加えて、
一度イベントが起きたら0.5秒間同じ処理をさせたくない場合です。
一度イベントが起きたら0.5秒間同じ処理をさせたくない場合です。
Event OnHit(ObjectReference akAggressor, Form akSource, Projectile akProjectile, bool abPowerAttack, bool abSneakAttack, bool abBashAttack, bool abHitBlocked) if akAggressor == None || akSource as Weapon ;攻撃者がいない場合のエラー防止。ダメージソースが武器以外はリターンで即処理中断 return endif GotoState("Busy");BusyのStateに飛ばす int WeapType = (akSource as Weapon).GetWeaponType() ;武器の種類を取得 if WeapType == 5 || WeapType == 6 ;両手剣と両手斧槌だったとき Game.GetPlayer().DamageAV("Stamina",5.0) Utility.Wait(0.5) ;0.5秒間の重複防止のために待機 endif GotoState("") ;Stateを元の状態に戻す EndEvent State Busy Event OnHit(ObjectReference akAggressor, Form akSource, Projectile akProjectile, bool abPowerAttack, bool abSneakAttack, bool abBashAttack, bool abHitBlocked)} EndEvent ;StateがBusyの時はOnHitイベントが起こっても何も処理しない EndState
Stateはその名のとおり状態を表してまして、指定したState中にイベントが起こった場合は、Stateに記述したイベントが優先されます。
例のスクリプトはGotoState()で"Busy"というStateに移動します。Stateが変わっても元のOnHitイベントは継続して処理が進みます。
この間にOnHitイベントが起こっても、すべてState Busyの方で処理されます。最後に空のStateにGotoStateで戻って、また通常のOnHitイベントが起きるようになります。
例のスクリプトはGotoState()で"Busy"というStateに移動します。Stateが変わっても元のOnHitイベントは継続して処理が進みます。
この間にOnHitイベントが起こっても、すべてState Busyの方で処理されます。最後に空のStateにGotoStateで戻って、また通常のOnHitイベントが起きるようになります。
スタックエラーは特定の条件下で、何回も処理が動いてしまうのが原因の一つなので、Stateを使って複数回処理するのを制限することで回避できます。
return文で処理を中断する
returnは本来、戻り値を返すためのものですが、実行されると、実行中のイベントや関数から強制的に中断します。
OnHitなどの頻繁に動くイベントの場合は、条件に合わない処理を事前にreturnで中断させるのは非常に有効です。
OnHitなどの頻繁に動くイベントの場合は、条件に合わない処理を事前にreturnで中断させるのは非常に有効です。
- ダメな例
Event SomeEvent() bool keepRunning = true If someCondition1() keepRunning = false ElseIf someCondition2() keepRunning = false EndIf If(!keepRunning) DoStuff() EndIf EndEvent
- 良い例
Event SomeEvent() If someCondition1() || someCondition2() return EndIf DoStuff() EndEvent
悪い例ではkeepRunningを代入したり、チェックしたりの分の処理してますが、
良い例では条件が合わない場合は即処理を中断します。
良い例では条件が合わない場合は即処理を中断します。
Stateとreturnを組み合わせる場合は、returnで中断されるとStateが戻ってこれなくなるので、
GotoState前に書くか、return前にGotoState("")で抜け出します。
GotoState前に書くか、return前にGotoState("")で抜け出します。
if return endif
GotoState("Busy") if GotoState("") return endif
None(エラーを少なくする方法)
スクリプトが対象のオブジェクトが見つけれない時にエラーになりますが、これがエラーの中ではもっとも多いかと思います。
None Object、 has no 3d
基本的にスクリプトエンジンはこのエラーを無視するので問題無いですが、エラーログ出すぎると重くなったり不安定になったりする可能性があるのと、ログが読みにくくなるのでその対処法です。
None Object、 has no 3d
基本的にスクリプトエンジンはこのエラーを無視するので問題無いですが、エラーログ出すぎると重くなったり不安定になったりする可能性があるのと、ログが読みにくくなるのでその対処法です。
オブジェクトがない状態をオブジェクトはNoneと返します。
また、不必要になったオブジェクトにはNoneを代入すると安全です。
また、不必要になったオブジェクトにはNoneを代入すると安全です。
if PlayerRef != None ; プレイヤーのリファレンスがないときは処理を行わない ..... endif
もしくは
if PlayerRef == None ; プレイヤーのリファレンスが取得できない時はリターンでイベントの強制終了。 return endif
Noneを代入して完全に消す
ObjectReferenceやFormのデータはDeleteを使っただけではスクリプト上では完全に消えてないので、
これを解放するにはNoneを代入する必要があります。
これを解放するにはNoneを代入する必要があります。
ObjectReference Box = PlayerRef.placeAtMe(FXEmptyActivator) ;透明オブジェクトを置く
Box.MoveTo(PlayerRef, 0, 50, 85) ;透明オブジェクト移動
Box.Delete() ;透明オブジェクトを削除
Box = None ;Deleteでゲーム上からは消えますがスクリプトでは残っているのでNone入れて、ないことにする。
Box.MoveTo(PlayerRef, 0, 50, 85) ;透明オブジェクト移動
Box.Delete() ;透明オブジェクトを削除
Box = None ;Deleteでゲーム上からは消えますがスクリプトでは残っているのでNone入れて、ないことにする。
同じアクセサ関数(Get~系)を複数回使用しないこと。
アクセサ関数はなにか取得する(Get~)関数です。Game.GetPlayer()だとか、GetTargetActor()ですね。
- ダメな例
Event SomeEvent() GetTargetActor().AddItem(coolItem, 1) GetTargetActor().AddSpell(coolSpell) GetTargetActor().Kill() GetTargetActor().Resurrect() EndEvent
なぜダメかといえば、毎行たびにGetTargetActor()の処理を行い、取得しているからです。
つまり例では4回処理してます。
はじめの一行でGetTargetActor()を取得して変数に代入し、あとはそれを当てはめたほうがコードの見通しもよく効率的です。
つまり例では4回処理してます。
はじめの一行でGetTargetActor()を取得して変数に代入し、あとはそれを当てはめたほうがコードの見通しもよく効率的です。
- よい例
Event SomeEvent() Actor selfActor = GetTargetActor() selfActor.AddItem(coolItem, 1) selfActor.AddSpell(coolSpell) selfActor.Kill() selfActor.Resurrect() EndEvent
例外としてはGetTargetActor()の使用が1回だけの場合にはselfActor等の不必要な変数を追加する必要はなく、以下のが効率的です。
GetTargetActor().AddItem(coolItem, 1)
変数はローカルで保持する
不必要な静的変数を設定しないことです。
- ダメな例
int onHitVariable ;OnHitイベントで使う変数 int onDeathVariable ;OnDeath event int bothEventsVariable ;両方のイベントで使う変数 Event OnHit(<parameters>) DoStuffWith(onHitVariable) DoStuffWith(bothEventsVariable) EndEvent Event OnDeath(<parameters>) DoStuffWith(onDeathVariable) DoStuffWith(bothEventsVariable) EndEvent
- 良い例
int bothEventsVariable ;両方のイベント間で使う変数は静的変数としてイベント外で定義しておく Event OnHit(<parameters>) int onHitVariable ;OnHitでしか使わない変数はOnHit内で定義 DoStuffWith(onHitVariable) DoStuffWith(bothEventsVariable) EndEvent Event OnDeath(<parameters>) int onDeathVariable ;OnDeathでしか使わない変数はOnDeath内で定義 DoStuffWith(onDeathVariable) DoStuffWith(bothEventsVariable) EndEvent
Is3Dloaded()
3Dデータとして読み込まれているかどうかの判定をする関数で、has no 3d~のエラー対策に使えます。
インベントリに回収しちゃって処理ができない場合や、ラグがあって3Dオブジェクトが設置される前にスクリプトが稼働した場合にhas no 3dのエラーがでます。
インベントリに回収しちゃって処理ができない場合や、ラグがあって3Dオブジェクトが設置される前にスクリプトが稼働した場合にhas no 3dのエラーがでます。
If self.Is3Dloaded() == True ;3Dデータが読まれているなら処理 Endif
ラグ防止の場合:3Dデータが読まれるまで待機
int i = 10 ;時間切れを10秒に設定 While self.Is3Dloaded() == False && i > 0 ;3Dデータが読み込まれるか時間切れまで待つ Utility.Wait(1.0) i -= 1 EndWhile
3Dデータが必ず読み込まれるという保証はないため、タイムアウトを設定するのは極めて重要です。
引数が多い関数やイベントほど重い
パピルスの仕様で、引数から変換するときの処理が重いのです。したがって引数が多いほど重いので
OnHitイベントやFind~()は重いです(OnHitは頻発するのとFindはそもそも探索が遅いのあります)。
別のイベントや関数で代替できるならそちらでしたほうが良い場合もあります。
OnHitイベントやFind~()は重いです(OnHitは頻発するのとFindはそもそも探索が遅いのあります)。
別のイベントや関数で代替できるならそちらでしたほうが良い場合もあります。
安易なスクリプト使用の代替回避をしない
スクリプト使わないパターンでよくあるのが魔法のアビリティのコンディションで代替するパターンでこれは極めて悪手です。
スペルのアビリティのコンディションは毎秒条件の判定があるのでスクリプトで毎秒ループしてるのと同じぐらい重いです。
スクリプトのループと違って、papyrusログにスタックエラーが出ないのでより悪質です。
素直にスクリプト使ってイベント駆動型にしたほうが断然軽く安定します。
スペルのアビリティのコンディションは毎秒条件の判定があるのでスクリプトで毎秒ループしてるのと同じぐらい重いです。
スクリプトのループと違って、papyrusログにスタックエラーが出ないのでより悪質です。
素直にスクリプト使ってイベント駆動型にしたほうが断然軽く安定します。
スクリプト最適化実践編
装備時のイベント重複防止
OnObjectEquippedは何か装備したときに発動するイベントですが、
一つのアイテムにもかかわらず、6回以上(特にエンチャント武器)呼び出されたりするうえ、対処が厄介です。
例では武器装備時に処理したい場合です。
一つのアイテムにもかかわらず、6回以上(特にエンチャント武器)呼び出されたりするうえ、対処が厄介です。
例では武器装備時に処理したい場合です。
Form PreObj = None Event OnObjectEquipped(Form akBaseObject, ObjectReference akReference) if akBaseObject ==None || akBaseObject as Enchantment || PreObj == akBaseObject ; ベースオブジェクトが取得できない場合とエンチャントの除外、装備が前と同じ場合の除外 return endif GotoState("Busy") PreObj = akBaseObject ; 処理 GotoState(") EndEvent State Busy Event OnObjectEquipped(Form akBaseObject, ObjectReference akReference) EndEvent EndState
解説
aksourceをas enchantmentでキャスト(変換)すると、エンチャントかどうかの判定になる。
エンチャントが武器より先に処理されてしまって肝心の武器のほうがBusyで除外されちゃうので、エンチャントは最初にリターンで中断。
PreObjに前回のオブジェクトを代入しておいて、前回と同じだったらリターンで中断。ほぼ同タイミングぐらいに処理されるのでBusyだけだと間に合わずにこれが必要。
aksourceをas enchantmentでキャスト(変換)すると、エンチャントかどうかの判定になる。
エンチャントが武器より先に処理されてしまって肝心の武器のほうがBusyで除外されちゃうので、エンチャントは最初にリターンで中断。
PreObjに前回のオブジェクトを代入しておいて、前回と同じだったらリターンで中断。ほぼ同タイミングぐらいに処理されるのでBusyだけだと間に合わずにこれが必要。
OnHitの重複防止
OnHitはエンチャントなどで延焼させている場合に、延焼ダメージが攻撃した武器ダメージと同じ換算してしまう仕様なので、けっこう重複しやすいのです。
事前にソース元を代入して被ったら飛ばす仕組み。
事前にソース元を代入して被ったら飛ばす仕組み。
Form PreSource = None Event OnHit(ObjectReference akAggressor, Form akSource, Projectile akProjectile, bool abPowerAttack, bool abSneakAttack, bool abBashAttack, bool abHitBlocked) if akAggressor == None || akSource as Weapon || PreSource == akSource;攻撃者がいない場合のエラー防止とダメージソースが武器以外はリターンで即処理中断 return endif GotoState("Busy");BusyのStateに飛ばす PreSource = akSource ;PreSourceに今のダメージ元を代入 int WeapType = (akSource as Weapon).GetWeaponType() ;武器の種類を取得 if WeapType == 5 || WeapType == 6 ;両手剣と両手斧槌だったとき Game.GetPlayer().DamageAV("Stamina",5.0) endif Utility.Wait(0.5) ;0.5秒間の重複防止のために待機 PreSource = None ;PreSourceをなしの状態に戻す。 GotoState("") ;Stateを元の状態に戻す EndEvent State Busy Event OnHit(ObjectReference akAggressor, Form akSource, Projectile akProjectile, bool abPowerAttack, bool abSneakAttack, bool abBashAttack, bool abHitBlocked)} EndEvent ;StateがBusyの時はOnHitイベントが起こっても何も処理しない EndState
スクリプトログをとる(デバッグの仕方)
スクリプトログの設定
マイドキュメント\My Games\Skyrim\Skyrim.ini
を開いて、以下の通りにしたあと保存。(項目がなければ追加)
を開いて、以下の通りにしたあと保存。(項目がなければ追加)
[Papyrus] bEnableLogging=1 bEnableTrace=1 bLoadDebugInformation=1
すると次回から
マイドキュメント\My Games\Skyrim\Logs\Script
にPapyrus.0.logというのが出ますのでメモ帳以外のテキストエディタで開くとデバッグの内容が見れます。
マイドキュメント\My Games\Skyrim\Logs\Script
にPapyrus.0.logというのが出ますのでメモ帳以外のテキストエディタで開くとデバッグの内容が見れます。
LogExpertを導入
リアルタイムでログが取れるツールです。
- LogExpertをダウンロードします。
- LogExpert.exeを起動します。
- File→Openから、マイドキュメント\My Games\Skyrim\Logs\Script\Papyrus.0.logを開きます。
- Options→Always on Topを押して、常に手前に表示にします。
- Optionの下あたりにあるメニューのFollow Tailsにチェックを入れます。
スクリプトにデバッグ情報の記載
ログに書き出すにはDebug.Trace("文字")を使います。""のあとに+を使うと変数や関数の結果を書き出せます。
例:
Event OnObjectEquipped(Form akBaseObject, ObjectReference akReference) Debug.Trace("Debug:FormName" + akBaseObject.GetName() ) EndEvent
これでゲームとLogExpertを起動して、装備を付けたりすることでスクリプトの動作をリアルタイムで確認できます。
意図する結果になるまで以下の手順で実験してみてください。
- スクリプトを書き直してコンパイル
- コンソールコマンドで reloadscript script名
OnAnimationEventで取得できるイベント
RegisterForAnimationEventで登録したアニメーションイベント(モーション)をOnAnimationEventで取得することができますが、
取得できるのとできないのがあります。
☓staggerStart
○staggerStop
Start系のモーションは軒並みダメです。
Skyrim - Animation.bsaの中のmeshes\responses\actorresponse.txt
というテキストファイルに記載されてるのが使用可能なアニメーションイベントです。
取得できるのとできないのがあります。
☓staggerStart
○staggerStop
Start系のモーションは軒並みダメです。
Skyrim - Animation.bsaの中のmeshes\responses\actorresponse.txt
というテキストファイルに記載されてるのが使用可能なアニメーションイベントです。
ブロックの動作はじめにイベントを受け取りたい場合にBlockStartだとダメですが、裏ワザ的なやり方があります。
SoundPlay.NPCHumanCombatShieldBlockです。
SoundPlay系は受け取れるので開始時にイベント取得したいなという時にSoundPlayのAnimeEventをあたってみるといいと思います。
Gameplay -> Animations.. -> AnimEventの選択項目でSoundPlayを探して手当たり次第試してみましょう。
SoundPlay.NPCHumanCombatShieldBlockです。
SoundPlay系は受け取れるので開始時にイベント取得したいなという時にSoundPlayのAnimeEventをあたってみるといいと思います。
Gameplay -> Animations.. -> AnimEventの選択項目でSoundPlayを探して手当たり次第試してみましょう。
- 使えそうなイベント一覧
MRh_SpellFire_Event | 右手で魔法を放ったとき |
MLh_SpellFire_Event | 左手で魔法を放ったとき |
arrowRelease | 矢を放ったとき |
BowDrawn | 最大限弓を引いたとき |
weaponSwing | 右手の武器を振ったとき |
weaponLeftSwing | 右手の武器を振ったとき |
preHitFrame | 近接攻撃がヒットする直前 |
HitFrame | 近接攻撃がヒットしたとき。当たらない場合も検出する |
BashExit | バッシュしたとき |
BashStop | バッシュしたとき |
BashRelease | バッシュボタンを押し続けてバッシュが発動したとき。パワーバッシュは盾装備時のみ検出する |
- 参考になりそうなサイト
Creationkit.com:プレイヤー動作時のAnimEventが動く順番
Withe01さんのMOD作成日誌:20130930-対人用KillMove一覧
Withe01さんのMOD作成日誌:AnimationEvent
Withe01さんのMOD作成日誌:20130930-対人用KillMove一覧
Withe01さんのMOD作成日誌:AnimationEvent
Withe01さんブログは主に動作をさせる(イベントを起こす)ときのことが書いてある。
AnimationVariableの使い方
スカイリムのモーションを司るHavok Behaviorが扱うアニメーション変数(AnimationVariable)を取得したり変更したりできます。
これを使うことによってActorに関して細かく状態を判定したり、制御したりできます。
この変数はActorの種類によって異なります。
例えばCharacter(人)だとIsStaggeringはありますが、Dragonにはありません。
どんなアニメーション変数が何があるのかは以下のスプレッドシートのデータを確認してください。
人のアニメーション変数とイベントデータ
人以外のアニメーション変数とイベントデータ
これを使うことによってActorに関して細かく状態を判定したり、制御したりできます。
この変数はActorの種類によって異なります。
例えばCharacter(人)だとIsStaggeringはありますが、Dragonにはありません。
どんなアニメーション変数が何があるのかは以下のスプレッドシートのデータを確認してください。
人のアニメーション変数とイベントデータ
人以外のアニメーション変数とイベントデータ
そのほかは、Josh Behavior file patcherで確認ができます。
人ならbehaviorフォルダの0_master.hkx、ドラゴンならdragonbehavior.hkx。
人ならbehaviorフォルダの0_master.hkx、ドラゴンならdragonbehavior.hkx。
これらのアニメーション関数はConditionでも使えます。
Condtionでの関数名はGetGraphVariableFloatとGetGraphVariableIntです。
例:GetGraphVariableInt "IsStaggering" == 1 ;BoolはIntで代用。1はtrue 0はfalse
Condtionでの関数名はGetGraphVariableFloatとGetGraphVariableIntです。
例:GetGraphVariableInt "IsStaggering" == 1 ;BoolはIntで代用。1はtrue 0はfalse
さまざまな状態の判定
Actor.GetAnimationVariableBool("xxx")
判定 | xxxに記載する文字列 |
攻撃中 | IsAttacking |
ブロック中 | IsBlocking |
バッシュ中 | IsBashing |
はじかれ中 | IsRecoiling |
よろめき中 | IsStaggering |
抜刀中 | IsEquipping |
納刀中 | IsUnequipping |
ジャンプ中 | bInJumpState |
ブロック成功 | IsBlockHit |
例:Actor.GetAnimationVariableBool("IsAttacking") ;攻撃中、パワーアタックも含まれる
一人称視点かどうかの判定
player.GetAnimationVariableInt("i1stPerson") == 1
移動方向の判定
floatのDirectionを使います。
floatのDirectionを使います。
Actor.GetAnimationVariableFloat("Direction") == 0 ; forward
時計回りに0から1まで。
0 | 前と立ち状態 |
0.125 | 右斜め前 |
0.25 | 右 |
0.375 | 右斜め後 |
0.5 | 後ろ |
0.625 | 左斜め後 |
0.75 | 左 |
0.875 | 左斜め前 |
コントローラーのアナログスティックはSKSEやScriptDragon(※ScriptDragonはAnimationVaribleが使えません)でも検知できないので、このDirectionを使います。
立ち状態と前とで区別つけるときは下の移動中判定と組み合わせてください。
立ち状態と前とで区別つけるときは下の移動中判定と組み合わせてください。
移動中かどうかの判定
一見するとbInMoveStateなんですが、壮大なトラップで片手どちらかに魔法か杖を持ってると移動中でもFalseを返します。
代わりにSpeedを使います。
一見するとbInMoveStateなんですが、壮大なトラップで片手どちらかに魔法か杖を持ってると移動中でもFalseを返します。
代わりにSpeedを使います。
if Actor.GetAnimationVariableFloat("Speed") < 5.0 ;停止中
iStateの変数
GetAnimationVariableInt("iState")で取得できる値はMovement Typeと連動していると思われます。
何ができるかというと、状態判定ができます。
例:パピルスにはIsBlockingがないので、以下のようにします。(上のIsBlockingを使ったほうが確実)
例:パピルスにはIsBlockingがないので、以下のようにします。(上のIsBlockingを使ったほうが確実)
if (Actor.GetAnimationVariableInt("iState") == 4) || (Actor.GetAnimationVariableInt("iState") == 17)
変数の数値が何を意味するかは以下の通り。
スプリント中 | 1 | iState_NPCSprinting |
スニーク移動中 | 2 | iState_NPCSneaking |
弓・クロスボウ構え中、リロード中 | 3 | iState_NPCBowDrawn |
ブロック中 | 4 | iState_NPCBlocking |
ダウン中 | 5 | iState_NPCBleedout |
片手・素手移動中 | 6 | iState_NPC1HM |
両手移動中 | 7 | iState_NPC2HM |
弓・クロスボウ移動中 | 8 | iState_NPCBow |
魔法移動中・停止中 | 9 | iState_NPCMagic |
魔法・杖キャスト中 | 10 | iState_NPCMagicCasting |
騎乗時? | 11 | iState_NPCHorse |
片手・素手攻撃中 | 12 | iState_NPCAttacking |
両手攻撃中 | 13 | iState_NPCAttacking2H |
パワーアタック中 | 14 | iState_NPCPowerAttacking |
酩酊中? | 15 | iState_NPCDrunk |
弓・クロスボウ構え中(QuickShot習得後) | 16 | iState_NPCBowDrawnQuickShot |
ブロックランナー取得後ブロック中 | 17 | iState_NPCBlockingShieldCharge |
騎乗時移動中 | 60 | iState_HorseDefault |
騎乗時スプリント中 | 61 | iState_HorseSprint |
騎乗時ジャンプ中 | 62 | iState_HorseFall |
騎乗時水泳中 | 63 | iState_HorseSwim |
これはBehaviorファイルのBSiStateTaggingGeneratorという項目で指定してます。
iStateToSetAsで数値指定で、iPriorityが優先度です。
一部プレイヤーとNPCで違う模様。アクターによっても違います。
iStateToSetAsで数値指定で、iPriorityが優先度です。
一部プレイヤーとNPCで違う模様。アクターによっても違います。
セーブデータに残るもの
セーブするとセーブデータに保存されるものがあります。
- グローバル変数
- 静的変数
- プロパティ
一旦セーブされたデータでMODを更新したときにプロパティや静的変数などを削除した場合やMODを抜いた場合はセーブ内と一致しないのでログにエラーを吐きます。
エラーを吐くのですが、データがないと無視するので基本的に害はありません。
エラーを吐くのですが、データがないと無視するので基本的に害はありません。
不必要になったプロパティの削除
プロパティはesp側にも紐ついてるので、そちらも消す必要があります。
- スクリプトのついてるPropertyボタンを押してプロパティウィンドウを出します。
- 不要なプロパティのClear Valueを押して消してください。
- そのあと、スクリプト側のプロパティの記述を消します。
- espを保存します。

SM Eventを使ったRepeatQuest
OnUpdateを使わずにループができるクエストの作り方です。
セル移動時関係のイベント
セル移動時にスクリプトを動かしたいときにいくつかイベントがあるんですが、どれも癖があってそれを記したいと思います。
Onload
3Dオブジェクトがロードされるときに発生するイベントで、ロード画面が挟むセル移動でなら起きます。
ただしセルを移動してすぐ戻る場合はセル移動時にロード挟まないのでその場合は発生しません。
プレイヤーは稼働しません。
3Dオブジェクトがロードされるときに発生するイベントで、ロード画面が挟むセル移動でなら起きます。
ただしセルを移動してすぐ戻る場合はセル移動時にロード挟まないのでその場合は発生しません。
プレイヤーは稼働しません。
OnAttachedToCell
セルからセルに移動するときに発生するイベントです。
たとえばワールドTamrielのWildness1からWildness2に移動するときにも発生します。
ただしプレイヤーは稼働しません。
またスカイリムからホワイトランに入るときには動きません。(逆は動くので条件不明)
セルからセルに移動するときに発生するイベントです。
たとえばワールドTamrielのWildness1からWildness2に移動するときにも発生します。
ただしプレイヤーは稼働しません。
またスカイリムからホワイトランに入るときには動きません。(逆は動くので条件不明)
OnLocationChange
ロケーションの移動時に動くイベントですが、複数のセルをまとめて一つのロケーションとして扱う場合があって、例えばホワイトラン→スカイリム、スカイリム→ホワイトランは動きません。
ですので一般的にセル移動時判定には向いてません。
ロケーションの移動時に動くイベントですが、複数のセルをまとめて一つのロケーションとして扱う場合があって、例えばホワイトラン→スカイリム、スカイリム→ホワイトランは動きません。
ですので一般的にセル移動時判定には向いてません。
OnCellLoad
セルロード時にイベントが発生しますが、メモリキャッシュ済みのセルの場合は発生しません。
続けてゲームプレイする場合に一度入ったセルにもう一度入った場合に発生しない可能性が高いです。
プレイヤー(エイリアス)に使えます。
セルロード時にイベントが発生しますが、メモリキャッシュ済みのセルの場合は発生しません。
続けてゲームプレイする場合に一度入ったセルにもう一度入った場合に発生しない可能性が高いです。
プレイヤー(エイリアス)に使えます。
NPCであれば、Onloadをおすすめします。
プレイヤーは厳密さを要求しないのであれば、OnCellLoadで大抵何とかなります。
プレイヤーは厳密さを要求しないのであれば、OnCellLoadで大抵何とかなります。
NPCへのパーク付与について
コンソールやスクリプトのAddPerkについてプレイヤーに対しては正常に機能しますがNPCに対しては残念ながら機能しません。
同様に魔法のマジックエフェクトの"Perk to Apply"にパークを設定されてた場合でも該当のマジックエフェクトが掛かったNPCにはパークは付与されません。
同様に魔法のマジックエフェクトの"Perk to Apply"にパークを設定されてた場合でも該当のマジックエフェクトが掛かったNPCにはパークは付与されません。
ゲーム中にパークを持たせる手段全てがNPCに効果が無いせいでNPCにパークを持たせる場合はCKやTES5Editを使って事前に付与させる必要がありました。
しかし最近ではpowerofthree's Papyrus Extender(LE / SE)というスクリプトの関数拡張SKSEプラグインがバージョンアップを重ねた結果、スクリプトでNPCへのパーク付与が行う事が可能となっております。
(SEには設定ファイルから事前にパークを持たせるというmodは存在していますが、ゲーム中で付与となると自分が知る限り唯一の方法となります)
しかし最近ではpowerofthree's Papyrus Extender(LE / SE)というスクリプトの関数拡張SKSEプラグインがバージョンアップを重ねた結果、スクリプトでNPCへのパーク付与が行う事が可能となっております。
(SEには設定ファイルから事前にパークを持たせるというmodは存在していますが、ゲーム中で付与となると自分が知る限り唯一の方法となります)
※パークを付与する場合
; targetActorに対象のActor、addPerkに付与させたいPerkを渡す po3_sksefunctions.AddBasePerk(targetActor, addPerk)
※付与したパークを消す場合は下記
po3_sksefunctions.RemoveBasePerk(targetActor, addPerk)
※現状だと不具合あり
- 同一のアクターに二回以上AddBasePerkを行うと最初にAddBasePerkで付与したパークが再付与され二重に適用される(disable→enableで正常に戻る)
- NPCにパーク付与後に『対象NPCがパーク付与前かつセルにロードされている』セーブデータをロードするとNPCにパークが付与される
- NPCにパーク付与後にNPCがセルからアンロードされた状態で保存した後にスカイリムを終了させ、再度起動してタイトルからロードした場合、ロード後のパーク付与がNPCに適用されない
最終更新:
添付ファイル