スクリプトmod製作

スクリプトmod製作過程

スクリプトを使ったmodの製作過程を一から纏めてみました。
トライ&エラーで修正しつつmodの作成を目指します。

言語としてのpapyrusの説明は最小限に留めています。
ifとwhileしかない言語なのでそれ自体はそんなに難しくはないですので。



どんなmodを作るか考える。

おぼろげに"こんなモノ"が欲しいと考えるのは簡単なのですが、その"こんなモノ"を形にするのはとても大変です。
今回は、かの有名な国産RPGの一つのアビリティからアイデアを頂戴して"ぶんどる"を作ることにします。
実際にskyrimで作れるのかどうかが心配です…


仕組みを考える


ここにある関数を使って作っていきますが、ある意味で一番大変であり重要な所です。
"こんなモノ"をどのようにすればskyrimのmodとして形にできるのでしょう?

とりあえず"ぶんどる"を作るには、どのような処理をするのかを考えます。
プレイヤーの「攻撃対象を取得」して「攻撃を当てた時」に、対象の「手持ちアイテム」を「盗む」
なんとなくコレで出来そうです。

では、上記のURLでそれっぽい物を探します。
※適当に検索等で探しています

攻撃対象を取得 → GetCombatTarget
攻撃を当てた時 → OnHit
手持ちアイテム → GetNumItems
盗む → RemoveItem
探した結果、出来そうな物が揃っていましたので難しいことをしないでも可能なようです。
早速、作り始めることにします。

今回のようにスクリプトで何が出来て何が出来ないのかを
事前に判断できれば良いのですが、始めての人には難しいと思います。
作りたいものが出来たら兎に角作ってみましょう。


CKの作業1

Skyrim.esmの読み込み

まずはCKを起動します。
CKウィンドウの左上にあるFile→Dataと選んだあとskyrim.esmをダブルクリックしてチェックを入れてOKボタンを押します。
エラー表示は[キャンセル]を押してください。
これでskyrimの世界を読み込んだ状態になりました。

これから変更を加えた場合、skyrimの"何か"を変更することになりますが
マスターファイル(esm)自体は追加・編集をする事が出来ませんので安心してください。
保存をする場合は"何か"の部分を上書きするプラグイン(esp)を作ることになります。


オブジェクト(Object)

攻撃対象を取得するのにGetCombatTargetを使うことにしていますが
スクリプトは何らかのオブジェクトにくっつけて動作する必要があります。

例えば、ゲーム開始時に自動で"Hello World"と文字を出そうとした場合は
「スクリプトをquestに付けて、そのquestをスタートさせた時に文字を出す」
「プレイヤーにスクリプトを付けて、ゲームに読み込まれた時に文字を出す」
といった形になります。本であれ魔法であれクエストであれ
とにかく何かに付ける事で動かす事が出来ると覚えてください。
今回はプレイヤーの攻撃対象を取得するのですから"Player"に付ける事にします。


{※この説明ではスクリプトを直接Playerに付けていますが、この方法は現在はほぼ使われていません。
 新規QuestのReferenceAliasでPlayerを指定。スクリプトをAliasに張り付ける手法が一般的です。
}

画面左にある"object window"の上のあるFilterに"player"と入れて
マウスでActorを選択すれば"Player"を発見できるはずです。


ダブルクリップで開き、Actor window左側にあるScriptsの"Add"を押します。


出てきたscript windowの上段の"[New File]"を押せば
新しいスクリプトをくっ付ける事が出来ます。


スクリプトの名前を決めるwindowが出てきました。


今回は"aaaBundoriTarget"とでもしておきます。
他はとりあえずそのままでおkです。
aaa等を付けることでfilterが使いやすくなります。

スクリプトはオブジェクトに付いたので一度保存しておきます。
Actor windowの左下の[OK]を押して、閉じてください。
保存をするためには、開いたwindowは閉じて置かなければいけません。
CKのメニューバーFile→Saveと選んで"BundoriTutorial"と名前を入力しました。

先ほど閉じた"Player"を再度開きます。
"aaaBundoriTarget"がScriptとして追加されていますが、"aaaBundoriTarget"の所で右クリックするとメニューが出てくるので、"Edit Source"を選びます。
※その下の"Open in External Editor"を選ぶとpscファイルに関連付けされたテキストエディタで編集することができます。


スクリプト(Script)1

今回、私が考えているスクリプトはこのような感じです。
1 繰り返し処理をする
2 プレイヤーの攻撃対象を取得する
3 攻撃対象に攻撃を与えたか確認する
4 確認できたら何を持っているか確認する
5 ランダムで選んだアイテムをプレイヤーに移動する
6 1にもどる
では順番にやっていきましょう。


繰り返し処理をする

繰り返し処理に関してはこのwikiの逆引きリファレンスの中にある常時稼動させるスクリプトを参考にします。
実際の処理の部分を置き換えればそのまま使えそうです。


攻撃対象の取得

ck wikiのGetCombatTargetのページを開きます。
細かい仕様は後に回しましょう。用があるのはExamplesです。


素晴らしいことに必要であるプレイヤーの攻撃対象を取得するサンプルがありました。
これをそのまま使います。
先ほどの繰り返し処理と併せてこのような感じになっています。


ここで一度スクリプトを保存します。Edit windowのFile→Save(ctrl+S)です。
これで攻撃対象の取得を一秒ごとに繰り返すはず…

ところが…なにやらエラーらしきものが出てしまいました。
[いいえ]を押して修正をします。


下段に理由がかいてありますが、8行目のTargetRefが定義されていないのが問題のようです。
c:\...\aaaBundoriTarget.psc(8,1): variable TargetRef is undefined
c:\...\aaaBundoriTarget.psc(8,1): type mismatch while assigning to a none (cast missing or types unrelated)
8行目という事はGetCombatTargetの部分なのでGetCombatTargetのページに戻ります。。
答えはSyntaxにありました。


赤丸がついた所、Functionの前にActorと書いてあります。
これはGetCombatTargetがActor型で返すと言うことなので、8行目のTargetRefはActor型でなければいけません。
TargetRefの頭にActorと付けて定義することで無事に成功しました。



Onhitを使う1

先ほどと同じようにOnHitのページを開きます。
とりあえずはExamplesからなのですが…先ほどとは少し違います。
どうやら、OnHitはFunctionではなく"Event"のようです…



イベント(Event)

"Event"はスクリプトを動かす為のトリガーです。
本を読むとOnReadが、扉を開くとOnOpenのイベントが起こります。
ですが重要な事は、その動作を受け取ったオブジェクトに対してイベントが起こる事です。
上記の例の場合、OnReadのイベントが起こるのは本のオブジェクトであり、本を読んだ人には起こりません。
極稀な一部のイベントを除いて、ほぼ全てのイベントは受動の為、慣れるまではイライラすると思います。
今回の場合、OnHitは攻撃対象(TargetRef)に起こり、Playerには起こりません。


再検討

当初の予定のままでは駄目なようなので予定を変更することにしました。
現在作った部分はそのままに再検討しています。
その時に書いたメモはこんな感じです
手書きのものを画像で作りなおしてはいますが、だいたいこのような感じです。
修正案では先ほどの物とは別のスクリプトを用意しようとしています。

攻撃対象は特定されていない今回の場合はどのようにすれば良いのでしょう?
今回は攻撃対象にスクリプトを動的にくっつける為に"Spell"を使う事にします。


スペル(Spell)

スペルは様々な"Magic Effect"を組み合わせたもので、アビリティ・病気・パワー・呪文・スクロール・シャウト等全てスペルの一つです。
明かりの魔法のように "Spell - MagicEffect" が一対になっているものもあれば、術者のレベルで使用するMagicEffectを変えるもの、雷の魔法のようにダメージ+麻痺にする物もあります。
とりあえず、スペルにはMagic Effectが必要だと覚えてください。

SpellやMagic Effectの細かい部分はCK wikiのここが一番詳しいです。
英語分からん!な人は ck wiki 日本語ヘルプファイルをみてください。

CKの作業2

マジックエフェクト(Magic Effect)の作成

まずはぶんどりスペルのマジックエフェクト部分から作ります。
最初のうちは新規に作るよりも間違いが少なく良いと思います


既存のエフェクトを複製

複製と言っても適当な物を選んでしまっていいのでしょうか?
今回は"abGhost"を事前に選んでおきました。
理由として
  • アビリティである。
  • ビジュアルエフェクトがついている。
  • このエフェクトを単独で使っているスペルがある。
があります。


フィルターに "abg" と入力すれば"abGhost"が見つかるので開いてください。
開いたら左上にあるIDとNameをそれぞれ"aaaBundoriOnhit","Bundori Onhit"に変更します。
次に画面左側のFlagsの中の"No Hit Effect"にチェックを入れてください。
これにチェックを入れるとvisual effectを無視します。
逆に効果が出ているか確認したい時はチェックを外します。


また、右下の"Papyrus Scrips"にある"magicsetActorAlphaScript"は不要なので削除しておきました。
選択して右側のRemoveボタンを押してください。
代わりに2個目の自作スクリプト"aaaBundoriOnHit"を新規作成して付けておきます。
とりあえず今は付けておくだけです。


最後に下の方にある[OK]を押します。
勝手にIDを変更した為に確認のダイアログが出てきますが、既存エフェクトを元にして新規作成したいので[OK]を押します。
これで"abGhost"を元にしたマジックエフェクト"aaaBundoriOnhit"を作りました。


既存のスペルを複製

次はスペルなのですが既存スペルに"abGhostを使っている物があります。
せっかくなのでそちらのスペルも使わせていただきました。
abGhostを右クリックして一番下 "Use Info" を開きます。
このリポートは今後よく使う事になると思います。とても便利。
※ここから使用されている物・所が一覧で参照できる

このリポートからも直接スペルを開く事が出来ます。
前回と同じようにID,Nameを変えた後、今回は右側のエフェクト"Ghost Visuals"も変更しておきます。
右クリックしてEditを選んでください。ダブルクリックでも良いです。


"Ghost Visuals"は"abGhost"の事なので、今回作った"Bundori Onhit"に変更しておきます。
ID順に並んでいるのでaaaを付けている事で一番上にありました。
後はそのまま変更しないで問題ありません。


現状確認

予定通りにスペルをつくりました。
これで後はスクリプトの完成を目指すだけになりました。

Category ID Script
Actor Player aaaBundoriTarget
Magic Effect  aaaBundoriOnhit  aaaBundoriOnhit
Spell aaaBundoriAbility

ところで別々のオブジェクトにスクリプトが付いた事でCKでの作業がしづらくなりました。
毎回、フィルターを切り替えても良いのですがもっと簡単に出来る方法があります。
object windowの右側、一覧部分の適当な部分で右クリックをして出てくるメニューから"Create New Object Window"を選ぶと
新しいObject windowが出てきます。
これを使えばPlayerとaaaの2つのフィルターを同時に使う事が出来るので非常に便利です。
※こんな感じで同時に出しておくことが出来る


スクリプト(Script)2

スペルをプレゼントする

現状確認の項で見た通りPlayerについている"aaaBundoriTarget"は現状対象の取得だけ行なっています。
これに先ほど作ったスペルをプレゼントする部分を追加します。
GetCombatTargetの行の後に次のように加えました。

	Actor TargetRef = Game.GetPlayer().GetCombatTarget()
	if TargetRef != none
		TargetRef.AddSpell(aaaBundoriAbility)
	endif

TargetRefが空じゃない場合、先ほど作った"aaaBundoriAbility"をTargetRefに与えると言うもの。

ですが、これを保存しようとするとやっぱりエラーが出ました。
aaaBundoriAbilityの未定義エラー…スクリプトにはCKにあるIDを直接書いても駄目なようです。

存在するスペルをスクリプト内で使う為には"Property"を使って指定してあげる必要があります。
今回は[はい]を選び、スクリプトはその状態のまま保存して"Property"の定義を行います。


プロパティ(Property)

プロパティは外部(主にCK)からオブジェクトや変数を指定したい場合に使います。
面倒くさいように感じますがプロパティがある事で汎用性のあるスクリプト作成が可能になります。


"Player"のScriptsから"Properties"を選びます。

Add Propartyを選び、更にtypeを指定(今回はSpell)。
今回は汎用スクリプトではありませんので"aaaBundoriAbility"としておきます。
IDと同じにすると自動的で割り当ててくれますが、ちゃんと指定されていることも確認します。



これで"aaaBundoriAbility"の定義が出来ました。
もう一度"Edit Source"を開き保存をしてください。
今度はエラーも無く成功しました。

aaaBundoriTargetスクリプト完成

一つ目のスクリプトが完成しました。
この状態でCKの保存をしましょう。

Scriptname aaaBundoriTarget extends ObjectReference  

Event Oninit()
	RegisterForSingleUpdate(1)
EndEvent

Event OnUpdate()
	Actor TargetRef = Game.GetPlayer().GetCombatTarget()
	if TargetRef != none
		TargetRef.AddSpell(aaaBundoriAbility)
	endif
	RegisterForSingleUpdate(1)
EndEvent

SPELL Property aaaBundoriAbility  Auto  


スクリプト(Script)3

最後にもう一つのスクリプトを作りました。
こちらのスクリプトに関しては作成済みのものを使い、簡単にを説明していきます。

Scriptname aaaBundoriOnhit extends ActiveMagicEffect  

Event OnHit(ObjectReference akAggressor,Form akSource, Projectile akProjectile, bool abPowerAttack, bool abSneakAttack, bool abBashAttack, bool abHitBlocked)
	if akAggressor != Game.Getplayer()
		return
	elseif akSource as Spell
		return
	elseif akProjectile != none
		return
	endif

	Actor targetRef = GetTargetActor()
	int index = targetRef.GetNumItems()
	form akForm
	
	if index > 1
		int random = Utility.RandomInt(1, index)
		akForm = targetRef.GetNthForm(random)
	elseif index == 1
		akForm = targetRef.GetNthForm(index)
	endif

	if akForm != none
		targetRef.RemoveItem(akForm,1,false,Game.GetPlayer())
	endif
EndEvent

イベント

Event OnHit(ObjectReference akAggressor,Form akSource, Projectile akProjectile, bool abPowerAttack, bool abSneakAttack, bool abBashAttack, bool abHitBlocked)


ぶんどり発動条件

	if akAggressor != Game.Getplayer()
		return
	elseif akSource as Spell
		return
	elseif akProjectile != none
		return
	endif
違った場合Returnを返して処理を終了させています。
上から順に
  • プレイヤーの攻撃ではない場合
  • 魔法で攻撃をしている場合
  • 飛び道具で攻撃している場合
となります。
現在はプレイヤーの攻撃にしか反応しないようになっています。
modを拡張しようとした場合、条件の緩和や追加なども考えられます。


定義

	Actor targetRef = GetTargetActor()
	int index = targetRef.GetNumItems()
	form akForm
このイベント内で使用する変数を用意しています。
一行目、GetTargetActor()は本来 "MagicEffectProperty.GetTargetActor()"と言うようにしなければいけませんが、省略した場合予約語selfが指定されたことになります。
この場合のSelfはMagic Effectの"aaaBundoriOhHit"を指していますが、この事はスクリプトの一行目にあるScriptnameのextendsで何を拡張するスクリプトなのか宣言されています。
Scriptname aaaBundoriOnhit extends ActiveMagicEffect  
もちろん self.GetTargetActor()と言うようにしても構いません。
二行目、GetNumItems()はSKSEを使った拡張関数でコンテナ(宝箱)のアイテム種類の数を返します。
その為、ぶんどりmodはSKSEが必須となっています。


アイテムの選定

	if index > 1
		int random = Utility.RandomInt(1, index)
		akForm = targetRef.GetNthForm(random)
	elseif index == 1
		akForm = targetRef.GetNthForm(index)
	endif
アイテムの種類数が二個以上あればランダムで、一個ならそれをakFormにしています。
現在akFormは全てのアイテムの内の何かと同じオブジェクトを指していることになります。
同じものですがそのもの自体ではありません。


ぶんどる

	if akForm != none
		targetRef.RemoveItem(akForm,1,false,Game.GetPlayer())
	endif
最後にプレイヤーに向けてアイテムを移動します。


完成?!

ついに完成?!しました。早速動かしてみましょう。
なんだかアレなmodが出来た気もします。しかし夢が広がりそうでもあります。
とりあえずは脱が…ぶんどる事が出来ましたので良しとしましょう。

出来た物はここを置いておきます。


不満点

ここからはこのmodの問題点・不満な所をあげて、改善案が思いつくのなら考えていきたいと思います。

服を着てしまうぞ!フザケンな。

modとしての動作は問題ないのですがスカイリムの仕様のために、完全に装備を外してしまうと何処からか鎧・服を持ちだしてきて着てしまいます。
靴は対象外にする等の処理を加えるか、鎧を取った後にぶんどれない透明な服を着せるとか少し考えなければいけません。
でもやっぱ足元だけ残ってるのがロマンだと思う。

ぶんどり100%ないわー

現在は攻撃が当たったらすぐにアイテムを剥ぎに行っていますがFFの「ぶんどる」はランダムです。
元々の確率を低くして、敵の体力に比例して確率が上がっていくような処理を加えても良いかも知れません。

もうちょっと演出をなんとかしてくれ

いきなり取ってしまうのでもう少しなにかあっても良いですね。
ぶんどったものはその場に散乱するとかあっても良いのかな?
…今考えたら過剰演出かも知れません。

不意打ちでぶんどる出来ない

対象のreferece取得が簡単で分かりやすいのでGetTargetActorを使いましたが、本当はスペルをプレゼントする相手は攻撃対象じゃなくても構わないです。
攻撃前にスペルをプレゼントする仕組みを考えれば可能です。
少し冗長になるので今回は簡単な方を選びました。

更新はないの?

ありません。権利等全てフリーなmodです。
更新したいと思った人が引き継いで結構です。
出来たら更新した部分と手法をwikiに反映して欲しいです