CK Wikiに書かれていない関数がある

Modを作ろう

Papyrusでコードを書く時、真っ先に参考にするのがCK Wikiだと思います。 ところが、ここに書かれていない関数が実はあります。例えばここにSKSEのUtility.pscというファイルがあります。このソースの下の […]

Papyrusでコードを書く時、真っ先に参考にするのがCK Wikiだと思います。

ところが、ここに書かれていない関数が実はあります。例えばここにSKSEのUtility.pscというファイルがあります。このソースの下のほうにあるCreate*Array関数、Resize*Array関数です。

Papyrusの配列は要素数を定数でしか決められないので、要素数は決め打ち、ゲーム内で動的に決めることもサイズを変更することもできません。Create*Array関数なら動的に要素数を決められますし、Resize*Array関数を使えば一度作成した配列を小さくしたりできます。

アクターの装備している防具の一覧を取得する処理を考えてみます。CK Wikiのサンプルソースコードを参考に関数を作ってみます。

337 (papyrus)

Form[] Function GetWornArmors(Actor akTarget)
    Form[] kArmors = new Form[32]
    Int iIndex
    Int iSlotsChecked
    Int iThisSlot = 0x01

    iSlotsChecked += 0x00100000 ; チェックを省くスロット
    iSlotsChecked += 0x00200000
    iSlotsChecked += 0x80000000

    while iThisSlot < 0x80000000
        if Math.LogicalAnd(iSlotsChecked, iThisSlot) != iThisSlot
            Form kThisArmor = akTarget.GetWornForm(iThisSlot)

            if kThisArmor
                kArmors[iIndex] = kThisArmor
                iIndex += 1
                iSlotsChecked += (kThisArmor as Armor).GetSlotMask()
            else
                iSlotsChecked += iThisSlot
            endif
        endif

        iThisSlot *= 2
    endWhile

    return kArmors
EndFunction

この関数はActorが装備している防具の一覧をFormの配列で返します。要素数は32個固定で、先頭の0から順に防具がFormの形で格納されています。

340 (papyrus)

Form[] kArmors = GetWornArmors(Game.GetPlayer()) ; プレイヤーの装備一覧を取得する
Int iCount = kArmors.Length ; 常に32を返す

さて、防具の一覧からランダムでひとつ選び、何かしようとしたとき、きれいに処理する方法がありません。ループを最大32回もまわしていく必要があります。

342 (papyrus)

Form[] kArmors = GetWornArmors(Game.GetPlayer())
Int iIndex = Utility.RandomInt(0, 31) ; ループの開始位置をランダムに決定
Int iMaxIndex = kArmors.Length ; ループの最大回数は配列の個数
Int iCount = 0

while iCount = iMaxIndex ; 配列の最後に達したら先頭に戻す
            iIndex = 0
        endif
    endif
endWhile

結構無駄がありますね。配列の中身がどうなっているかは、実際に覗いてみないとわからないので、全部見ていく必要があるのです。もしこの処理が、その場にいるアクター全員が攻撃を受けるたびに動いていたら、相当の無駄が生じることになります。そこで、Resize*Array関数を使って改良してみます。

344 (papyrus)

Form[] Function GetWornArmors(Actor akTarget)
    Form[] kArmors = new Form[32]
    Int iIndex
    Int iSlotsChecked
    Int iThisSlot = 0x01

    iSlotsChecked += 0x00100000 ; チェックを省くスロット
    iSlotsChecked += 0x00200000
    iSlotsChecked += 0x80000000

    while iThisSlot < 0x80000000
        if Math.LogicalAnd(iSlotsChecked, iThisSlot) != iThisSlot
            Form kThisArmor = akTarget.GetWornForm(iThisSlot)

            if kThisArmor
                kArmors[iIndex] = kThisArmor
                iIndex += 1
                iSlotsChecked += (kThisArmor as Armor).GetSlotMask()
            else
                iSlotsChecked += iThisSlot
            endif
        endif

        iThisSlot *= 2
    endWhile

    return Utility.ResizeFormArray(kArmors, iIndex)
EndFunction

最後にResizeFormArrayで配列の空いている部分を削除しました。これで配列の要素数は見つかった防具の数になります。

346 (papyrus)

Form[] kArmors = GetWornArmors(Game.GetPlayer()) ; プレイヤーの装備一覧を取得する
Int iCount = kArmors.Length ; 見つかった防具の数を返す

では、ランダムに何かしてみます。

348 (papyrus)

Form[] kArmors = GetWornArmors(Game.GetPlayer())

if kArmors.Length > 0
    Int iIndex = Utility.RandomInt(0, kArmors.Length - 1)
    Debug.Trace("今回の防具はこれ: " + kArmors[iIndex])
endif

とても短くなりました。配列の要素数が実際に見つかった防具の数なので、要素数が0なら何も見つからなかった、ということを簡単に判別できます。また、配列に無駄な要素がないということは、ランダムに抜き出す処理も乱数を1個作るだけで済んでしまうのです。

タイトルとURLをコピーしました