部位破壊を作ってみた

Mod作成

攻撃を受けたら服が破れて壊れていくようにしてみました。

作るべき3つのポイント

  • 破れた装備のメッシュ
  • アタッチメントを実現するレコード一式
  • 部位破壊を実現するスクリプト

破れた装備のメッシュを作る

Outfit Studioで装備のプロジェクトを読み込みます。

破れるShapeを決めて選択したら、残す部分にマスクをかけます。Tを押してテクスチャは隠したほうがいいです。

メニューからSlider、New Zap Sliderを選んでZap Sliderを作成します。

プロジェクトを別名で保存します。生成されるメッシュの名前を別のものにします。

Display Nameを変更したらTo Projectボタンをクリックすると下にも反映されるので楽です。

Output File Nameも忘れずに変更します。

あとはBodySlideでメッシュをBuildするだけです。

上の方にZap Sliderが出てきますので、チェックを入れます。

アタッチメントを実現するレコードを作る

何でもいいですが、プラグインを1つ用意します。今回はMyBreakArmors.espとしました。

必要なものは以下の通りです。

  • Armor
  • Armor Addon
  • Constructible Object
  • Keyword
  • Object Modification
  • Component

Armorは防具そのものです。Armor Addonは防具の見た目を決めます。Constructible Objectは装備を作成するレシピです。ここまではSkyrimと同じです。

Fallout 4はアタッチメントという仕組みが用意されています。防具に機能を追加したり、見た目をかえたりできます。

Keywordがアタッチメントのスロットになり、防具とアタッチメントを紐付ける目印になります。

Object Modificationはアタッチメントそのものです。アタッチメントを取り付けるとどうなるのかを定義します。

Componentは材料です。アタッチメントを取り付けるときに要求される材料になります。

Constructible Objectで防具にアタッチメントを取り付けるレシピが必要です。これがないとアーマー作業台で出てこないので取り付けができません。

綺麗、傷んでいる、ボロボロの3段階を用意することにします。

Keyword

Mod AssociationとAttach Pointをそれぞれ用意します。

KeywordのTypeのところで指定です。Editor IDはバニラにならってma_とap_にしました。

Armor

ModelsでArmor Addonを指定しますが、このようにバリエーションを用意して指定します。

Addon Indexが重要です。きちんと1から並べます。

KeywordsにMod Association (ma_)のキーワードを指定します。

Attach Parent SlotsにAttach Point (ap_)のキーワードを指定します。

Object Modification

アタッチメント3つ分用意します。

内容はAddonIndexの変更になります。Value 1のところにAddon Indexの値を入れます。Armorで設定したものと合わせます。

Component

ダミーのComponentを用意します。

Constructible Object

アタッチメントのレシピを3つ用意します。

「綺麗な状態だ」にするレシピは、いわゆる修理に相当しますので、バニラの布などを指定します。

Created ObjectのところはObject Modificationを指定することになります。

「傷んでいる」と「ボロボロだ」にするレシピは意図的に取り付けできないようにするため、さきほど用意したComponentを指定します。

Armor

Armorに戻り、Object TemplateのところでデフォルトのObject Modificationを指定します。

部位破壊を実現するスクリプト

サンプルソースコードを用意しました。

Questを1つ作って紐付けます。

BreakArmorsScript.psc (papyrus)

Scriptname MyBreakArmors:BreakArmorsScript Extends Quest

Actor Property PlayerRef Const Auto Mandatory
Keyword Property BreakableArmor Const Auto Mandatory
BreakArmorSyntax[] Property BreakArmorsList Const Auto Mandatory

Struct BreakArmorSyntax
    Int BipedSlotIndex
    Form BaseArmor
    ObjectMod ObjectMod01
    ObjectMod ObjectMod02
    ObjectMod ObjectMod03
EndStruct


Function Initialize()
    RegisterForRemoteEvent(PlayerRef, "OnPlayerLoadGame")
EndFunction


Function Initialize2()
    RegisterForHitEvent(PlayerRef, aiBlockFilter = 0)
EndFunction


Function ShowBreakArmorsList()
    Int i = BreakArmorsList.Length

    Debug.Trace("BreakArmorsList " + i)

    while i > 0
        i -= 1
        Debug.Trace(i)
        Debug.Trace("BipedSlotIndex = " + BreakArmorsList[i].BipedSlotIndex)
        Debug.Trace("BaseArmor = " + BreakArmorsList[i].BaseArmor)
        Debug.Trace("ObjectMod01 = " + BreakArmorsList[i].ObjectMod01)
        Debug.Trace("ObjectMod02 = " + BreakArmorsList[i].ObjectMod02)
        Debug.Trace("ObjectMod03 = " + BreakArmorsList[i].ObjectMod03)
    endwhile
EndFunction


Function Debug()
    Actor:WornItem kWornItem = PlayerRef.GetWornItem(3)
    Debug.Trace("kWornItem.Item = " + kWornItem.Item)

    ObjectMod[] kObjectMods = PlayerRef.GetWornItemMods(3)
    Int i = kObjectMods.Length
    while i > 0
        i -= 1
        Debug.Trace("kObjectMods " + i)
        ObjectMod:PropertyModifier[] kProperties = kObjectMods[i].GetPropertyModifiers()
        Int j = kProperties.Length

        while j > 0
            j -= 1
            Debug.Trace("kProperties " + j)
            Debug.Trace("Target = " + kProperties[j].Target)
            Debug.Trace("Operator = " + kProperties[j].Operator)
            Debug.Trace("Object = " + kProperties[j].Object)
            Debug.Trace("Value1 = " + kProperties[j].Value1)
            Debug.Trace("Value2 = " + kProperties[j].Value2)
        endwhile
    endwhile
EndFunction


Function TryToBreakArmor()
    Actor:WornItem kWornItem
    Int[] iBipedSlotList = new Int[2]
    iBipedSlotList[0] = 3
    iBipedSlotList[1] = 10
    Int i = Utility.RandomInt(0, 1)
    Int j = iBipedSlotList.Length

    while j > 0
        j -= 1
        kWornItem = PlayerRef.GetWornItem(iBipedSlotList[i])

        if kWornItem && kWornItem.Item.HasKeyword(BreakableArmor)
            if TryToThisArmor(kWornItem.Item, iBipedSlotList[i]) == 1
                return
            endif
        endif

        i += 1

        if i >= iBipedSlotList.Length
            i = 0
        endif
    endwhile
EndFunction


Int Function TryToThisArmor(Form akArmor, Int aiBipedSlotIndex)
    ObjectMod[] kObjectMods = PlayerRef.GetWornItemMods(aiBipedSlotIndex)
    Int i = kObjectMods.Length

    if i == 0
        ; this armor is not breakable
        Debug.Trace("TryToThisArmor " + akArmor + " this armor is not breakable")
        return 0
    endif

    Int iListIndex = BreakArmorsList.FindStruct("BaseArmor", akArmor, 0)

    while i > 0
        i -= 1
        ObjectMod:PropertyModifier[] kProperties = kObjectMods[i].GetPropertyModifiers()

        Int k = kProperties.FindStruct("Target", 263, 0)

        if k != -1
            Float v = kProperties[k].Value1

            if v == 1.0
                if BreakArmorsList[iListIndex].ObjectMod02
                    PlayerRef.AttachModToInventoryItem(akArmor, BreakArmorsList[iListIndex].ObjectMod02)
                    ; success
                    Debug.Trace("TryToThisArmor " + akArmor + " success")
                    return 1
                else
                    ; no break omod
                    Debug.Trace("TryToThisArmor " + akArmor + " no break omod")
                    return 2
                endif
            elseif v == 2.0
                if BreakArmorsList[iListIndex].ObjectMod03
                    PlayerRef.AttachModToInventoryItem(akArmor, BreakArmorsList[iListIndex].ObjectMod03)
                    ; success
                    Debug.Trace("TryToThisArmor " + akArmor + " success")
                    return 1
                else
                    ; almost broken
                    Debug.Trace("TryToThisArmor " + akArmor + " almost broken")
                    return 2
                endif
            else
                ; almost broken
                Debug.Trace("TryToThisArmor " + akArmor + " almost broken")
                return 2
            endif
        endif
    endwhile

    ; this armor is not breakable
    Debug.Trace("TryToThisArmor " + akArmor + " this armor is not breakable")
    return 0
EndFunction


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

    ;Debug.Trace("akAggressor=" + akAggressor + " akSource=" + akSource)

    Utility.Wait(5.0)

    if akSource as Weapon
        if !(akTarget as Actor).IsDead() && !(akTarget as Actor).IsInKillMove()
            ;if Utility.RandomInt(1, 5) == 1
                TryToBreakArmor()
            ;endif
        endif
    endif

    RegisterForHitEvent(PlayerRef, aiBlockFilter = 0)
EndEvent


Event OnQuestInit()
    Initialize()
    Initialize2()
EndEvent


Event Actor.OnPlayerLoadGame(Actor akActorRef)
    Initialize2()
EndEvent

補足

戦闘中はできるだけ処理を早くしたいので、まず部位破壊に対応している防具であることを示すキーワード「BreakableArmor」を用意してArmorに付けておきます。これでキーワード検索を行えば条件判断をほぼゲームエンジン任せにできるので、スクリプトの稼働時間が短くなるはずです。

プレイヤーがキルムーブを受けている最中に防具のアタッチメントを操作するとCTDするような印象がありました。そこでOnHitを受けてから少し待ってからキルムーブも死んでもいないことを確認した上で処理を行うようにしました。

BreakArmorSyntaxという構造体(Struct)ですが、以下のようになっています。

キー内容
BipedSlotIndexスロット番号。GetWornItemやGetWornItemModsで使用する。
BaseArmorArmorのFormを指定。
ObjectMod01Object Modificationその1。「綺麗な状態」のものを指定。
ObjectMod02Object Modificationその2。「傷んでいる」のものを指定。
ObjectMod03Object Modificationその3。「ボロボロだ」のものを指定。

BipedSlotIndexは胴装備が3番です。スカートも用意して10番も指定しました。

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