「Papyrus入門」の編集履歴(バックアップ)一覧に戻る

Papyrus入門 - (2015/09/18 (金) 04:05:18) の編集履歴(バックアップ)



概要

スクリプト作成の流れや構造をざっくり掴みます。
流れをつかみやすくするために平たく説明するので厳密に言うと違うこともあります。
実践編はCK WikiのPapyrusチュートリアルをおすすめします。

スクリプトに対する誤解

エラーや競合を気にして、スクリプトを避けてコンディション(条件設定)やパッケージ(AIの振る舞いを決めるもの)で似たようなことが出来ますが、負荷はほぼ変わりません。
エラーに関しては、書き方が悪い場合や、やり方に問題がある、またはスクリプトで処理をするのが最適ではない場合です。アンインストールも書き方次第です。問題ありません。

競合に関してはむしろ積極的にスクリプトを使ったほうが柔軟な設計ができるでしょう。
スクリプトを多用するmodは概して改変範囲が広いので競合しやすいように思えますが、だからといってスクリプトと競合しやすいは結びつきません。

スクリプトを用いるメリット

できることが増えます。
既存のスクリプトを流用したり、改変するだけでオリジナルのトラップを作ったり、かっこいいエフェクトを動的につけたりもできます。
デバック用のスクリプトを組んで戦闘データを取って自分のフォロワーの強さを調整したり、別のmodと機能が被ってる場合、競合回避のためにどちらかをオフにしたり。

Papyrusって何?

スカイリム専用のイベント駆動型オブジェクト指向スクリプト言語です。
いきなり難しい用語が出てきましたが、大事なことなのでざっくりと概念を掴みましょう。

イベント駆動とは?

イベント駆動型というのはイベントが発生する時にしかスクリプトが動きません。イベントを起点として動くということです。
イベントとは例えば座る時(OnSit)、攻撃を受ける時(OnHit)、死ぬ時(OnDeath)、ロード(OnLoad)時などに発生します。
イベント一覧

ゲームは基本的にイベント駆動型です。なぜイベント駆動型なんでしょうか?
ゲーム画面には大量のオブジェクトが設置してあり、その一つ一つに対しての状態を0.1秒毎に監視して動くようにしたらどんなにハイスペックでもまず重くなってしまって動きません。
それに何が・どこで・どう動いているのかがわかりにくくなり複雑すぎます。
これをイベント駆動にすると見た・触った・動いた等のタイミングでスクリプトが動くだけなので、構造がずっとシンプルで動作は軽いです。
ゲームは大体、スイッチを踏んだらトラップが稼働するなど、何かのアクション(イベント)に対して反応という仕組みでできています。
なのでゲームにはイベント駆動型が向いています。

オブジェクト指向

オブジェクト指向というのはオブジェクトを主体として考える手法です。
あなたが操作するプレイヤーはオブジェクトです。あなたが持っている武器もオブジェクトですし、NPCもオブジェクト、天気が晴れているならその晴れの状態もオブジェクトです。
見たまんまの実体としてオブジェクトで画面が構成されているからわかりやすいですね。
だからゲームには非常に相性が良いです。
でも、オブジェクト自体はなんかしらの役割を持つものとして覚えておいてください。必ずしもすべてがゲーム画面で実体を持っているわけではないです。

CK上のオブジェクト=フォーム(Form)です。そしてそのFormの種類がFormType。Formが被らないように管理番号を与えられているのがFormID。
実際に見たほうが早そうですね。
それではCKでSkyrim.esmを開いて、Object Windowを見てみます。



左側のツリーに入ってるものはすべてオブジェクト(フォーム)です。
右側見ますとPlayerが一つのフォームでFormIDは00000007、FormTypeがNPC_。
UserっていうのはこのPlayerを参照にしているオブジェクトです。
Countは実際のゲーム世界(Cell)に設置してある数です。このUserとCountが実際に何に参照されていてどこに設置してあるかは対象を右クリックしてUseInfoで見れます。
オブジェクト同士が相互に作用しあってゲームが成り立っているのでこのUseInfoは何と何がつながっているのか手がかりになるので極めて重要です。

もう一つ事例を見てみましょう。


BasicTankard01を開いた状態にしてます。ただのジョッキです。
名称(Name)、Weight(インベントリでの重さ)、Value(価格)が設定されてますね。
画像にはないですがモデルデータの指定もここです。

これ、ゲーム画面での実体のあるモノではないんです。
ただの設定だけのオブジェクトですね。これをベースオブジェクトと言います。

実際にゲーム画面に出てくるジョッキはセル内に設置してあります。
これがリファレンスオブジェクトです。
画像準備中。

なぜ、ベースとリファレンスで分けるのでしょうか?ややこしいですよね。
ではジョッキ一個の値段を10にしましょうか。
ベースとリファレンスを分けずに、リファレンス単体が価格の設定を持っていると仮定したら、セル上にある4179個設置されているのを一つ一つ価格を直さないといけません。途方も無いですよね。
これを回避するために設定=ベースと設置=リファレンスの役割を分けるんです。
役割を分けた上でリファレンスはベースの設定を持っています。(包含関係。リファレンス変更してもベースは変わらない。)

この役割でオブジェクトを分けるというのがオブジェクト指向の肝だと思います。

魔法なら:威力の強さ、持続時間、魔法名を持つSpellと、効果の種類、耐性、エフェクトやサウンドなどを設定するMagicEffectで役割を分けてます。
鎧なら:防具の性能や種類を決めるArmorと、モデルデータと装備箇所と適用する種族のArmorAddonに分かれています。こうやって分けてあるから種族別で革の兜のモデルデータを変えたりできます。

要はテンプレ化(ひな形を作る)です。
基本となるテンプレ作っちゃえば後は組み合わせであとは無数のバリエーション作れます。
キャラクター例:
名前 種族 戦闘AI 装備 スケジュール
山賊長 ノルド ボスクラス 重装ボスセット 一日鍛冶してるスケジュール
山賊 ブレトン 魔法使い 魔法使いセット ダンジョン内巡回
市民A インペリアル 非戦闘 服セット 畑仕事

Papyrusを言語として覚える

言語なので文法(構文)があります。つまり、ルールですね。

プレイヤーを取得する。をPapyrusに翻訳すると。
Game.GetPlayer()

後ろから分解してみて、GetPlayer()関数です。
英語でFunction、直訳すれば機能です。
関数の語尾には必ず()がつきます。これ付いてるものは人も機械も関数だってわかります。
GetPlayer()はそのまんま、プレイヤーを取得する機能(関数)です。

関数は自分で作ることもできますが、ゲーム側でまとまった関数リスト(以下ライブラリ)を作ってます。
このリストの中の一つがGameで、このGameはゲーム全般に関わるライブラリです。
実際のこのリストの場所はData\Scripts\Source\Game.pscです。

ここからGetPlayer()を引っ張ってくるために先頭にGameを付けます。
つまりGameというライブラリの中からGetPlayer()を呼び出しただけなんです。
.は単に区切りです。、みたいなものです。

関数とライブラリは全部は把握できないので[[CK wikiのパピルスリファレンス>http://www.creationkit.com/Category:Papyrus
]]を見ながら、どんな関数や関数リストがあったけなーって何ができるかなーって探して使います。
たいてい使用例が書いてあるのでコピペで使えます。

※Tips SKSEの基本的な機能はこの関数とイベントのリスト(ライブラリ)を大幅に拡張するものです。

次はアクター(キャラクター)の体力を取得したいと思います。
Actor.GetActorValue("Health")

ActorのライブラリからGetActorValue()という関数を呼んでます。
さて、関数のカッコ内に"Health"とありますがこれを引数(ひきすう)と呼びます。
英語でParameter(argumentとも)、今じゃ英語のほうがわかりやすい気がする、パラメータのことです。
GetActorValueはアクターに設定されている数値、体力、スタミナ、マジカ、錬金術のスキル値などを取得できます。※取得できるActorValue一覧
アクターの何の数値を対象にするのか指定しないと、ですね。ここでは"Health"です。対象がスタミナなら"Stamina"を指定します。

実は上のコードでは動きません。対象が必要なんです。
一体誰のアクターの値を取得するんだ、とコンピュータにはわかりません。

対象がプレイヤーの場合は
Game.GetPlayer().GetActorValue("Health")

実はActorの部分、ライブラリだけではなくての役割を持ってます。
このGetActorValueの関数はActorの型にしか使えません。壷などがスタミナの値持ってませんしね。
型はデータの種類だと思っていいです。関数の使用できる範囲を区切る役割が型にはあります。

Game.GetPlayer()で取得したプレイヤーのデータはActorという型に入れます。
これで対象のアクターをプレイヤーにすることができます。

今は型の概念を理解するのは難しいかもしれないですが、
一つの構文パターン(SVとかSVOとか)だと思ったら全然構造は難しくないです。

対象があるパターンの構文

~の.~を~する()
対象.実行()
型.関数(引数)

上全部意味は同じ。

Actor、ObjectReference、Form、Formlistなど

対象の指定がないパターンの構文

ライブラリ.~を~する()

例:
Game.GetPlayer() プレイヤーをアクター型として取得する。
Utility.Wait(0.5) これの書かれた部分でこのスクリプトの処理を0.5秒待つ。
Debug.notification("Hello world.") 左上にHello worldと通知を出す。
Math.abs(-1.0) 引数の数値を絶対値として返す。つまり結果は1.0。

Utility、Debug、Math、Gameなど。

変数、宣言、型

変数(variable)はデータを一時的に記録したり、そのデータを代入したりできます。

変数には型と名前をつけます。この型と名前を明確に定義することを宣言といいます。
型というのは格納するデータの種類です。
基本形が4つあるのでそれをまず覚えましょう。

基本の4型
int 整数の型
float 小数点も扱える数字型(浮動小数点数型)
bool true(真)かfalse(偽)かで返す型
string 文字列の型。""で囲う必要がある

名前は自由につけられますが、接頭辞に数字と記号(例外はアンダーバー→_)はダメです。
※ダメな理由を見たことないですが、文字のはじめが数字なら数字という時代の名残りのようです。
☓0IsWalking
☓-IsWalking
○IsWalking
○_IsWalking


型 名前
float PlayerHealth

このように記述することで、float型のPlayerHealthという名前の変数が作れます。
この変数に入ってる数値はデフォルトだと0.0です。
この数値ははじめから代入しておくことができます。

float PlayerHealth = 1.5

変数の基本は数字や文字列なんですが、以下も同じく変数です。
Actor player
playerの名前のActorの型です。


プロパティ

プロパティは変数に似てますが、少し違います。


if



while

条件がFalse(偽=不一致)になるまでループします。
そのとおりに繰り返しの処理をする場合に使ったり、特定の条件を待機だとかにも使います。
条件が満たされない場合のタイムアウトのために(でないと永遠と回り続けてしまう)、処理の手前でカウントの変数を用意して、カウントまで達したら抜けるようにしておいたほうがいいです。
例:
int count = 0
While self.Is3DLoaded == False &bold(){&&} count < 10
	Utility.wait(1.0)
EndWhile

イベント


配列

Papyrusで難解なものの一つが配列なんですが、使いこなせれば強力です。
Papyrus上で基本となるのは一次元の配列で、これは平たく言って変数の集合リストだと思ってください(厳密にはリストではない)。

変数は一時的にデータを記録したり、代入したりするものですから、例えたらと言えます。
この箱が連なってるのが配列です。箱には番号が振り当てられます。番地みたいなもんです。
その番号がインデックス(添え字)です。

上の画像をpapyrusで書くと
int[] a = new int[4]
a[0] = 12
です。

分解していきます。
intは整数型の指定ですが、配列の時は通常時と区別するため[]を付けます。
aは変数名です。ここまでは普通の変数の宣言とあまり変わりません。
newは新しく配列の長さをセットします。
[x]は配列の長さです。例のように[4]なら0,1,2,3の4つの箱が作られます。
これが[2]なら0,1ですし、[5]なら0,1,2,3,4です。

インデックスは0から始まるのが、ややこしく間違いやすい点です。([5]の長さで設定したなら[4]で終わる。一個ずれる)


最初の行は配列の変数を宣言、そして配列の長さを新しく定義しました。

あとの行では箱の中身に数値を入れます。
例ではa[0]の箱に12を代入してます。
a[1] = 13
a[2] = 15
a[3] = 7
みたいに箱別に代入できます。
debug.notification(""+ a[0]) で表示されるのは12です。

例2:
string[] myArray = new string[5]
myArray[0] = "Hello"
myArray[1] = "World"
myArray[2] = "Hello"
myArray[3] = "World"
myArray[4] = "Again"

Event OnInit()
int i = 0
While i <= 5
	debug.notification("" + myArray[i])
	i += 1
EndWhile
EndEvent

で順番にHello,World,Hello,World,Againと左上に表示されていきます。
宣言の部分は前と同じです。
iというカウント用の変数作って0に設定します。
Whileでループして5以上になったら止めます。
myArrayの変数にiを代入します。

例3:
Actor[] Property DeadActorList Auto
プロパティにも配列使えます。プロパティのウィンドウで複数のプロパティを指定できます。


活用すると、まとめて変数を扱えるため冗長なコードになりにくくなり、またインデックスは自然数ですから足したり引いたりできて扱いやすいです。
(かなり応用例があるのですがそれはまた今度)

実例としてはCK wikiのComplete Example Scripts(テキスト検索で[]で調べる)
http://www.creationkit.com/Complete_Example_Scripts

またこちらの配列の解説も読んでおきましょう。
http://www.creationkit.com/Arrays_%28Papyrus%29/ja



目安箱バナー