atwiki
メニュー ページ一覧
SkyrimMOD作成wiki

  • @メニュー
    • 新規ページ作成
    • 新規ページ作成(その他)
      • このページをコピーして新規ページ作成
      • このウィキ内の別ページをコピーして新規ページ作成
      • アップロードファイルから新規ページ作成
      • 他のホームページから引用して新規ページ作成
      • スレッド式掲示板から引用して新規ページ作成
      • このページの子ページを作成
    • アットウィキで新規ウィキ作成
  • 編集
    • ページ編集
    • ページ編集(簡易版)
    • メニュー非表示でページ編集
    • ページ名変更
    • ページの閲覧/編集権限変更
    • ページの編集モード変更
    • このページにファイルをアップロード
    • メニューを編集
  • 表示
    • 最新版変更点 (差分)
    • 編集履歴 (バックアップ)
    • このウィキの全ページ一覧
    • 編集履歴のあるページ一覧
    • このウィキのタグ一覧
    • このウィキのタグ一覧(更新順)
    • このwikiの更新情報RSS
    • このwikiの新着ページRSS
  • ツール
    • このウィキ内を検索
    • このウィキの管理者に連絡
    • このページを通報・違反報告する
  • ヘルプ
    • ご利用ガイド
    • はじめての方へ
    • プラグイン一覧
    • よくある質問
    • 編集モードの違いについて
    • 不具合や障害を見つけたら
    • 管理・設定マニュアル

アットウィキホームへ
atwiki
  • @メニュー
    • 新規ページ作成
    • 新規ページ作成(その他)
      • このページをコピーして新規ページ作成
      • このウィキ内の別ページをコピーして新規ページ作成
      • アップロードファイルから新規ページ作成
      • 他のホームページから引用して新規ページ作成
      • スレッド式掲示板から引用して新規ページ作成
      • このページの子ページを作成
    • アットウィキで新規ウィキ作成
  • 編集
    • ページ編集
    • ページ編集(簡易版)
    • メニュー非表示でページ編集
    • ページ名変更
    • ページの閲覧/編集権限変更
    • ページの編集モード変更
    • このページにファイルをアップロード
    • メニューを編集
  • 表示
    • 最新版変更点 (差分)
    • 編集履歴 (バックアップ)
    • このウィキの全ページ一覧
    • 編集履歴のあるページ一覧
    • このウィキのタグ一覧
    • このウィキのタグ一覧(更新順)
    • このwikiの更新情報RSS
    • このwikiの新着ページRSS
  • ツール
    • このウィキ内を検索
    • このウィキの管理者に連絡
    • このページを通報・違反報告する
  • ヘルプ
    • ご利用ガイド
    • はじめての方へ
    • プラグイン一覧
    • よくある質問
    • 編集モードの違いについて
    • 不具合や障害を見つけたら
    • 管理・設定マニュアル
  • このウィキに参加
  • ログイン

SkyrimMOD作成wiki

Tips

MENU

最初に

  • mod制作をはじめる前に
  • Skyrim Special Edition

MOD制作Tips

  • CK Tips
  • Nif Tips
  • ファイルの種類
  • データ集
  • テクスチャ作成

スクリプト

  • Papyrus入門
  • 逆引きリファレンス
  • Tips
  • SKSEプラグイン

チュートリアル

  • ポーズ・モーション作成
  • 鉛筆 / サイコロ / Tシャツ
  • 武器作成 2 3
  • GND.nif作成
  • スクリプトmod製作
  • SkyUI MCM
  • ゲーム設定の調整
  • 装備のBBP対応
  • チュートリアルリンク集

ツール

Blender

  • Blenderで入出力
  • Blender入門
  • モデリング入門
  • Blender Tips
  • Blenderリンク集
  • Blender アドオン

その他

  • ENB
  • Modの英語
  • Mod公開後のトラブル対策

リンク

  • Creation Kit Wiki
  • UESP:Skyrim
  • Textures.com
  • Skyrim Wiki JP
  • キャラメイクwiki


更新履歴

取得中です。

合計: -
今日: -
昨日: -

  • メニュー編集


ここではスクリプトの小技やタメになる情報を扱います。 

  • 予約語self
  • 否定の "!"
  • 関数の処理について
  • イベントの処理
  • スクリプト最適化Tips
    • 重複処理をさせない→Stateを使う(スタックエラーの防止策)
    • return文で処理を中断する
    • None(エラーを少なくする方法)
      • Noneを代入して完全に消す
    • 同じアクセサ関数(Get~系)を複数回使用しないこと。
    • 変数はローカルで保持する
    • Is3Dloaded()
    • 引数が多い関数やイベントほど重い
    • 安易なスクリプト使用の代替回避をしない
  • スクリプト最適化実践編
    • 装備時のイベント重複防止
    • OnHitの重複防止
    • スクリプトログをとる(デバッグの仕方)
      • スクリプトログの設定
      • LogExpertを導入
      • スクリプトにデバッグ情報の記載
  • OnAnimationEventで取得できるイベント
  • AnimationVariableの使い方
    • さまざまな状態の判定
    • iStateの変数
  • セーブデータに残るもの
    • 不必要になったプロパティの削除
  • SM Eventを使ったRepeatQuest
  • セル移動時関係のイベント
  • NPCへのパーク付与について


予約語self

予約語は役割が予め決まっており変数で使用できない語です。
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関数です。
これも実際に画面上で魔法が放たれるまで待ってます。
しかし、ノーモーションで魔法が放たれるので、非常に処理が速いです。

CK wiki内のリストには入ってませんがFind系の関数は値が返ってくるまで待ち、処理が遅いです。

2.非同期処理の関数
これは関数の実行が終わったかどうかは関係なく、処理の手続きをしたらさっさと次に進む関数です。
モーションを再生するPlayIdle()がそうです。
モーションが終わったかどうかは関係なく次に進みます。
SoundのPlay()なども同じく、処理の手続きすればすぐ次に行きます。
これと同じ機能で同期処理版がPlayAndWaitです。
スクリプトではなく実際の処理自体はフレームレートやPCの性能に左右されます。
手続された順に再生されるので画面上では遅延が起きるかもしれません。
※仮説上の話ですが、パピルスが言語として遅いのは画面と同期するために意図的に遅くしている可能性も。

3.画面と同期する必要のないNon-delayed Native Function
画面で起こってることとは全く関係ない、MathやRegister系の関数などです。
これらの関数はフレームレートに左右されずに、常に高速で動きます。

3.以外は1.だから速いとか2.だから遅いというわけではなく、個々での関数で速度を勘定したほうがいいでしょう。


イベントの処理

同一のフォーム内のスクリプトではイベントのフラグは同時に受けとります。
たとえば、クエスト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使います。

スクリプト最適化Tips

エラーの少なく、処理の早い書き方があります。

原則
イベントも関数も呼び出しが少ないほうがよい

なので重複処理を防止したり、繰り返しの処理をまとめたりが重要です。

重複処理をさせない→Stateを使う(スタックエラーの防止策)

敵から攻撃受けた時に両手武器の場合はスタミナに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イベントが起きるようになります。

スタックエラーは特定の条件下で、何回も処理が動いてしまうのが原因の一つなので、Stateを使って複数回処理するのを制限することで回避できます。

return文で処理を中断する

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("")で抜け出します。

if 
	return
endif

GotoState("Busy")
if 
	GotoState("")
	return
endif


None(エラーを少なくする方法)

スクリプトが対象のオブジェクトが見つけれない時にエラーになりますが、これがエラーの中ではもっとも多いかと思います。
None Object、 has no 3d
基本的にスクリプトエンジンはこのエラーを無視するので問題無いですが、エラーログ出すぎると重くなったり不安定になったりする可能性があるのと、ログが読みにくくなるのでその対処法です。

オブジェクトがない状態をオブジェクトはNoneと返します。
また、不必要になったオブジェクトにはNoneを代入すると安全です。
if PlayerRef != None ; プレイヤーのリファレンスがないときは処理を行わない
..... 
endif
もしくは
if PlayerRef == None ; プレイヤーのリファレンスが取得できない時はリターンでイベントの強制終了。
	return
endif

Noneを代入して完全に消す

ObjectReferenceやFormのデータはDeleteを使っただけではスクリプト上では完全に消えてないので、
これを解放するにはNoneを代入する必要があります。

ObjectReference Box = PlayerRef.placeAtMe(FXEmptyActivator) ;透明オブジェクトを置く
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()を取得して変数に代入し、あとはそれを当てはめたほうがコードの見通しもよく効率的です。

  • よい例
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のエラーがでます。
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はそもそも探索が遅いのあります)。
別のイベントや関数で代替できるならそちらでしたほうが良い場合もあります。

安易なスクリプト使用の代替回避をしない

スクリプト使わないパターンでよくあるのが魔法のアビリティのコンディションで代替するパターンでこれは極めて悪手です。
スペルのアビリティのコンディションは毎秒条件の判定があるのでスクリプトで毎秒ループしてるのと同じぐらい重いです。
スクリプトのループと違って、papyrusログにスタックエラーが出ないのでより悪質です。
素直にスクリプト使ってイベント駆動型にしたほうが断然軽く安定します。


スクリプト最適化実践編


装備時のイベント重複防止

OnObjectEquippedは何か装備したときに発動するイベントですが、
一つのアイテムにもかかわらず、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だけだと間に合わずにこれが必要。

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というのが出ますのでメモ帳以外のテキストエディタで開くとデバッグの内容が見れます。

LogExpertを導入

リアルタイムでログが取れるツールです。

  1. LogExpertをダウンロードします。
  2. LogExpert.exeを起動します。
  3. File→Openから、マイドキュメント\My Games\Skyrim\Logs\Script\Papyrus.0.logを開きます。
  4. Options→Always on Topを押して、常に手前に表示にします。
  5. Optionの下あたりにあるメニューのFollow Tailsにチェックを入れます。

スクリプトにデバッグ情報の記載

ログに書き出すにはDebug.Trace("文字")を使います。""のあとに+を使うと変数や関数の結果を書き出せます。

例:
Event OnObjectEquipped(Form akBaseObject, ObjectReference akReference)
	Debug.Trace("Debug:FormName" + akBaseObject.GetName() )
EndEvent

これでゲームとLogExpertを起動して、装備を付けたりすることでスクリプトの動作をリアルタイムで確認できます。

意図する結果になるまで以下の手順で実験してみてください。
  1. スクリプトを書き直してコンパイル
  2. コンソールコマンドで reloadscript script名

OnAnimationEventで取得できるイベント

RegisterForAnimationEventで登録したアニメーションイベント(モーション)をOnAnimationEventで取得することができますが、
取得できるのとできないのがあります。
☓staggerStart
○staggerStop
Start系のモーションは軒並みダメです。
Skyrim - Animation.bsaの中のmeshes\responses\actorresponse.txt
というテキストファイルに記載されてるのが使用可能なアニメーションイベントです。

ブロックの動作はじめにイベントを受け取りたい場合にBlockStartだとダメですが、裏ワザ的なやり方があります。
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さんブログは主に動作をさせる(イベントを起こす)ときのことが書いてある。

AnimationVariableの使い方

スカイリムのモーションを司るHavok Behaviorが扱うアニメーション変数(AnimationVariable)を取得したり変更したりできます。
これを使うことによってActorに関して細かく状態を判定したり、制御したりできます。
この変数はActorの種類によって異なります。
例えばCharacter(人)だとIsStaggeringはありますが、Dragonにはありません。
どんなアニメーション変数が何があるのかは以下のスプレッドシートのデータを確認してください。
人のアニメーション変数とイベントデータ
人以外のアニメーション変数とイベントデータ

そのほかは、Josh Behavior file patcherで確認ができます。
人ならbehaviorフォルダの0_master.hkx、ドラゴンならdragonbehavior.hkx。

関数:
Get/SetAnimationVariableBool
Get/SetAnimationVariableFloat
Get/SetAnimationVariableInt

これらのアニメーション関数はConditionでも使えます。
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を使います。
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を使います。
if Actor.GetAnimationVariableFloat("Speed") < 5.0 ;停止中

iStateの変数

GetAnimationVariableInt("iState")で取得できる値はMovement Typeと連動していると思われます。

何ができるかというと、状態判定ができます。
例:パピルスには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で違う模様。アクターによっても違います。

セーブデータに残るもの

Save File Note(CK wiki)

セーブするとセーブデータに保存されるものがあります。
  • グローバル変数
  • 静的変数
  • プロパティ
一旦セーブされたデータでMODを更新したときにプロパティや静的変数などを削除した場合やMODを抜いた場合はセーブ内と一致しないのでログにエラーを吐きます。
エラーを吐くのですが、データがないと無視するので基本的に害はありません。

不必要になったプロパティの削除

プロパティはesp側にも紐ついてるので、そちらも消す必要があります。
  1. スクリプトのついてるPropertyボタンを押してプロパティウィンドウを出します。
  2. 不要なプロパティのClear Valueを押して消してください。
  3. そのあと、スクリプト側のプロパティの記述を消します。
  4. espを保存します。



SM Eventを使ったRepeatQuest

OnUpdateを使わずにループができるクエストの作り方です。


セル移動時関係のイベント

セル移動時にスクリプトを動かしたいときにいくつかイベントがあるんですが、どれも癖があってそれを記したいと思います。

Onload
3Dオブジェクトがロードされるときに発生するイベントで、ロード画面が挟むセル移動でなら起きます。
ただしセルを移動してすぐ戻る場合はセル移動時にロード挟まないのでその場合は発生しません。
プレイヤーは稼働しません。

OnAttachedToCell
セルからセルに移動するときに発生するイベントです。
たとえばワールドTamrielのWildness1からWildness2に移動するときにも発生します。
ただしプレイヤーは稼働しません。
またスカイリムからホワイトランに入るときには動きません。(逆は動くので条件不明)

OnLocationChange
ロケーションの移動時に動くイベントですが、複数のセルをまとめて一つのロケーションとして扱う場合があって、例えばホワイトラン→スカイリム、スカイリム→ホワイトランは動きません。
ですので一般的にセル移動時判定には向いてません。

OnCellLoad
セルロード時にイベントが発生しますが、メモリキャッシュ済みのセルの場合は発生しません。
続けてゲームプレイする場合に一度入ったセルにもう一度入った場合に発生しない可能性が高いです。
プレイヤー(エイリアス)に使えます。

NPCであれば、Onloadをおすすめします。
プレイヤーは厳密さを要求しないのであれば、OnCellLoadで大抵何とかなります。

NPCへのパーク付与について

コンソールやスクリプトのAddPerkについてプレイヤーに対しては正常に機能しますがNPCに対しては残念ながら機能しません。
同様に魔法のマジックエフェクトの"Perk to Apply"にパークを設定されてた場合でも該当のマジックエフェクトが掛かったNPCにはパークは付与されません。

ゲーム中にパークを持たせる手段全てがNPCに効果が無いせいでNPCにパークを持たせる場合はCKやTES5Editを使って事前に付与させる必要がありました。
しかし最近では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に適用されない
最終更新:2021年02月26日 16:53
添付ファイル
  • ClearValue.PNG
ツイート
このエントリーをはてなブックマークに追加
  • アットウィキ
  • 利用規約
  • プライバシーポリシー
© 2019 AtWiki, Inc.