■Unity開発メモ用のWiki◎現在扱っていること・Unity全般・MMDからUnityへの変換してインポート・MMDの仕様把握・BlenderでMMDモデルのカスタム


目次

イベント系・何かのタイミングで処理したい時

魔法効果開始イベント(OnEffectStart - ActiveMagicEffect)

アクティブな魔法の効果が指定されたターゲットで開始されたときに呼び出されるイベント。
◎構文
Event OnEffectStart(Actor akTarget, Actor akCaster)
◎パラメータ
akTarget: このエフェクトが適用されたアクター。
akCaster: このエフェクトの元となった呪文を唱えたアクター。
◎例
Event OnEffectStart(Actor akTarget, Actor akCaster)
  Debug.Trace("Magic effect was started on " + akTarget)
endEvent

魔法効果の終了イベント(OnEffectFinish - ActiveMagicEffect)

指定されたターゲットでアクティブな魔法の効果が終了したときに呼び出されるイベント。

UI・通知・デバッグ関連

画面の左上隅に通知(Notification - Debug)

画面の左上隅に通知を表示します。
◎構文
Function Notification(string asNotificationText) native global
◎パラメータ
asNotificationText: 左上隅に表示されるテキスト。
◎例
; クラス「hello world」通知を表示する
Debug.Notification("Hello, world!")

通知メッセージ(Notification)

https://www.creationkit.com/index.php?title=Notifi...
Debug.Notification("Hello, world!")

スカイリムのデフォルトの標準メニュー(Message Box)

・選択肢が横並びな為、スペースの都合上、選択肢の個数が限られてしまう
・長いテキスト内容の選択肢も使えない
・選択肢をCK上で追加しなくてはならない
 →本体がMessageというオブジェクトなので

◎参考
【Papyrus.psc】メッセージボックスの説明にアイテム名を取得して表示する
https://skyrimfromnow.blogspot.com/2018/01/papyrus...
【Papyrus.psc】選択式メッセージボックスの作り方
https://skyrimfromnow.blogspot.com/2018/01/papyrus...
NPCの情報を表示する
http://afternun.mydns.jp/blog/skyrimspecialedition...

リストメニューを追加するUI Mod「SkyUI」の「SkyUILib」

・SkyUILibのリストメニューはデフォルトのメニューの横並び状態を縦並びにしたもの
・バニラのデフォルトのメニューと同様に、メニューの上にタイトル的な一文を追加できる
・スクリプトもSkyUILibはバニラのMessageの「show()」と同じ感覚で使える

◎「SkyUILib」をダウンロードする
DL:https://skyrim.2game.info/detail.php?id=57308
↑LE版だが、SEでも動?

◎ドキュメント
・導入
https://github.com/schlangster/skyui-lib/wiki/How-...
・サンプル
https://github.com/schlangster/skyui-lib/wiki/Exam...
・API
https://github.com/schlangster/skyui-lib/wiki/API-...

◎CK・QuestのScriptに取り付ける

UIExtensions(メニューリスト)の使い方

参考:UIExtentions リストメニューの実装方法
https://skyrim-sweetslab.blogspot.com/2018/06/uiex...

・「UIExtensions」のドキュメントは特に無いので注意
・「UIExtensions」のスクリプトソースのパスをコンパイル「Papyrus Compiler」の「Advanced Papyrus.ini」に追記する?
 →上手くいかない?一次的な対策として「Overwrite」フォルダにスクリプトソースを入れる
・、選択肢にさらに子の選択肢を簡単に追加できる
 →上手くコードを書かないとスパゲッティになるので、慣れない内は使わない方が良いかも?

◎UIExtentionsの使用準備
・「UIExtentions.esp」をマスター指定として読み込む
・自作Modの任意のスクリプト内でUIExtentionsのリストメニューを読み込む
UIListMenu listMenu = UIExtensions.GetMenu("UIListMenu") as UIListMenu

◎リストの追加・設定
・「AddEntryItem」という関数で選択肢を追加すると、追加した選択肢は追加した順に0から通し番号が振られる
 →「AddEntryItem」関数の戻り値として取得する
 →「entryParent」や「entryCallback」といった引数の値に使うことで、選択肢の分岐を指定する

◎子選択肢の設定
    • 「entryParent」は自分の親の選択肢の番号
    • 「entryCallback」はおそらく最初の分岐の根元の選択肢の番号
    • また子の選択肢を持つ選択肢については「entryHasChildren」の値を「true」にする

◎「OpenMenu()」関数でメニューを開く
    • 「GetResultInt」で選んだ選択肢の通し番号を取得
    • 「GetResultString」で選んだ選択肢のテキスト自体を取得


◎サブのリストメニューを表示する
	;第1段落のメニュー設定(メインメニュー)
;	listMenu.AddEntryItem("競泳水着",entryHasChildren = true);Child:0
;	listMenu.AddEntryItem("Menu_002",entryHasChildren = true);Child:1
	;第2段落のメニュー設定(サブメニュー1)
;	listMenu.AddEntryItem("KyouEi_Mizugi_Nomal", entryParent = 0, entryCallback = 0)
;	listMenu.AddEntryItem("競泳水着ジャッケット半脱ぎ", entryParent = 1, entryCallback = 1)

UIExtensions(ホイールメニュー)の使い方メモ

◎参考動画

・10:30〜(ホイールメニューのスクリプト実践)



◎サンプルコード

MOD作成のTIPS・メモ

キー入力(RegisterForKey)

GetPlayerTeammateCount(プレイヤーのチームメイトとして設定されているアクターの現在の数を返す)

相手のアクターの名前を取得

【アクター.GetLeveledActorBase().GetName()】
GetActorBase().GetName()

性欲値を取得する(GetArousal)

AppUtil.GetArousal(selfact)

コンソールでスクリプトをリロードする

  • ゲーム起動しながらCK上でスクリプトの編集->コンパイルしたあと、このコマンドで編集内容を反映出来ます。
構文:
reloadscript スクリプト名
※ロードすると反映される?
  • クエストに付いているスクリプトでうまくいかないのがあるのでその時は以下のようにします。

体力のパーセンテージを取得する(GetActorValuePercentage)

https://ck.uesp.net/wiki/GetActorValuePercentage_-...
・GetActorValuePercentage - Actor
・指定されたアクター値をアクターからその最大値のパーセンテージとして取得します (0〜1)
◎例)
; ボブの現在のヘルス アクター値を取得します
float bobsHealth = Bob.GetAVPercentage("Health") 
if (bobsHealth < 0.1) 
  Debug.Trace("Bob のヘルス残量は 10% 未満です") 
endIf

乱数の取得(RandomInt)

https://ck.uesp.net/w/index.php?title=RandomInt_-_...
・RandomInt - Utility
・デフォルトだと1〜100までの間のランダムな値を取得する
◎構文
int Function RandomInt(int aiMin = 0, int aiMax = 100) native global
◎例 1
; 0 から 100 までの乱数を取得します
int random = Utility.RandomInt()
◎例 2.
; 0 から 10 までの乱数を取得します
int random = Utility.RandomInt(0, 10)

MODやDLCのロードオーダーやファイル名を取得する

■GetModByName(esp名のロードオーダーを取得する)
・name (String型) のesp名のロードオーダーを Int型 で取得する。
 espが無ければ 255 (0xFF) を返す。
 →MODやDLCが読み込まれているかの判定にも使える
・内部処理で Int型 を扱う場合には、10進数と16進数を変換する必要はない
Game.GetModByName("Hoge_MOD.esp")

■Game.GetModName(ESPの名称を取得する)
・modIndex (Int型) をロードオーダーとして読み、そのesp名を String型 で取得する。
Game.GetModName(int modIndex)

◎サンプル
		;esp名のロードオーダーを取得する
 		int TargetModIndex = Game.GetModByName("My_test_MOB_01.esp")
		Debug.Trace("@@@:GetModByName:" + TargetModIndex)
		;ESPの名称を取得する
		;ベースIDの頭に16進数の「0x」を追加する
		String Esp_Name = Game.GetModName(TargetModIndex)
		Debug.Trace("@@@:GetModName:" + Esp_Name)

		;例:ナゼーム(Nazeem)・BaseID(00013BBF)・RefID(0001A6A4)
		;フォームID番号で指定されたフォームを取得
		Form someForm = Game.GetForm(0x00013BBF)
		Debug.Trace("@@@:GetForm:" + someForm)
		;→[ActorBase < (00013BBF)>]が返される

		;このフォームのフォームIDを返します。
		int formID = someForm.GetFormID()
		Debug.Trace("@@@:GetFormID(BaseID):" + formID)
		;→「80831」が返される→10進数なので16進数に変換すると「13BBF」→頭に「000」をつけるとナゼームのベースID「00013BBF」になる
		;内部処理で Int型 を扱う場合には、10進数と16進数を変換する必要はない

		;ナゼームのフォームを取得し、名前を表示する
		Form FormNaze = Game.GetFormFromFile(0x00013BBF, "Skyrim.esm") 
		Debug.Trace("@@@:GetFormFromFile(Form):" + FormNaze.GetName())

   		;**なぜかコレでは動かない?**
   		;海外のスレッドでも同じ問題を見つけたが未解決?→https://tesalliance.org/forums/index.php?/topic/7970-sky-getformfromfile/
		;ナゼームのフォームをアクター型にキャストして取得する
		;Actor ActNaze = Game.GetFormFromFile(0x00013BBF, "Skyrim.esm") as Actor
		;Debug.Trace("@@@:GetFormFromFile(Actor):" + ActNaze)

PapyrusUtilを使ったアクターやオブジェクトの取得

■セルスキャン機能(Cell scanning functions)
◎ScanCellNPCs(アクターを取得する)
Actor[] function ScanCellNPCs(ObjectReference CenterOn, float radius = 0.0, Keyword HasKeyword = none, bool IgnoreDead = true) global native
・指定された CenterOn の現在のセルをスキャンして、指定された半径内のアクターを探し、指定されたすべてのアクターの配列を返します。
・現在生きており、デフォルトのなしから変更された場合は、(オプションで) 指定されたキーワードが含まれます。
・ 半径を 0.0 より大きく設定すると、CenterOn 付近からの検索距離、0.0 はオブジェクトが存在するセル全体を検索します。
・注: キーワード検索は少し予測不可能なため、結果を使用する前に、使用法が機能するかどうかを必ずテストしてください。

◎ScanCellNPCsByFaction(派閥でフィルターする)
Actor[] function ScanCellNPCsByFaction(Faction FindFaction, ObjectReference CenterOn, float radius = 0.0, int minRank = 0, int maxRank = 127, bool IgnoreDead = true) global native
・ScanCellNPCs() と同じですが、指定された派閥と (オプションで) その派閥内でのランクによって返値をフィルターします。

◎ScanCellObjects(全てのオブジェクトを取得する?)
ObjectReference[] function ScanCellObjects(int formType, ObjectReference CenterOn, float radius = 0.0, Keyword HasKeyword = none) global native
・指定された CenterOn の現在のセルをスキャンして、半径内の指定されたフォーム タイプ ID のオブジェクトを探し、そのすべての配列を返します。
・また、デフォルトのなしから変更された場合は、(オプションで) 指定されたキーワードも含まれます。
・半径を 0.0 より大きく設定すると、CenterOn 付近からの検索距離、0.0 はオブジェクトが存在するセル全体を検索します。
・注: キーワード検索は少し予測不可能なため、結果を使用する前に、使用法が機能するかどうかを必ずテストしてください。

プレイヤーにテスト用のスクリプトを取り付ける【OnPlayerLoadGame】

参考:逆引きリファレンス
https://w.atwiki.jp/skyrim_mod/pages/10.html#id_f4...

・ゲームロード度に動くスクリプト
・Oninit(ゲーム起動時呼ばれる初期化に関するイベント関数?)だと一回しか呼ばれない?
 →OnLoad()でロード時にも初期化したい場合上手くいかない?
 →OnPlayerLoadGameを使う(skyrim1.6以上必要?)
・OnPlayerLoadGameはActorのPlayerにしか返さないので、
 直接QuestにスクリプトをつけずにAlias(エイリアス)を経由させて使う

1.Object WindowのCharacterツリーのQuestを選択
 AchievementsQuestを右クリック->Duplicateでコピー


2.コピーしたクエストを開いてIDと名前をつける
 Priorityは99など高い数値にしておく
 Start Game Enabledにチェックが入ってるか確認する。


3.Scriptタブに移動し、スクリプトをすべてRemoveする。
 Addボタン->[New Script]でスクリプト新規作成する。

Scriptname MY_Outfit_Test_001 extends Quest

Event OnInit()
	; 自前の関数
	LoadFunc()
EndEvent

;実際の処理はここに書く
Function LoadFunc()
	debug.notification("Hello Work!")
EndFunction

4.「Quest Aliases」タブ->右クリック->「New Referense Alias」で新しくアイリアスを作成する
 Aliasは「player」
 Unique ActorからPlayerを選ぶ。



5.ScriptのところをAddボタン->[New Script]でスクリプト新規作成。
Scriptname MY_Outfit_Test_Ailias extends MY_Outfit_Test_001

MY_Outfit_Test_001 Property QuestScript Auto

Event OnPlayerLoadGame()
	QuestScript.LoadFunc()
EndEvent

6.Propertyで当クエストの指定する

TIPS

どのスクリプトがどのクエストに使われているか調べる

CKの「Papyrus Script Manager」では、Objectウィンドウのオブジェクト類と同じように、
スクリプトも右クリックの「Use Info」からリンクしている関連オブジェクトを調べることができる

xEdit・使っていないマスター指定を解除する


xEdit・そのフォームIDが使われるレコードを参照する


xEdit・エラーがないか確認する



スクリプト関連・一時メモ

参考となるスクリプト

◎SkyrimMOD作成wikiによるスクリプトの作成の流れ
https://w.atwiki.jp/skyrim_mod/pages/23.html

◎FAQ: スクリプトが機能しません!
https://www.creationkit.com/index.php?title=FAQ:_M...
◎ベセスダ チュートリアル パピルス Hello World
https://www.creationkit.com/index.php?title=Bethes...
◎スレッド化 (パピルス)
https://www.creationkit.com/index.php?title=Thread...
◎状態(パピルス)
https://www.creationkit.com/index.php?title=States...
◎MODを作る上での様々なTIPS
https://w.atwiki.jp/skyrim_mod/pages/18.html
◎逆引きリファレンス
https://w.atwiki.jp/skyrim_mod/pages/10.html

■周りとのインタラクト
◎参考
周りのActorを探して何かする
http://afternun.mydns.jp/blog/skyrimspecialedition...

【Papyrus.psc】周囲のNPCやフォロワーに対してスクリプトを配布する方法
https://skyrimfromnow.blogspot.com/2019/07/script-...

■プレイヤーのエリア移動を検出する(未調査)
参考:エリア移動を検出しよう・プレイヤー編
http://afternun.mydns.jp/blog/skyrimspecialedition...

◎想定
プレイヤーがエリア移動したタイミングで何かしたい時に、エリア移動を検出する。

Reference Aliasでループを回す

◎Actorがいるエリアのそれぞれを取得するコード
Debug.Trace("worldspace = " + PlayerRef.GetWorldSpace())
Debug.Trace("cell = " + PlayerRef.GetParentCell())
Debug.Trace("location = " + PlayerRef.GetCurrentLocation())

MCMの実装(未調査)

【MCM実装-2】MCMに登録するための基本構造を作る
https://ekagames.lifecycle.jp/creation-kit/mcm-bas...
 →MCM専用のクエストを作成する
【MCM実装-3】初期画面とページの枠組みを作る
https://ekagames.lifecycle.jp/creation-kit/mcm-pag...
 →MCMを開いたときに最初に表示される画面や、各ページの枠組み
【MCM実装-4】ページ内の項目を作る
https://ekagames.lifecycle.jp/creation-kit/mcm-opt...

SkyUI および SkyUI SDK のインストール(MCM用)(後で)
参考:【MCM実装-1】Creation Kit で Papyrus を扱えるようにする
https://ekagames.lifecycle.jp/creation-kit/mcm-cre...

◎SkyUI MCMのチュートリアル
https://w.atwiki.jp/skyrim_mod/pages/46.html

CKを使った簡単な参考例

◎各種制作例のリンク集
https://w.atwiki.jp/skyrim_mod/pages/72.html
◎スカイリムのキャラメイクに関する基礎知識のまとめ
https://w.atwiki.jp/skyrimmaking/pages/20.html
◎SKSEのよく使われる関数
https://w.atwiki.jp/skyrim_mod/pages/17.html

◎セルの修正
【Skyrim】CKを使った簡単なセルの競合修正実例
https://skmod.hatenablog.com/entry/kaizou/Breezeho...

◎セルの改造・アクティベート・スクリプト
【Skyrim】「すべての井戸から水を汲めるように」イマーシブ感の向上実験
https://skmod.hatenablog.com/entry/kaizou/Well-of-...

◎セル・シンボルの修正
【Skyrim】Extra Guardと都市改変MODの競合修正
https://skmod.hatenablog.com/entry/kaizou/ExtraGua...

◎セル系MODの改造
【Skyrim】Haven Bagのコンパクト化改造
https://skmod.hatenablog.com/entry/kaizou/HavenBag
■スクリプト付きのMOD装備をちょこっと改造する
参考:Skyrimのスクリプト付き装備MODの簡単な改造例:https://skmod.hatenablog.com/entry/kaizou/RingOfSu...

・ソースファイルがMODと一緒に配布されているか?
 →Scripts/sourceフォルダの中を見る
・改造前にTES5Editで事前調査し、MODの構成を把握する
・CKで改造&ビルドする

検証用モブ敵NPCの作成


デバッグ・開発の効率化・最適化

(ConsoleUtilは指定した文字列をコンソールに出力する関数があるため、デバッグ等でも便利です)

■アルゴリズムを組む前にSKSEプラグインを確認する
アルゴリズムを組む前に、wikiメニュー一覧の「SKSEプラグイン」に記載されてる「主なPapyrus関数拡張SKSE」のSKSEをダウンロードし、
それのソースを確認して実現したい処理がそのSKSEが提供しているPapyrus関数一つで行えるかを確認しましょう。

・アクターやオブジェクトの状態の操作や確認、アクターやフォームの一覧を取得したい場合
 →"powerofthree's Papyrus Extender"、
・AIパッケージ上書きやファイル関連の操作
 →"PapyrusUtil"を使用する事で大抵の事はできます。
まずはこの2つだけでもダウンロードしてどのような処理が行えるか把握しておくと良いでしょう。

■デバッグのためエフェクトや状態をコンソールから確認できる環境を整える
SE版はMore Informative Consoleを導入すれば、コンソール画面で対象を選ぶだけで対象の状態の確認が行えるようになります。
アビリティは付与されたけどマジックエフェクトがアクティブになってない等の確認も簡単に行えるようになるのでデバッグが非常に容易になります。

■Papyrusに慣れたらSkyrim Platformでのスクリプト開発を検討する

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

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()ですね。

■Is3Dloaded()
3Dデータとして読み込まれているかどうかの判定をする関数で、has no 3d〜のエラー対策に使えます。
インベントリに回収しちゃって処理ができない場合や、ラグがあって3Dオブジェクトが設置される前にスクリプトが稼働した場合にhas no 3dのエラーがでます。

引数が多い関数やイベントほど重い
パピルスの仕様で、引数から変換するときの処理が重いのです。したがって引数が多いほど重いので
OnHitイベントやFind〜()は重いです(OnHitは頻発するのとFindはそもそも探索が遅いのあります)。
別のイベントや関数で代替できるならそちらでしたほうが良い場合もあります。

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

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

Menu

【メニュー編集】

スカイリム関連

スカイリム関連


Menu

【メニュー編集】

スクリプト関連

フリーエリア

管理人/副管理人のみ編集できます