PlasmaPlugin Dec/07/2023 10:00

独自の使用効果を実装する方法 - 既存の実装に最大限乗っかる

この記事は、ツクールアドベントカレンダー2023 7日目の記事になります。

前の記事はYUWAKAさんの RPGツクールMVでニコ生ゲー作ってみた です。
次の記事はA-9さんの 初めてのツクール作品は涙の味がした です。

見返してみたら、昨年は4日目の記事を担当していました。
本記事のコード例ではちょうど昨年紹介したTypeScriptで書くことがありますが、そんなに難しい書き方はしませんので、安心してください。

今年は、去年よりも具体的なプラグインの実装に関するお話をします。
コードを書かない人にとっては退屈な話になるかもしれませんが、話半分にでも読んでみてください。

本記事は、本ci-enをフォローしていなくても、最後まで読むことができます。

はじめに 使用効果

まずは、使用効果という言葉の認識を読者の皆様との間で揃えておきます。

使用効果とは、データベースにおけるスキルやアイテムの設定画面右側にある使用効果の欄で設定する、そのスキルやアイテムの効果のことです。
例えば、スクショのスキル「ポイズン」には、 ステート付加 毒 50% という使用効果が設定されています。

この記事で解説すること

この記事では、以下の内容を解説します。

  • 使用効果のRPGツクールMZにおける実装
  • 独自の使用効果をメンテナンス性よく効率的に実装する方法

また、本記事はRPGツクールMZ コアスクリプト1.7.0の実装をもとに解説します。

デフォルトの機能では実現が難しかったり、煩雑な実装になってしまうような使用効果をプラグインでスマートに実装したい、という方向けの記事になります。

使用効果の実装

では、先程も例にあげたスキル「ポイズン」の使用効果 ステート付加 毒 50% と、もうひとつ、スキル「キュアーⅠ」の使用効果について、実装を見ていきましょう。

使用効果の設定

スキルの設定は、 data/Skills.json にJSON形式で書かれてます。
ポイズンの設定は以下のようになっています。

{
  "id": 71,
  "animationId": 59,
  "damage": {
    "critical": true,
    "elementId": 0,
    "formula": "a.atk",
    "type": 0,
    "variance": 20
  },
  "description": "敵全体を毒状態にする。",
  "effects": [
    {
      "code": 21,
      "dataId": 4,
      "value1": 0.5,
      "value2": 0
    }
  ],
  "hitType": 2,
  "iconIndex": 2,
  "message1": "%1は%2を唱えた!",
  "message2": "",
  "mpCost": 15,
  "name": "ポイズン",
  "note": "",
  "occasion": 1,
  "repeats": 1,
  "requiredWtypeId1": 0,
  "requiredWtypeId2": 0,
  "scope": 2,
  "speed": 0,
  "stypeId": 1,
  "successRate": 100,
  "tpCost": 0,
  "tpGain": 2,
  "messageType": 1
},

実際のファイルには1行にこの内容が詰め込まれているので、かなり読みにくくなっていますが、ここでは読みやすくなるように改行とインデントでフォーマットしています。

今回重要なのは使用効果の部分です。

  "effects": [
    {
      "code": 21,
      "dataId": 4,
      "value1": 0.5,
      "value2": 0
    }
  ],

effects というキー名で配列が定義されており、その配列の各要素はオブジェクトになっています。
オブジェクトはそれぞれ、 code dataId value1 value2 という要素を持っています。

code は、その効果の種別を表します。21はステート付加です。
この番号はコード上、 rmmz_objects.js の1400行目付近に定義されています。

Game_Action.EFFECT_RECOVER_HP = 11;
Game_Action.EFFECT_RECOVER_MP = 12;
Game_Action.EFFECT_GAIN_TP = 13;
Game_Action.EFFECT_ADD_STATE = 21;
Game_Action.EFFECT_REMOVE_STATE = 22;
Game_Action.EFFECT_ADD_BUFF = 31;
Game_Action.EFFECT_ADD_DEBUFF = 32;
Game_Action.EFFECT_REMOVE_BUFF = 33;
Game_Action.EFFECT_REMOVE_DEBUFF = 34;
Game_Action.EFFECT_SPECIAL = 41;
Game_Action.EFFECT_GROW = 42;
Game_Action.EFFECT_LEARN_SKILL = 43;
Game_Action.EFFECT_COMMON_EVENT = 44;

Game_Action.EFFECT_ADD_STATE がステート付加に対応します。
この定数をベースにして、コードを読んでいくことになります。

dataId value1 value2 については、 code の種別に応じて意味が変わります。
ステート付加の場合は、 dataId は付加するステートのIDになります。
今回は4なので、データベースのID4に定義されている毒です。
value1 はステート付加の基礎成功率(※)、 value2 は常に0で意味を持ちません。

※実際の計算では対象のステート有効度や使用者と対象者の運差による補正を行いますが、その補正を行う前の成功率を本記事では基礎成功率と呼んでいます。

同じようにして、キュアーⅠの設定から使用効果の部分を抜き出してみます。

  "effects": [
    {
      "code": 22,
      "dataId": 4,
      "value1": 1,
      "value2": 0
    },
    {
      "code": 22,
      "dataId": 10,
      "value1": 1,
      "value2": 0
    },
    {
      "code": 22,
      "dataId": 12,
      "value1": 1,
      "value2": 0
    }
  ],

ステートID 4, 10, 12 つまり毒、睡眠、麻痺について、それぞれ100%の確率でステートを解除するという使用効果が設定されています。
先程の定義を見れば、 code 22は、 Game_Action.EFFECT_REMOVE_STATE ですね。
value1 はステート解除の確率ですが、 value2 はステート付加と同じく、常に0で意味を持ちません。

使用効果の有効性

ここからは、コードを読みます。
ステートの付加はちょっと複雑な部分があるので、ステートの解除について見ていきます。

Game_Action.EFFECT_REMOVE_STATE で rmmz_objects.js 内をテキスト検索すると、3箇所ヒットすることがわかります。

1箇所目が、先程見た22を代入している箇所です。
2箇所目が本項目で説明する、使用効果の有効性判定を行っている箇所になります。

Game_Action.prototype.testItemEffect で、渡された効果の code をもとにswitch文で分岐していますね。
スキルやアイテムに有効な使用効果があるかどうかを確かめるために使われているコードです。

なぜ使用効果の適用の前に有効性判定が必要なのかというと、メニュー画面において使っても意味のない回復アイテムやスキルを使えないようにするためです。
もう回復できないのに回復スキルが使える、というようにしてしまうと、無限にMPを浪費できてしまいますね。

ステート解除については、有効性判定は以下のような実装になっています。

case Game_Action.EFFECT_REMOVE_STATE:
  return target.isStateAffected(effect.dataId);

対象者がすでに dataId で指定したステートを付加された状態である場合に真 (つまり、有効である) と判定します。
キュアーⅠの場合は毒、睡眠、麻痺についてこの判定が行われ、どれかひとつでもそのステートが付加された状態であれば使えますが、どのステートも付加されていない対象にキュアーⅠを使うことはできません。
(ただし、戦闘中はいわゆる先読み回復などのテクニックを使えるようにするため、この有効性判定に関わらずキュアーⅠを使用できます)

使用効果の適用

テキスト検索でヒットした3箇所目は、 Game_Action.prototype.applyItemEffect 関数です。

case Game_Action.EFFECT_REMOVE_STATE:
  this.itemEffectRemoveState(target, effect);
  break;

ついに、ステートの解除という使用効果を実際に適用するコードです。
アイテムの使用効果によってステートを解除する、という関数を呼び出しています。

Game_Action.prototype.itemEffectRemoveState = function(target, effect) {
  let chance = effect.value1;
  if (Math.random() < chance) {
    target.removeState(effect.dataId);
    this.makeSuccess(target);
  }
};

Math.random() で生成された 0 以上 1未満の数値が、使用効果の value1 の値よりも小さい場合に、対象のステートを解除しています。
エディタ上でステート解除 100%になっている場合、 value1 の値は 1 です。したがって、確実にステートを解除できます。

独自の使用効果を作りたい

ここまで、ステートの解除を例にして、使用効果がどう実装されているかを見てきました。
これをもとにして、以下のようなオリジナルの使用効果を実装してみます。

  • 敵味方全体のレベルが3の倍数の対象にステート「アホ」を付加する
  • 「アホ」の有効度が0%であるか、無効化の特徴がついた対象には効かない


実装においてやるべきことは以下の通りです。

  • 使用効果コードを割り当てる
  • スキル・アイテムのメモ欄に使用効果のメモタグを記述する
  • 使用効果のメモタグを読み込む
  • 使用効果の有効性判定を行う
  • 使用効果の適用を行う

余談:力技による実装も可能ではある

元々の使用効果の仕組みに乗っからず、力技で実装していくことも可能ではあります。
Game_Action.prototype.apply をフックして、スキルやアイテムのメモ欄に特定の記述があれば効果を適用する、というような記述をしていくことも不可能ではありません。
しかし、既存の仕組みに乗っかるほうが遥かに実装が簡単になり、メンテナンス性がよくなりますし、競合の心配も減ります。

使用効果コードを割り当てる

独自の使用効果を表現するために、 code を割り当てます。

最も単純なやり方をするとこうなります。

Game_Action.EFFECT_LEVEL_X_STATE = 51;

デフォルトの使用効果コードは44までしかありませんから、それとかぶらない番号を割り当てます。

ただし、こういう懸念を抱いた方がいるかもしれません。
もし、別のプラグインで使用効果コード51が使われていたら?

競合が起きます。意図しない挙動になってしまうかもしれません。

使用効果ごとに別の番号を割り当てないといけないので、番号の管理が人力ではしんどい、というのもその通りです。

その懸念を解消するためのプラグインがあります。
DarkPlasma_AllocateUniqueEffectCode.js を使えば、プラグインパラメータで設定した番号から順番に割り当てていってくれます。

const pluginName = "プラグイン名";
const levelXStateEffect = uniqueEffectCodeCache.allocate(pluginName, 0);

DarkPlasma_AllocateUniqueEffectCode.js より後ろに読み込むプラグインでこう書けば、これだけで独自の使用効果コードを割り当ててくれます。

スキル・アイテムのメモ欄に使用効果のメモタグを記述する

今回は、レベル3の倍数の対象にステートID50のステートを付加したいので、こういうふうに記述することにします。

<levelXState:
  3:50
>

使用効果のメモタグを読み込む

データベースのメモタグを読み込んで、対象のアイテムやスキルの effects に新しい使用効果として追加します。

function DataManager_LevelXStateMixIn(dataManager: typeof DataManager) {
  const _extractMetadata = dataManager.extractMetadata;
  dataManager.extractMetadata = function (data) {
    _extractMetadata.call(this, data);
    if ("effects" in data && data.meta.levelXState) {
      String(data.meta.levelXState)
        .split('\n')
        .filter(line => line.includes(':')).forEach(line => {
          const e = line.split(':');
          if (e.length === 2) {
            data.effects.push({
              code: levelXStateEffect.code,
              dataId: Number(e[1]),
              value1: Number(e[0]),
              value2: 0,
            });
          }
        })
    }
  };
}

DataManager_LevelXStateMixIn(DataManager);

levelXStateEffect.code に、先程のプラグインが割り当てた使用効果のコードが入っています。
dataId はステートIDのため50、 value1 は3の倍数のため、3です。 value2 は使わないので0にしておきます。

複数の設定を読み込めるように書いているので少し複雑に見えるかもしれませんが、やっていることは単純です。
levelXState メモタグを分解して、スキルやアイテムの使用効果として登録しています。

使用効果の有効性判定を行う

Game_Action クラスの testItemEffect メソッドで、渡された使用効果のコードが今回割り当てたものであった場合に、有効性を判定します。
以下のすべてを満たす場合に有効とみなします。

  • 対象にステートが付加されていない
  • 対象にレベルが存在する (※)
  • 対象のレベルが使用効果で指定した value1 の倍数である
function Game_Action_LevelXStateMixIn(gameAction: Game_Action) {
  const _testItemEffect = gameAction.testItemEffect;
  gameAction.testItemEffect = function (target, effect) {
    if (effect.code === levelXStateEffect.code) {
      return !target.isStateAffected(effect.dataId)
        && !!target.level
        && target.level%effect.value1 === 0;
    }
    return _testItemEffect.call(this, target, effect);
  };
}

Game_Action_LevelXStateMixIn(Game_Action.prototype);

※敵キャラには本来レベルが存在しませんが、 DarkPlasma_EnemyLevel.js など、敵キャラにレベルを設定するプラグインはいくつか存在します。

余談:コアスクリプトのバグ?

実は、ステートの付加の有効性判定は、対象がそのステートにかかっていない場合に真になります。
行動制約によって付加されない場合や、戦闘不能である場合なども無視しているので、例えば戦闘不能以外のステートを付加するスキルを作って、対象に戦闘不能の味方を含めると、メニュー画面から戦闘不能の味方に対して使用することができてしまいます。
(戦闘不能の場合は実際にステートを付加しようとする段階で無効化されるので、戦闘不能かつそのステートが付加されている、という状態にはなりません)

強化やステートを付加する使用効果は特定の条件で無駄な使用を防げない、というのはコアスクリプトのバグではないかと思いますが、実際に問題になるケースが限られているので、誰も何も言っていないのでしょう。

使用効果の適用を行う

いよいよ、実際に効果を適用します。
applyItemEffect メソッドで今回割り当てたコードだった場合に、条件を満たしていればステートを付加します。

function Game_Action_LevelXStateMixIn(gameAction: Game_Action) {
  /**
   * testItemEffect はここでは長くしすぎないために省略
   */

  const _applyItemEffect = gameAction.applyItemEffect;
  gameAction.applyItemEffect = function (target, effect) {
    if (effect.code === levelXStateEffect.code) {
      if (
        target.isStateAddable(effect.dataId)
        && target.stateRate(effect.dataId) > 0
        && target.level
        && target.level%effect.value1 === 0
      ) {
        target.addState(effect.dataId);
        this.makeSuccess(target);
      }
    }
    _applyItemEffect.call(this, target, effect);
  };
}

Game_Action_LevelXStateMixIn(Game_Action.prototype);

プラグイン完成品

今回紹介したものは、 レベルが特定数値の倍数の対象にのみ有効なステート付加効果 のページで紹介している こちらのプラグイン が完成品です。

ビルド前のTypeScriptを読みたい方はこちらにあります。

終わりに

今回はRPGツクールMZにおける使用効果の実装を解説し、レベルがある数の倍数の対象にステートを付加するという独自の使用効果の実装を紹介しました。

デフォルトでは微妙に手が届かない使用効果について、もちろんコモンイベントを駆使して頑張るのもひとつの手段ではあります。
今回のように類似した既存の使用効果があったり、コモンイベントをこねくり回しても複雑化してしまうというケースでは、プラグインによる実装のほうが管理しやすくなる可能性があります。

ビルド前のコードを見ていただければ、実装自体はきわめてシンプルであるというのがおわかりいただけるかと思います。

  • 使用効果コードを割り当てる
  • スキル・アイテムのメモ欄に使用効果のメモタグを記述する
  • 使用効果のメモタグを読み込む
  • 使用効果の有効性判定を行う
  • 使用効果の適用を行う

今回のポイントは、この5ステップで既存の実装に最大限乗っかりながら、シンプルに独自の使用効果を実装できる、というところです。

宣伝

RPGツクールMZ (あるいはMV) における、技術的なご相談をお受けいたします。

Discordサーバー
マシュマロ

いずれかお好きな方法で、お気軽にご相談ください。
お答えしやすい内容、しにくい内容については、プロフィールにある通りです。
Plasmaが公開しているプラグインについては、GitHubにissueを作成頂いても構いません。

GitHub - MZ向けプラグイン
GitHub - MV向けプラグイン

RPGツクールMZ向けプラグイン紹介ブログの記事には、GitHubにログインしているとコメントすることができます。

If you liked this article, support the creator with a tip!

Sending tips requires user registration.Find details about tips here.

Search by Article Tags

Monthly Archive

Search by Exclusive Perks

Search Articles