スペルがうまく動かない

Modを作ろう

ActorにSpellを持たせて発動したMagic Effectが切れてしまう問題に対処しました。

ActorでPapyrusスクリプトを動かすにはいくつか方法があります。

  • ActorにScriptを直接持たせる
  • ActorをQuestのReferenceAliasに入れて、ReferenceAlias経由でScriptを動かす
  • ActorにSpellをかけるか持たせるかして、Magic Effect経由でScriptを動かす

他にもあるかもしれませんが、大体これのどれかになると思います。

Active Magic Effectとは

Actorにスペルの効果が発動すると、Magic Effectが動き出します。現在動いているMagic EffectのことをActive Magic Effectといいます。

スペルには効果時間という概念があります。つまり、Active Magic Effectには寿命があるのです。

  • 効果が始まるときにOnEffectStartイベントが発生
  • 効果が終わるときにOnEffectFinishイベントが発生

効果が終わるのは以下のどれかです。

  • Magic Effectに設定された効果時間が経過した時
  • ActorがUnloadされた時
  • Dispel関数で意図的に終わらせた時

Active Magic Effectは永続に設定できる

通常、Magic Effectの種類はFire and ForgetかConcentrationです。Fire and Forgetはスペルをかけると発動し、効果時間が切れると終了します。Concentrationはスペルを詠唱している間だけずっと効果が続き、詠唱を止めると終わります。

種類をConstant Effectにすると、効果時間が無期限となります。Constant Effectはかけることはできず、スペルを持っている間はずっと効果が発動するようになります。

Constant Effectにしてあると、効果時間という概念がなくなるので、時間経過では切れません。また、Dispel関数で終わらせることもできません。ですが、ActorがUnloadされると終わります。つまり、Actorがエリア移動でプレイヤーと異なるセルに移動すると、Active Magic Effectが切れてしまいます。

永続にしたActive Magic Effectが切れたあとは

ActorにConstant Effectのスペルを持たせると、Active Magic Effectが切れるとスペルだけが残った状態になります。プレイヤーと同じセルに来たら再びMagic Effectが動いてほしいのですが、どうやら動かないようです。

そこで、Actorがプレイヤーと同じセルに来たらMagic Effectが発動し、その効果は永続で、セルから出たらActive Magic Effectが終了、再び同じセルに来たらMagic Effectが再発動する仕組みを考えました。

  • Actorに持たせるMySpellとMyEffectを作る(Magic EffectはConstant Effectで永続とする)
  • ActorにMySpellを持たせるMyDeliverSpellとMyDeliverEffectを作る
  • ActorにMyDeliverSpellをばらまくMyCloakSpellとMyCloakEffectを作る
  • 再発動させるためのRestartDeliverSpellとRestartDeliverEffectを作る
  • ActorにRestartDeliverSpellをばらまくRestartCloakSpellとRestartCloakEffectを作る

MySpellはActorに持たせたいスペルです。効果はMyEffectです。Magic EffectはConstant Effectで永続とします。MyEffectにスクリプトを設定し、対象にThisActorNeedRestartSpellを持たせるようにします。

MyDeliverSpellはMySpellをActorに持たせるスペルです。効果はMyDeliverEffectです。Magic EffectはConcentrationとAimedです。Conditionに「MySpellを持ってない」を設定します。MyDeliverEffectにスクリプトを設定し、AddSpell関数を使って対象にMySpellを持たせます。

MyCloakSpellはMyDeliverSpellを周囲のActorにばらまくスペルです。効果はMyCloakEffectです。Magic EffectはConstant Effectで永続とします。Arch TypeにCloakを選びます。Assoc ItemにMyDeliverSpellを指定します。

RestartDeliverSpellはMySpellを再発動させるスペルです。効果はRestartDeliverEffectです。Magic EffectはConcentrationとAimedです。Conditionに「MySpellを持っている」「MyEffectを持っていない」を設定します。RestartDeliverEffectにスクリプトを設定し、RemoveSpell関数を使って対象からMySpellを取り除きます。

RestartCloakSpellはRestartDeliverSpellを周囲のActorにばらまくスペルです。効果はRestartCloakEffectです。Magic EffectはConstant Effectで永続とします。Arch TypeにCloakを選びます。Assoc ItemにRestartDeliverSpellを指定します。

Active Magic Effectの注意事項

通常、Active Magic EffectはOnEffectStartイベントで始まり、OnEffectFinishイベントで終わります。プレイヤーがスペルを手動で唱えると相手に効果が発生し、しばらくしたら効果が切れて終わるのであれば、あまり問題にはなりません。

ところが、Cloakでばらまいている時に対象が一瞬だけ現れて消えたり、長い処理時間の最中にスペルが切れるようなことがあると、すこし困った状況になります。

Active Magic Effectが切れてもスクリプトは動き続けます。ただし、Active Magic Effect自体が消滅しているため、スクリプトは自分自身がいないという状態になります。Papyrusでいうと、SelfがNoneになります。こうなると、Selfが必要な関数は一切動かなくなります。RegisterSingleUpdate、GoToState、Dispelなどが該当します。これらの関数を実行すると、Traceログにエラーが出力され、関数呼び出しは失敗に終わります。

[02/01/2020 – 09:20:45PM] Error: Unable to call RegisterForSingleUpdate – no native object bound to the script object, or object is of incorrect type
stack:
[None].eqEffectMovementScript.RegisterForSingleUpdate() – “<native>” Line ?
[None].eqEffectMovementScript.onBeginState() – “eqEffectMovementScript.psc” Line 90
[None].eqEffectMovementScript.GotoState() – “ActiveMagicEffect.psc” Line ?
[None].eqEffectMovementScript.OnUpdate() – “eqEffectMovementScript.psc” Line 82

この手のエラーは大抵は害がなく無視できるものです。ですが、無駄な処理を中断したり、エラーそのものを抑制したい場合は、書き方に工夫が必要です。

Magic Effectが切れることに対処 (papyrus)

Scriptname SampleScript Extends ActiveMagicEffect

Bool IsFinished

; スペルがかかると呼ばれる
Event OnEffectStart(Actor akTarget, Actor akCaster)
    ; 速攻でスペルが切れることがあるので、少し待ってから次の処理に移る
    Utility.Wait(0.5)

    ; スペルが切れているなら何もしないで終わる
    if IsFinished
        return
    endif

    ; 1秒後になにかする
    RegisterForSingleUpdate(1.0)
EndEvent

; スペルが切れると呼ばれる
; 既にActiveMagicEffectは消えており、ActiveMagicEffectに対する操作は一切無効
Event OnEffectFinish(Actor akTarget, Actor akCaster)
    IsFinished = true
EndEvent

Event OnUpdate()
    ; スペルが切れているなら何もしないで終わる
    if IsFinished
        return
    endif

    ; ここでやりたことをする

    ; 再び1秒後になにかする
    if !IsFinished
        RegisterForSingleUpdate(1.0)
    endif
EndEvent

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