同じことをSkyrimでもやりましたので、基本的なことはそちらにまとめてあります。
BGMの準備
Music TypeとMusic TrackはFO4Editでバニラのレコードをコピーして作ります。aaaMusCombatとしました。
Music TypeのPriorityはバニラのMUSzCombatで始まるMusic Typeを参考に7に設定しました。そしてバニラのMusic Typeは99に下げました。
Music Type | 変更前 | 変更後 |
---|---|---|
MUSzCombat~ | 6~8 | 99 |
aaaMUSCombat | 7 |
クエスト
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