BGMを再生する

Mod作成

同じことをSkyrimでもやりましたので、基本的なことはそちらにまとめてあります。

BGMの準備

Music TypeとMusic TrackはFO4Editでバニラのレコードをコピーして作ります。aaaMusCombatとしました。

Music TypeのPriorityはバニラのMUSzCombatで始まるMusic Typeを参考に7に設定しました。そしてバニラのMusic Typeは99に下げました。

Music Type変更前変更後
MUSzCombat~6~899
aaaMUSCombat7

クエスト

BGMを管理するQuestのスクリプトです。Questを用意してこのスクリプトを紐づけます。

ベルチバードに乗っている時にもBGMを流すようにしてみました。

BGMを再生するサンプルコード (papyrus)

Scriptname MyTweak:MusicManagerScript Extends Quest

; 戦闘用BGM
MusicType Property aaaMUSCombat Const Auto Mandatory

; ボス戦用BGM
;MusicType Property aaaMUSCombatBoss Const Auto Mandatory

Actor PlayerRef

; BGMを流すために使うスコア
Int BGMScore

Int TimerBGMStartPrepare = 1 Const ; BGMを開始するか判定
Int TimerBGMEndPrepare = 2 Const ; BGMを終了するか判定
Int TimerCheckVertibird = 3 Const ; ベルチバードから降りたか判定

; 最初の1回だけ行う初期化処理
Function Initialize()
    PlayerRef = Game.GetPlayer()
    RegisterForRemoteEvent(PlayerRef, "OnPlayerEnterVertibird")
    RegisterForRemoteEvent(PlayerRef, "OnPlayerLoadGame")
EndFunction

; ゲームをロードする毎に行う初期化処理
Function Initialize2()
    BGMScore = 0
    GoToState("")

    ; プレイヤーの死亡時はBGMを止める
    if Game.IsPluginInstalled("Knockout Framework.esm")
        koframeworkevents KFManagerQuest = Game.GetFormFromFile(0xF99, "Knockout Framework.esm") as koframeworkevents
        RegisterForCustomEvent(KFManagerQuest, "OnUniqueKnockOutStart")
    endif
EndFunction

; スコアを加算する
Function AddBGMScore()
    BGMScore += Utility.RandomInt(1, 10)
    ;Debug.Trace("AddBGMScore score = " + BGMScore)
    StartTimer(1.0, TimerBGMStartPrepare)
EndFunction

; ベルチバードがバニラのものか調べる
Bool Function IsVanillaVertibird(ObjectReference akVertibird)
    Debug.Trace("IsVanillaVertibird " + akVertibird)

    return Math.RightShift(akVertibird.GetBaseObject().GetFormID(), 24) <= 0x06
EndFunction

Event OnQuestInit()
    Initialize()
    Initialize2()
EndEvent

; プレイヤーがベルチバードに乗った時に呼ばれる
Event Actor.OnPlayerEnterVertibird(Actor akActorRef, ObjectReference akVertibird)
    Debug.Trace("OnPlayerEnterVertibird " + akVertibird)

    if IsVanillaVertibird(akVertibird)
        aaaMUSCombat.Add()
        GoToState("VERTIBIRD")
    endif
EndEvent

; ゲームをロードした時に呼ばれる
Event Actor.OnPlayerLoadGame(Actor akActorRef)
    Initialize2()
EndEvent

Event OnTimer(int aiTimerID)
    if aiTimerID == TimerBGMStartPrepare
        ; 戦闘がある程度続いているならBGMを開始する
        if PlayerRef.IsWeaponDrawn() && PlayerRef.IsInCombat()
            if BGMScore >= 10
                aaaMUSCombat.Add()
                GoToState("COMBAT")
            endif
        else
            BGMScore = 0
        endif
    endif
EndEvent

Event KoFrameworkEvents.OnUniqueKnockOutStart(koframeworkevents akSender, var[] Arguments)
    ; place holder
EndEvent

; 戦闘用BGMが流れている時のステート
State COMBAT
    Event OnBeginState(string asLastState)
        Debug.Trace("COMBAT.OnBeginState")
        StartTimer(5.0, TimerBGMEndPrepare)
    EndEvent

    Event OnTimer(int aiTimerID)
        if aiTimerID == TimerBGMEndPrepare
            if PlayerRef.IsWeaponDrawn()
                if PlayerRef.IsInCombat()
                    BGMScore = 10
                else
                    BGMScore -= 1
                endif
            else
                BGMScore -= 5
            endif

            ; 戦闘が終わったようならBGMを止める
            if BGMScore <= 0
                aaaMUSCombat.Remove()
                GoToState("")
            else
                StartTimer(5.0, TimerBGMEndPrepare)
            endif
        endif
    EndEvent

    Event Actor.OnPlayerEnterVertibird(Actor akActorRef, ObjectReference akVertibird)
        Debug.Trace("OnPlayerEnterVertibird " + akVertibird)

        if IsVanillaVertibird(akVertibird)
            GoToState("VERTIBIRD")
        endif
    EndEvent

    Event KoFrameworkEvents.OnUniqueKnockOutStart(koframeworkevents akSender, var[] Arguments)
        string EventName = Arguments[0] as string
        ;Actor Victim = Arguments[1] as Actor
        ;Actor Aggressor = Arguments[2] as Actor

        if EventName == "PlayerDefaultKnockout"
            CancelTimer(TimerBGMEndPrepare)
            aaaMUSCombat.Remove()
            BGMScore = 0
            GoToState("")
        endif
    EndEvent

    Function AddBGMScore()
        ; do nothing
    EndFunction
EndState

; ベルチバードに乗っている時のステート
State VERTIBIRD
    Event OnBeginState(string asLastState)
        Debug.Trace("VERTIBIRD.OnBeginState")
        StartTimer(5.0, TimerCheckVertibird)
    EndEvent

    Event OnTimer(int aiTimerID)
        if aiTimerID == TimerCheckVertibird
            if PlayerRef.IsOnMount()
                StartTimer(5.0, TimerCheckVertibird)
            else
                if PlayerRef.IsInCombat()
                    BGMScore = 10
                    GoToState("COMBAT")
                else
                    aaaMUSCombat.Remove()
                    BGMScore = 0
                    GoToState("")
                endif
            endif
        endif
    EndEvent

    Event KoFrameworkEvents.OnUniqueKnockOutStart(koframeworkevents akSender, var[] Arguments)
        string EventName = Arguments[0] as string
        ;Actor Victim = Arguments[1] as Actor
        ;Actor Aggressor = Arguments[2] as Actor

        if EventName == "PlayerDefaultKnockout"
            CancelTimer(TimerCheckVertibird)
            aaaMUSCombat.Remove()
            BGMScore = 0
            GoToState("")
        endif
    EndEvent

    Function AddBGMScore()
        ; do nothing
    EndFunction
EndState

スペル

周囲のActorに配布するスクリプトです。Cloakスペルを用意してスペルを配布、そのMagic Effectにこのスクリプトを紐づけます。

別の処理も含まれています。

BGMを再生するサンプルコード2 (papyrus)

Scriptname MyTweak:DetectEntryScript Extends ActiveMagicEffect

; BGM管理クエスト
MyTweak:MusicManagerScript Property MusicManager Const Auto Mandatory

; 死体に入れるトークン
Weapon Property aaaCorpseToken Const Auto Mandatory

; NPCかどうかを判定するためのキーワードリスト
FormList Property NPCKeywordList Const Auto Mandatory

Actor Property PlayerRef Const Auto Mandatory

Actor MySelf

; スペルが切れたらTrueになる
Bool IsFinished

; スタック検知用に座標を保存する
Float LastX
Float LastY
Float LastZ

; スタック検知用カウンター
Int StuckCounter

; タイマー識別ID
Int TimerDetect = 1 Const ; 攻撃ヒット後の判定
Int TimerRestart = 2 Const ; OnHitイベントを再開する
Int TimerStuck = 3 Const ; スタック検知

Event OnEffectStart(Actor akTarget, Actor akCaster)
    ; スペルが速攻で切れてしまう時があるので様子をみる
    Utility.Wait(1.0)

    if IsFinished
        return
    endif

    MySelf = akTarget

    if MySelf.IsDead()
        GoToState("DEAD")
    else
        RegisterForHitEvent(MySelf, PlayerRef)

        ; NPCでないなら非NPCのステートへ
        ; NPCならステートはNoneのまま
        if !MySelf.HasKeywordInFormList(NPCKeywordList)
            GoToState("NOT_NPC")
        endif
    endif
EndEvent

Event OnEffectFinish(Actor akTarget, Actor akCaster)
    IsFinished = true
EndEvent

Event OnHit(ObjectReference akTarget, ObjectReference akAggressor, Form akSource, Projectile akProjectile, bool abPowerAttack, \
    bool abSneakAttack, bool abBashAttack, bool abHitBlocked, string apMaterial)
    ; しばらくはOnHitイベントを受け取らないようにして負荷対策する
    UnregisterForAllHitEvents()

    ; 3秒後に判定する
    ; 3秒後にする理由は、銃声がやむのを待つのと、即死したかどうかを判断するため
    StartTimer(3.0, TimerDetect)
EndEvent

Event OnDying(Actor akKiller)
    UnregisterForAllHitEvents()
    CancelTimer(TimerStuck)
    GoToState("DEAD")
    ;MySelf.CreateDetectionEvent(PlayerRef, 100)
EndEvent

Event OnDeath(Actor akKiller)
    UnregisterForAllHitEvents()
    CancelTimer(TimerStuck)
    GoToState("DEAD")
    ;MySelf.CreateDetectionEvent(PlayerRef, 100)
EndEvent

Event OnActivate(ObjectReference akActionRef)
    ;Debug.Trace(Self + " OnActivate")

    if MySelf.IsDead()
        GoToState("DEAD")
    endif
EndEvent

Event OnTimer(int aiTimerID)
    ;Debug.Trace(Self + " OnTimer " + aiTimerID)

    if aiTimerID == TimerDetect
        ;if MySelf.IsHostileToActor(PlayerRef)
            MusicManager.AddBGMScore()
        ;endif

        ; 30秒後から再びOnHitイベントを受け付ける
        StartTimer(30.0, TimerRestart)
    elseif aiTimerID == TimerRestart
        RegisterForHitEvent(MySelf, PlayerRef)
    elseif aiTimerID == TimerStuck
        ;Debug.Trace(Self + " OnTimer " + aiTimerID)

        ; 座標が変化していないならカウンターを増やす
        if Math.abs(LastX - MySelf.X) < 10.0 && Math.abs(LastY - MySelf.Y) < 10.0 && Math.abs(LastZ - MySelf.Z) < 10.0
            StuckCounter += 1
        else
            StuckCounter = 0
        endif

        LastX = MySelf.X
        LastY = MySelf.Y
        LastZ = MySelf.Z

        if StuckCounter >= 5
            ; スタックしているようなら戦闘を強制的に中断させる
            ;Debug.Trace(MySelf + " is stucked")
            Actor Victim = MySelf.GetCombatTarget()

            if Victim
                MySelf.StopCombat()
                Utility.Wait(0.5)
                MySelf.StartCombat(Victim)
            endif

            StuckCounter = 0
        elseif StuckCounter
            StartTimer(1.0, TimerStuck)
        else
            StartTimer(5.0, TimerStuck)
        endif
    endif
EndEvent

State NOT_NPC
    Event OnCombatStateChanged(Actor akTarget, int aeCombatState)
        ;Debug.Trace(Self + " OnCombatStateChanged " + aeCombatState)

        ; プレイヤーを戦闘を開始したのならスタックの検知を開始する
        if akTarget == PlayerRef
            if aeCombatState == 1
                StartTimer(1.0, TimerStuck)
            else
                CancelTimer(TimerStuck)
            endif
        endif
    EndEvent
EndState

State DEAD
    Event OnActivate(ObjectReference akActionRef)
        ;Debug.Trace(Self + " DEAD.OnActivate")

        ; プレイヤーがアクティベートしたら死体にトークンを入れる
        if akActionRef == PlayerRef
            MySelf.AddItem(aaaCorpseToken, abSilent = true)
            Dispel()
        endif
    EndEvent

    Event OnTimer(int aiTimerID)
        ;Debug.Trace(Self + " DEAD.OnTimer")
    EndEvent
EndState

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