バトル中に使う自作QTEの最適化について

Arkroyal
記事: 80
登録日時: 2021年1月06日(水) 10:41

バトル中に使う自作QTEの最適化について

投稿記事 by Arkroyal »

一人ではどうしても解決することができず、皆様のアドバイスをもらおうと質問いたします。

現在私が作ろうとしているのは矢印系のQTEで、



①敵がスキルを使うと矢印QTEを呼び出すと同時に、一定フレームごとにダメージを受ける

②矢印QTEはトリガーなしのコモンイベント

③「一定フレームごとにダメージを受ける」処理は並列処理で行う(“砂川赳”様のNRP_BattleParallelCommonスクリプトを使っております。)

④結果によって成功・失敗のコモンイベントに連携、③はスイッチを条件にイベントの中断処理



このような段階で処理しております。

問題はこのQTEを開始すると1‐3秒の間フレームがガタ落ちし、その後も頻繫に50フレームくらいに低下して戻っることを繰り返しています。

前に作った連打QTEの場合、並列処理を3‐4個を同時に行ったせいで一部の環境においてフレームの低下があったのを反省してできるだけ並列処理の数を減らしたんですが、1個しか使ってないのにどうしてフレームがこんなに不安定なのかがわかりません。

イメージのせいかなと思ってバトルイベントが始まる際にImageManager.loadPictureで必要なイメージをキャッシュしてみましたが効果はありませんでした。

なら②か③の構図に根本的に問題がある模様ですが、一人でいろいろいじってみても到底分かることができませんでした……。

何卒よろしくお願いいたします。




②の処理は以下となります。

コード: 全て選択

◆注釈:6個の矢印をランダムに設定
◆ラベル:矢印設定
◆条件分岐:スクリプト:$gameVariables.value(975).length === 6
  ◆ラベルジャンプ:矢印出力
  ◆
:それ以外のとき
  ◆スクリプト:var ArrowQTE = [];
  :     :var ArrowList = new Array('up', 'down', 'left', 'right');
  :     :function randomItem(a) {
  :     :return a[Math.floor(Math.random() * a.length)];
  :     :};
  :     :for (var i = 0; i < 6; i++) {
  :     :$gameVariables.setValue(974, randomItem(ArrowList));
  :     :console.log($gameVariables.value(974));
  :     :ArrowQTE[i] = $gameVariables.value(974);
  :     :}
  :     :$gameVariables.setValue(975, ArrowQTE);
  ◆
:分岐終了
◆注釈:ランダムに選ばれた矢印を絵にして画面上に出力
◆ラベル:矢印出力
◆ループ
  ◆条件分岐:Arrow QTE 出力番号 = 6
    ◆スクリプト:console.log('401switch')
    ◆スイッチの操作:#0401 Arrow QTE ON = ON
    ◆ラベルジャンプ:入力待ち
    ◆
  :それ以外のとき
    ◆スクリプト:if ($gameVariables.value(975)[$gameVariables.value(976)] === 'left') {
    :     :    $gameScreen.showPicture(21+$gameVariables.value(976),"qte_left",1,(340+(120*$gameVariables.value(976))),360,100,100,255,0) }
    :     :if ($gameVariables.value(975)[$gameVariables.value(976)] === 'right') {
    :     :    $gameScreen.showPicture(21+$gameVariables.value(976),"qte_right",1,(340+(120*$gameVariables.value(976))),360,100,100,255,0) }
    :     :if ($gameVariables.value(975)[$gameVariables.value(976)] === 'up') {
    :     :    $gameScreen.showPicture(21+$gameVariables.value(976),"qte_up",1,(340+(120*$gameVariables.value(976))),360,100,100,255,0) }
    :     :if ($gameVariables.value(975)[$gameVariables.value(976)] === 'down') {
    :     :    $gameScreen.showPicture(21+$gameVariables.value(976),"qte_down",1,(340+(120*$gameVariables.value(976))),360,100,100,255,0) }
    ◆変数の操作:#0976 Arrow QTE 出力番号 += 1
    ◆
  :分岐終了
  ◆
:以上繰り返し
◆注釈:プレイヤーの矢印キー入力を受け、判定
◆ラベル:入力待ち
◆条件分岐:Arrow QTE ONがON
  ◆ラベル:QTE
  ◆注釈:脱出成功
  ◆条件分岐:Arrow QTE 現在成功回数 = Arrow QTE 必要成功回数
    ◆イベント処理の中断
    ◆
  :それ以外のとき
    ◆注釈:6個全部正しく入力した場合
    ◆条件分岐:Arrow QTE 入力番号 = 6
      ◆変数の操作:#0979 Arrow QTE 現在成功回数 += 1
      ◆注釈:脱出に成功した時のちらつき防止用
      ◆条件分岐:Arrow QTE 現在成功回数 = Arrow QTE 必要成功回数
        ◆ラベルジャンプ:QTE
        ◆
      :それ以外のとき
        ◆SEの演奏:TKpageturn1 (100, 100, 0)
        ◆変数の操作:#0975 Arrow QTE リスト = 0
        ◆変数の操作:#0976 Arrow QTE 出力番号 = 0
        ◆変数の操作:#0977 Arrow QTE 入力番号 = 0
        ◆ラベルジャンプ:矢印設定
        ◆
      :分岐終了
      ◆
    :それ以外のとき
      ◆スクリプト:console.log('loopstart')
      ◆条件分岐:スクリプト:Input.isTriggered('left')
        ◆条件分岐:スクリプト:$gameVariables.value(975)[$gameVariables.value(977)] === 'left'
          ◆SEの演奏:Decision2 (80, 100, 0)
          ◆スクリプト:$gameScreen.showPicture(21+$gameVariables.value(977),"qte_left_done",1,(340+(120*$gameVariables.value(977))),360,100,100,255,0)
          ◆変数の操作:#0977 Arrow QTE 入力番号 += 1
          ◆ラベルジャンプ:QTE
          ◆
        :それ以外のとき
          ◆注釈:入力をミスした時の処理
          ◆SEの演奏:Damage3 (80, 100, 0)
          ◆変数の操作:#0975 Arrow QTE リスト = 0
          ◆変数の操作:#0976 Arrow QTE 出力番号 = 0
          ◆変数の操作:#0977 Arrow QTE 入力番号 = 0
          ◆変数の操作:#0980 Arrow QTE 現在失敗回数 += 1
          ◆ラベルジャンプ:矢印設定
          ◆
        :分岐終了
        ◆
      :分岐終了
      ◆条件分岐:スクリプト:Input.isTriggered('right')
        ◆条件分岐:スクリプト:$gameVariables.value(975)[$gameVariables.value(977)] === 'right'
          ◆SEの演奏:Decision2 (80, 100, 0)
          ◆スクリプト:$gameScreen.showPicture(21+$gameVariables.value(977),"qte_right_done",1,(340+(120*$gameVariables.value(977))),360,100,100,255,0)
          ◆変数の操作:#0977 Arrow QTE 入力番号 += 1
          ◆ラベルジャンプ:QTE
          ◆
        :それ以外のとき
          ◆SEの演奏:Damage3 (80, 100, 0)
          ◆変数の操作:#0975 Arrow QTE リスト = 0
          ◆変数の操作:#0976 Arrow QTE 出力番号 = 0
          ◆変数の操作:#0977 Arrow QTE 入力番号 = 0
          ◆変数の操作:#0980 Arrow QTE 現在失敗回数 += 1
          ◆ラベルジャンプ:矢印設定
          ◆
        :分岐終了
        ◆
      :分岐終了
      ◆条件分岐:スクリプト:Input.isTriggered('up')
        ◆条件分岐:スクリプト:$gameVariables.value(975)[$gameVariables.value(977)] === 'up'
          ◆SEの演奏:Decision2 (80, 100, 0)
          ◆スクリプト:$gameScreen.showPicture(21+$gameVariables.value(977),"qte_up_done",1,(340+(120*$gameVariables.value(977))),360,100,100,255,0)
          ◆変数の操作:#0977 Arrow QTE 入力番号 += 1
          ◆ラベルジャンプ:QTE
          ◆
        :それ以外のとき
          ◆SEの演奏:Damage3 (80, 100, 0)
          ◆変数の操作:#0975 Arrow QTE リスト = 0
          ◆変数の操作:#0976 Arrow QTE 出力番号 = 0
          ◆変数の操作:#0977 Arrow QTE 入力番号 = 0
          ◆変数の操作:#0980 Arrow QTE 現在失敗回数 += 1
          ◆ラベルジャンプ:矢印設定
          ◆
        :分岐終了
        ◆
      :分岐終了
      ◆条件分岐:スクリプト:Input.isTriggered('down')
        ◆条件分岐:スクリプト:$gameVariables.value(975)[$gameVariables.value(977)] === 'down'
          ◆SEの演奏:Decision2 (80, 100, 0)
          ◆スクリプト:$gameScreen.showPicture(21+$gameVariables.value(977),"qte_down_done",1,(340+(120*$gameVariables.value(977))),360,100,100,255,0)
          ◆変数の操作:#0977 Arrow QTE 入力番号 += 1
          ◆ラベルジャンプ:QTE
          ◆
        :それ以外のとき
          ◆SEの演奏:Damage3 (80, 100, 0)
          ◆変数の操作:#0975 Arrow QTE リスト = 0
          ◆変数の操作:#0976 Arrow QTE 出力番号 = 0
          ◆変数の操作:#0977 Arrow QTE 入力番号 = 0
          ◆変数の操作:#0980 Arrow QTE 現在失敗回数 += 1
          ◆ラベルジャンプ:矢印設定
          ◆
        :分岐終了
        ◆
      :分岐終了
      ◆注釈:フリーズ防止用
      ◆ラベルジャンプ:QTE
      ◆
    :分岐終了
    ◆
  :分岐終了
  ◆
:分岐終了
アバター
WTR
記事: 625
登録日時: 2015年12月22日(火) 19:14

Re: バトル中に使う自作QTEの最適化について

投稿記事 by WTR »

わかったわけではないのですが
②のイベントが呼び出されるタイミング・頻度が気になりますね。
トリガーなしということは別のイベントが明示的に呼び出しているはずですが
毎フレーム呼び出されていたりすれば、まぁ重くもなるだろうなぁという規模のイベントですね。

②のイベントの先頭にでもデバッグメッセージを仕込んで( console.log("test") とかでいいので
頻度を確認してみては。

あとは…
スクリプトも使った結構複雑な構造なのでこのイベントの可読性がデバッグの肝だと思います。
できる限りコンパクトに仕上げたいところですね。
以下、何も実験せずに書いているので間違いがあるかもしれませんが
切り詰められそうなポイントを挙げてみます。

コード: 全て選択

var ArrowQTE = [];
var ArrowList = new Array('up', 'down', 'left', 'right');
function randomItem(a) {
return a[Math.floor(Math.random() * a.length)];
};
for (var i = 0; i < 6; i++) {
$gameVariables.setValue(974, randomItem(ArrowList));
console.log($gameVariables.value(974));
ArrowQTE[i] = $gameVariables.value(974);
}
$gameVariables.setValue(975, ArrowQTE);

コード: 全て選択

const ArrowQTE = [];
const ArrowList = new Array('up', 'down', 'left', 'right');
for (let i = 0; i < 6; i++) {
	ArrowQTE[i] = ArrowList[Math.randomInt(4)];
}
$gameVariables.setValue(975, ArrowQTE);
関数 randomItem() が定義されていますが
手狭なスクリプト欄で頑張るほどのものでもないかな、と思いました。
ツクールではMathクラスに追加されている Math.randomInt() が使えます。
あと、他で使っているなら仕方ありませんが見える範囲では変数974 は不要な気がしました。

コード: 全て選択

if ($gameVariables.value(975)[$gameVariables.value(976)] === 'left') {
    $gameScreen.showPicture(21+$gameVariables.value(976),"qte_left",1,(340+(120*$gameVariables.value(976))),360,100,100,255,0) }
if ($gameVariables.value(975)[$gameVariables.value(976)] === 'right') {
    $gameScreen.showPicture(21+$gameVariables.value(976),"qte_right",1,(340+(120*$gameVariables.value(976))),360,100,100,255,0) }
if ($gameVariables.value(975)[$gameVariables.value(976)] === 'up') {
    $gameScreen.showPicture(21+$gameVariables.value(976),"qte_up",1,(340+(120*$gameVariables.value(976))),360,100,100,255,0) }
if ($gameVariables.value(975)[$gameVariables.value(976)] === 'down') {
    $gameScreen.showPicture(21+$gameVariables.value(976),"qte_down",1,(340+(120*$gameVariables.value(976))),360,100,100,255,0) }

コード: 全て選択

const list = $gameVariables.value(975);
const index = $gameVariables.value(976);
const picId = 21 + index;
const picName = "qte_" + list[index];
$gameScreen.showPicture(picId, picName, 1, 340 + 120 * index, 360, 100, 100, 255, 0);
showPicture はファイル名さえ決めてしまえば1回書くだけで済むと思います。
$gameVariables.value(n) とか $gameScreen.showPicture とか頻繁に使うものほど長くて読みにくいのがほんとに…
ゲーム変数は set のときは仕方ないですが get のときはいったん中間変数に受け取ったほうが
結果的にスッキリ、見やすくなることが多いと思います。


あと、後半に上下左右の4入力に対してほとんど同じ処理が書かれているところ
これもまとめられそうな気がするんですが、どうだろう…

コード: 全て選択

◆条件分岐:スクリプト:Input.isTriggered("left") || Input.isTriggered("right") || Input.isTriggered("up") || Input.isTriggered("down")
  ◆条件分岐:スクリプト:Input.isTriggered($gameVariables.value(975)[$gameVariables.value(977)]);
    ◆SEの演奏:Decision2 (80, 100, 0)
    ◆スクリプト:const list = $gameVariables.value(975);
    :     :const index = $gameVariables.value(977);
    :     :const picId = 21 + index;
    :     :const picName = "qte_" + list[index] + "_done";
    :     :$gameScreen.showPicture(picId, picName, 1, 340 + 120 * index, 360, 100, 100, 255, 0)
    ◆変数の操作:#0977 Arrow QTE 入力番号 += 1
    ◆ラベルジャンプ:QTE
    ◆
  :それ以外のとき
    ◆注釈:入力をミスした時の処理
もっと言えば、ラベルで分けられている処理はそれぞれ別のコモンイベントでもいい感じですね。
[1]矢印の表示処理
[2]入力受付処理
[3]全て正解で抜ける処理
[4]正解で次に進む処理
[5]不正解で戻る処理
個人的にはこれくらいに分けたいような気がします。
[2] だけ並列処理に。

ラベルって使ったことがなくてよくわからないんですよね…
Twitter、はじめました。
https://twitter.com/wtr_in_reverie/
Arkroyal
記事: 80
登録日時: 2021年1月06日(水) 10:41

Re: バトル中に使う自作QTEの最適化について

投稿記事 by Arkroyal »

ご指導ありがとうございます……!初めて矢印を生成するときはいまだにフレームが落ちていますが、私が作ったものに比べたら何倍もマシな状態です!

一つ質問がありますが、どうして私が作ったものは段階に分割してそれぞれのコモンイベントにしてもフレームが安定しないんでしょうか?

WTR様と同じ数の段階で分割してみたものの、私が作ったものは分割前とあまり状況が変わりませんでした。

私のスクリプトが長いのが原因でしょうか?それとも私のスクリプトに何か負担を増やす処理があったのでしょうか……?
アバター
WTR
記事: 625
登録日時: 2015年12月22日(火) 19:14

Re: バトル中に使う自作QTEの最適化について

投稿記事 by WTR »

分割するのは処理を軽くするためではないですね。
分割してもすべて実行されるなら負荷は同じです。
むしろイベントを呼び出すオーバーヘッドが増える方向かもしれないです。誤差だと思いますが。

分割するメリットは
単純に短いほうが見やすい、問題の切り分けがしやすい、使いまわしできる、という点だと思います。
まぁ好みの問題かもしれません。

フレームレートが落ちるのは…
なんでしょう

ぱっと見た感じ、そんなに重そうな処理はない気がしますけど…

ちなみにスイッチ 401 の on を消してみたらどうでしょう。
それでも重いなら②のイベント内に何かあるということになると思いますが
スイッチON以降の問題かもしれないです。
Twitter、はじめました。
https://twitter.com/wtr_in_reverie/
jp_asty
記事: 81
登録日時: 2019年11月12日(火) 15:34

Re: バトル中に使う自作QTEの最適化について

投稿記事 by jp_asty »

こんにちは。

予想になってしまいますが、
冒頭の矢印をランダムに選んでいる部分のスクリプトの
$gameVariables.setValue(975, ArrowQTE);

$gameVariables._data[975] = ArrowQTE;
に変更したら改善されないでしょうか。

ツクールでは変数やスイッチの値をsetValueを通して更新した場合、そのマップのイベント全てに対して更新のチェックが入るという仕様になっており、最初の例ではsetValueが1フレームに7回呼ばれていることになると思います。
(WTRさんのスクリプトに変更されていた場合は1回)

変化なかったらすみません。
---------------------------------------------------------------------------------------------
プラグイン置き場 : https://github.com/ste0/RPG-Maker-MV-Plugins
Arkroyal
記事: 80
登録日時: 2021年1月06日(水) 10:41

Re: バトル中に使う自作QTEの最適化について

投稿記事 by Arkroyal »

401番のスイッチは入力受けコモンを並列処理させるためのスイッチですので、その変更は根本から変える必要がありますね。

実はここ最近、並列処理の数を減らそうと一つ試していたものがあります。

プレイヤーの入力をリアルタイムで監視する代わりに、矢印キーを入力する時その判定を行うコモンを実行するよう、ということです。そうした方が並列処理をせずに済むので負担も減ると思いまして。

ですが「戦闘中」でこの機能を作るところで失敗しました。マップ画面上では他の方々が作ってくださったプラグインの力を借りてどうにかできましたけど、戦闘画面では始めの部分、つまり「入力判定→コモン実行」から何も完成できず進展がありませんでした。

なので結局401番スイッチは今も使っている状態です。今のQTEイベントの流れは以下となります。

処理.png

⓪戦闘が始まるとオリジナルパラメータをゲージ化させて表示する並列コモン0は常に実行中
(Mano_Gaugeプラグインを使って表示していますが、戦闘中では並列処理にしないと変化がゲージに反映されないのでこれも並列処理にしています。)

①必要成功回数、QTEスキルの命中判定などを行うコモンをスキルを通じて実行
(敵からプレイヤーに使うスキルです。以下コモン①)

②プレイヤーが回避できなかった場合、矢印をランダムに生成するコモンを実行する
※コモン①は5ウェイトを入れてループさせる
(コモン①のスキル処理を長引いて、次のターンに進まないようにするため)

③矢印をピクチャー化するコモンを実行する
 それと同時に一定フレームごとにダメージを与える並列コモン1を始動する

④プレイヤーの入力を受けて処理する並列コモン2を始動する



つまり同時に並列処理を3個処理させている状態です。

scene_battleに並列処理を使うこと自体が問題なのでしょうか?それとも並列処理の数を減らすべきでしょうか?
Arkroyal
記事: 80
登録日時: 2021年1月06日(水) 10:41

Re: バトル中に使う自作QTEの最適化について

投稿記事 by Arkroyal »

jp_asty様、ご指導ありがとうございます!

早速比べてみます!
アバター
WTR
記事: 625
登録日時: 2015年12月22日(火) 19:14

Re: バトル中に使う自作QTEの最適化について

投稿記事 by WTR »

401番のスイッチの件は
スイッチを使わないように処理を組みなおしたら、という提案ではなく
イベントの処理がスイッチONに到達する前に問題があるのかどうか判断する実験の意味でした。
消すとたぶんそこで処理が止まっちゃうと思いますがそれで問題なくて
スイッチONされなくても数秒FPSがガターッと落ちる現象は起こるのだろうか?を知りたかっただけです。
Twitter、はじめました。
https://twitter.com/wtr_in_reverie/
Arkroyal
記事: 80
登録日時: 2021年1月06日(水) 10:41

Re: バトル中に使う自作QTEの最適化について

投稿記事 by Arkroyal »

401スイッチをOFFにしてやってみたところ、一定ターンごとにダメージを与える処理だけでもフレームが5くらい落ちるのを確認しました。

しかしこのコマンドは三つの条件分岐で、パラメータの状態に合わせて効果音を再生し、ダメージの処理をするだけですが……。

コード: 全て選択

◆注釈:ゲージの残量によって効果音と速度、減少量を設定
◆条件分岐:スクリプト:$gameActors.actor(1)._aop[0] > 300
  ◆SEの演奏:slow1 (100, 100, 0)
  ◆スクリプト:$gameActors.actor(1)._aop[0] -= 25;
  :     :BattleManager._statusWindow.refresh()
  ◆画面のシェイク:3, 7, 30フレーム
  ◆画面のフラッシュ:(170,51,170,51), 30フレーム
  ◆ウェイト:90フレーム
  ◆
◆条件分岐:スクリプト:$gameActors.actor(1)._aop[0] < 101
  ◆SEの演奏:fast1 (100, 100, 0)
  ◆スクリプト:$gameActors.actor(1)._aop[0] -= 10;
  :     :BattleManager._statusWindow.refresh()
  ◆画面のシェイク:3, 7, 30フレーム
  ◆画面のフラッシュ:(170,51,170,102), 30フレーム
  ◆ウェイト:30フレーム
  ◆
:分岐終了
◆条件分岐:スクリプト:$gameActors.actor(1)._aop[0] > 100 && $gameActors.actor(1)._aop[0] < 301
  ◆SEの演奏:normal1 (100, 100, 0)
  ◆スクリプト:$gameActors.actor(1)._aop[0] -= 20;
  :     :BattleManager._statusWindow.refresh()
  ◆画面のシェイク:3, 7, 30フレーム
  ◆画面のフラッシュ:(170,51,170,85), 30フレーム
  ◆ウェイト:60フレーム
  ◆
:分岐終了

本来はmath.randomを使って条件分岐でそれぞれの状況にいくつかの効果音を再生するようにしましたけど、これもフレーム低下の原因かなと思って消してみたところあまり効果はありませんでした。
アバター
くろうど
記事: 318
登録日時: 2016年1月22日(金) 20:52
お住まい: 東京都
連絡する:

Re: バトル中に使う自作QTEの最適化について

投稿記事 by くろうど »

こんばんは。
私の知ってる限りでは、
BattleManager._statusWindow.refresh()
という処理はかなり重い処理です。

詳しくは、以下の公式フォーラム参照なのですが、
参照先の件では、refreshが複数回呼ばれている事が重くなる原因なので、
1回であれば影響は少ないかもしれません。

本件の全体は見れてないですが、
BattleManager._statusWindow.refresh()
の回数を確認してみてはいかがでしょうか?

↓公式フォーラムの該当情報(私が書いたんですけど)
https://forum.tkool.jp/index.php?thread ... 1%9F.3476/

よろしくお願いします。
▼だいたいTwitterにいます。たぶん。
https://twitter.com/kuroudo119
返信する

“MV:質問”に戻る