【解決済】メニューコマンドのカーソル移動

KTY
記事: 12
登録日時: 2021年8月21日(土) 16:41

【解決済】メニューコマンドのカーソル移動

投稿記事by KTY » 2023年9月01日(金) 23:07

久しぶりに書き込ませていただきます。
今回は「コマンド選択時におけるカーソル移動」のスクリプトについてお尋ねしたいことがあり訪れました。

<質問概要>
デフォでは1列に表示に表示される「メニューコマンド」を2列にしたい。
カーソル移動で縦横の頭とお尻でループするようにしたい。
奇数個ではなるが偶数個ではできない。何故?

<質問内容>
概要で述べてますが「メニューコマンド」をいじるのが目的です。
下記のとおり“Window_MenuCommand”の『桁数の取得』を追加して2列にすることは成功しました。

続いて“Window_Selectable”にて手を加えたところとりあえず“ほぼ”思い通りの挙動が実装できました。
しかし、選択するコマンドの数が奇数の場合は問題ないのですが、偶数だと2列目にカーソルが行かない不具合が起きてしまいます。

この理由が知りたくお邪魔しました。


具体的に示すなら

AA BB
CC DD
EE

とあるとします。「AA」などはコマンド名です。

この場合はAAから「→」を押すと「BB→CC→DD→EE」と動き、AAに戻ります。逆(←)は「AA→EE→DD」と問題なく動きます。
「↓」を押下すると「CC→EE→BB→DD」となり、「↑」の入力で「DD→BB→EE」です。

これは思い通りなので何も問題はないのですが、これを偶数にすると

AA BB
CC DD

ですね。この形にすると、縦回転が「AA→CC」「BB→DD」間でしか行われないのです。言い換えるなら「AA→DD」「BB→CC」間が連動(?)していない状態です。

何故このようになるのか、理解が足りず悩んでおります。どなたかお手すきでしたら易しく、できれば優しくご教示いただけますと幸いです。

尚、データ的に足りない場合は仰っていただければUP 致しますので、申付けください。

宜しくお願い致します。

<変更箇所>
■ Window_Selectable(再定義)
● カーソルを下に移動
def cursor_down(wrap = false)
if index < item_max - col_max || (wrap && col_max == 2) ※1を2に変更
select((index + col_max) % item_max)
end
end
● カーソルを上に移動
def cursor_up(wrap = false)
if index >= col_max || (wrap && col_max == 2) ※1を2に変更
select((index - col_max + item_max) % item_max)
end
end
● カーソルを右に移動
def cursor_right(wrap = false) ※↓の「item_max - 1」をitem_maxに変更
if col_max >= 2 && (index < item_max || (wrap && horizontal?))
select((index + 1) % item_max)
end
end
● カーソルを左に移動
def cursor_left(wrap = false)
if col_max >= 2 && (index > -1 || (wrap && horizontal?))※0を-1に変更
select((index - 1 + item_max) % item_max)
end
end
end

■ Window_MenuCommand(再定義)
● ウィンドウ幅の取得
def window_width(再定義)
return 260 #160から変更
end
● ウィンドウ高さの取得(追加定義)※項目量からこのサイズにしました。
def window_height
return 135
● 表示行数の取得(追加定義)※はじめは1列分が半分になるので、これでピッタリサイズになりました。
def visible_line_number
item_max#/2#/2を追加
end
● 項目数の取得(追加定義)
def item_max
@list.size
end
● 桁数の取得(追加定義)
def col_max
return 2
end

<9/2追記>
使用ソフトはACEです。書きそびれていたので追記します。ごめんなさい。
最後に編集したユーザー KTY on 2023年9月03日(日) 14:18 [ 編集 3 回目 ]

名無し蛙
記事: 304
登録日時: 2015年11月23日(月) 02:46

Re: メニューコマンドのカーソル移動

投稿記事by 名無し蛙 » 2023年9月02日(土) 07:38

KTY さんが書きました:とあるとします。「AA」などはコマンド名です。

この場合はAAから「→」を押すと「BB→CC→DD→EE」と動き、AAに戻ります。逆(←)は「AA→EE→DD」と問題なく動きます。
「↓」を押下すると「CC→EE→BB→DD」となり、「↑」の入力で「DD→BB→EE」です。

これは思い通りなので何も問題はないのですが、これを偶数にすると

自分にはこちらの挙動の方がおかしいと思いますね
horizontal?にfalseが返るので奇数、偶数どちらもEE⇔AA間は繋がらないはずです。

そもそも論としてWindow_SelectableやWindow_Commandは
基底クラスとして用意されたものなので基本的に直接改造する機会は少ないはずです。
普通は派生クラスを作り、必要とあらば既存の処理の改変はオーバーライドで対応します。
半端な改造をした影響で妙な不具合が発生している気がしますね。

どうしても繋げたいなら

コード: 全て選択

def horizontal?
  true
end

とか書いておけば良いのでは。
KTY
記事: 12
登録日時: 2021年8月21日(土) 16:41

Re: メニューコマンドのカーソル移動

投稿記事by KTY » 2023年9月02日(土) 20:18

名無し蛙 さま、ご回答ありがとうございます。

>奇数、偶数どちらもEE⇔AA間は繋がらないはずです。
は横回転(?)の話ですね?

確かにデフォだと「→(←)キー」で“EE⇔AA間は繋がらない”です。
ただ、「BB⇔CC」は移動しますね。

そして、縦回転(↑↓キー)ではEE⇔AAはループしてクルクル回ります。
ごめんなさい↑は一部変更を直してないからでした。デフォだと縦もループしなかったですね。ごめんなさい。
AAからDDに移りませんし、EEからBBにも動きません。

以上のことを踏まえると、質問の仕方を変えた方がよさそうですね。

<質問>
横選択(「→」「←」キー)では1列目(AA・CC(・EE))と2列目(BB・DD)間でジグザグにカーソルが動いてくれるのに、縦方向(「↑」「↓」キー) では各列で独立してしまうのは仕様でしょうか?
ご指摘された「悪改造」において奇数個の場合、1列目と2列目が縦方向にリンクする(BBとEEが繋がる)のは所謂“バグ”的な挙動ですか?
「縦」も「横」と同様の動き方(2つの列を行き来する)にすることは可能ですか?

の方がより適切な訊き方だったかもしれませんね。

因みにEE⇔AAは繋がんないなら繋がらんでもいいです(笑)どちらかといえば「1列目と2列目が縦方向にリンク」ことが優先です。「AA↓CC↓BB↓DD↓AA」が理想ですね。

できればメニューコマンドを2列にしてる関係上、偶数個のほうがすっきりして見た目美しいとおもうのでデザイン的にそうしたいだけなので、無理ならしょうがないです。



>そもそも論として~
この件は、まぁその通りですね。因みにそのままダイレクトに改造せずに引っこ抜いて「別枠」で変更するようにはしています(戻せんくなる)。

この辺は余談になりますが、メニューコマンド自体も、簡易ステ画面を消してコマンド部分だけ表示するようにしていたりと変えていているのですよ。
で、コマンド「パーティ」を押すと元のコマンド画面っぽいのが出る・・・みたいな仕様にしています。

この際にWindow_Commandの下層にWindow_Command2を使って実装したので、この要領で改造しろってことですよね。

話は逸れましたが、カーソル移動の仕組みについて、教えていただければありがたいです。

あ、今更な報告ですが、ACE使用中ということを書きそびれていました。申し訳ありません。
KTY
記事: 12
登録日時: 2021年8月21日(土) 16:41

Re: メニューコマンドのカーソル移動

投稿記事by KTY » 2023年9月02日(土) 20:55

名無し蛙 さま

コード: 全て選択

def horizontal?
  true
end


の入力確認しました。確かにデフォのまま横回転ではコマンドすべてループで回れました。
ありがとうございます。

あとはこの動きを上記したように縦でもしたいのが目的にございます。

よろしくお願いいたします。
名無し蛙
記事: 304
登録日時: 2015年11月23日(月) 02:46

Re: メニューコマンドのカーソル移動

投稿記事by 名無し蛙 » 2023年9月02日(土) 21:07

KTY さんが書きました:<質問>
横選択(「→」「←」キー)では1列目(AA・CC(・EE))と2列目(BB・DD)間でジグザグにカーソルが動いてくれるのに、縦方向(「↑」「↓」キー) では各列で独立してしまうのは仕様でしょうか?
ご指摘された「悪改造」において奇数個の場合、1列目と2列目が縦方向にリンクする(BBとEEが繋がる)のは所謂“バグ”的な挙動ですか?
「縦」も「横」と同様の動き方(2つの列を行き来する)にすることは可能ですか?

仕様かと言われたら仕様でしょうね。要素数は
0 1 2 3 4
5 6 7 8 9
10 11 12 13 14
15 16 17 18 19
20 21 22 23 24
と左から右へ、端まで行ったら下へと割り振られていて
左右キーはindexを1ずつ上下させて
上下キーは桁数分だけ上下させるという仕組みです。
そして横1行のみ、あるいは縦1列の時のみ例外的に0と末尾をループさせる仕様です。
ジグザグ移動が不自然に見えるかもしれませんが
右端から左端に移る時も単純にインクリメント・デクリメントしているだけなのでおかしな挙動ではないです。

横一行のみという条件(horizontal?チェック)を取っ払ったように
縦一列のみという条件(col_max == 1)も取っ払ってしまえば良いのでは。
3桁以上だと不自然な挙動に見えますけど2桁なら許容範囲かな、と。
純粋な縦ループ、横ループにしたいのならちょっと計算式を考え直さないといけませんね。
KTY
記事: 12
登録日時: 2021年8月21日(土) 16:41

Re: メニューコマンドのカーソル移動

投稿記事by KTY » 2023年9月02日(土) 22:44

名無し蛙 さま、お付き合いありがとうございます。

>要素数は~
う~ん・・・わかったようなわからんような^^;
とりあえず、うっすらとは理解できました。ありがとうございます。

>縦一列のみという条件(col_max == 1)も取っ払ってしまえば良いのでは。


コード: 全て選択

if index < item_max - col_max || (wrap && col_max == 2) ※1を2に変更


の箇所でしょうか?

この改変だとたしかに2列に動くのですが、元々の質問どおり「奇数では動くけど偶数はならない」現象となります。

AAからEEへ移りBBに行く、は可能なのに一つ減らしてCCではBBに行かないというのは、4つで書いているので横向きで代用しても変わらない感じですが

AA BB
CC DD
EE FF
GG

の場合だと、GGはBBに行くけど、GGを消してFFまでの6個とするとEEからBBに行かない、という話ですね。

この偶数と奇数の挙動の違いがよくわかりません。
偶数は1列目・2列目それぞれがループするのでご説明通りなので納得できました。
奇数はGGが末尾だからBBに飛ぶ感じでしょうか?EEはお尻じゃないので飛ばない、と。
でもなんでBB?FFは→入力でGGだけど、↓だとAAになるし。

このミスマッチ(?)は仕方ないことで、列別ループが落としどころですかね~。

>ちょっと計算式を考え直さないといけませんね。
でなんとかなるものですか?

よろしくお願いいたします。
アバター
工作員X
記事: 15
登録日時: 2023年9月02日(土) 13:46
連絡を取る:

Re: メニューコマンドのカーソル移動

投稿記事by 工作員X » 2023年9月02日(土) 22:45

横からの回答で失礼します。
縦もジグザグとした動きにするなら
以下のようなコードでどうでしょうか?
(一応、3桁以上のコマンドに対応)

コード: 全て選択

class Window_MenuCommand < Window_Command
  def col_max
    2
  end

  def horizontal?
    true
  end

  def cursor_down(wrap = false)
    if (index + col_max) >= item_max
      target_index = (index + 1) % col_max
      select(target_index) if target_index > 0 || wrap
    else
      select(index + col_max)
    end
  end

  def cursor_up(wrap = false)
    m = row_max * col_max
    if (index - col_max) < 0
      return unless index > 0 || wrap
      max_index = item_max - 1
      target_index = m - col_max + ((index - 1) % col_max)
      target_index -= col_max while target_index > max_index
      select(target_index)
    else
      select(index - col_max)
    end
  end
end


縦横ともに列を跨がずにループさせるなら、また以下のような感じでしょうか
(かなり適当ですが…)

コード: 全て選択

class Window_MenuCommand < Window_Command
  def col_max
    2
  end

  def horizontal?
    true
  end

  def cursor_down(wrap = false)
    if (index + col_max) >= item_max
      select(index % col_max) if wrap
    else
      select(index + col_max)
    end
  end

  def cursor_up(wrap = false)
    m = row_max * col_max
    if (index - col_max) < 0
      max_index = item_max - 1
      target_index = m - (col_max - index)
      target_index -= col_max while target_index > max_index
      select(target_index) if wrap
    else
      select(index - col_max)
    end
  end

  def cursor_left(wrap = false)
    if index % col_max == 0
      if wrap
        target_index = index + col_max - 1
        target_index = item_max - 1 if target_index > item_max - 1
        select(target_index)
      end
    else
      select(index - 1)
    end
  end

  def cursor_right(wrap = true)
    target_index = index + 1
    if target_index % col_max == 0
      select(index - (index % col_max)) if wrap
    else
      target_index = item_max - 1 if target_index > item_max - 1
      select(target_index)
    end
  end
end


追記: うっかりコードにhorizontal?の再定義も混ぜたままでした…
計算式では使ってないので、消してもらって大丈夫です。
スパイじゃなくてクラフターだっ!
RGSS3の素材とか作ってます。
https://under-overworld.hatenablog.jp/entry/rgss3-material
KTY
記事: 12
登録日時: 2021年8月21日(土) 16:41

Re: メニューコマンドのカーソル移動

投稿記事by KTY » 2023年9月02日(土) 23:16

工作員Xさま、ご回答ありがとうございます。

>縦もジグザグとした動きにする
スクリプト、理想の動きです。ありがとうございます。

もしよろしければ、内容を解説していただくと勉強になるのでありがたいです。

コード: 全て選択

  def col_max
    2
  end

  def horizontal?
    true
  end


はうっすらわかります。

お手数をおかけしますがよろしくお願いします。
KTY
記事: 12
登録日時: 2021年8月21日(土) 16:41

Re: メニューコマンドのカーソル移動

投稿記事by KTY » 2023年9月02日(土) 23:21

名無し蛙 さま

とりあえず、挙動のほうはなんとかなりました。
いろいろ教えていただき大変勉強になりました。
ありがとうございます。

また何かありましたらよろしくお願いします。
良いツクールライフを!
アバター
工作員X
記事: 15
登録日時: 2023年9月02日(土) 13:46
連絡を取る:

Re: メニューコマンドのカーソル移動

投稿記事by 工作員X » 2023年9月03日(日) 06:10

先ほど掲載したコードですが不具合がありました。
桁数よりコマンド全体の数が少ないと、存在しないコマンドにカーソルが当たってしまっていました。
また、計算式もいまいち非効率だったので微修正しました。
必要があれば、以下をお使いください。(とりあえず、縦ジグザグの方のみ修正)

コード: 全て選択

class Window_MenuCommand < Window_Command
  def col_max
    2
  end

  def cursor_down(wrap = false)
    if (index + col_max) >= item_max
      target_index = (index + 1) % col_max
      target_index = 0 unless target_index < item_max
      select(target_index) if target_index > 0 || wrap
    else
      select(index + col_max)
    end
  end

  def cursor_up(wrap = false)
    if (index - col_max) < 0
      return unless index > 0 || wrap
      max_index = item_max - 1
      target_index = max_index - (col_max - index + (item_max % col_max)) % col_max
      target_index = max_index if target_index < 0
      select(target_index)
    else
      select(index - col_max)
    end
  end
end


仕組みの解説をと頼まれたので…縦ジグザグの方を解説します。

・col_max

Window_Selectableの派生クラスは、
このメソッドを元にコマンドの桁数(横方向の数)を取得しています。
今回は横2列のコマンドにしたいということで、定数の2を置き返すようにしています。


・cursor_down

カーソルを下へ動かす処理です。
まず前提として、複数桁に渡って並んでいるコマンドは、
以下のような番号が割り当てられています。(仮に要素を5個とします)

0 1
2 3
4

仮に0番のコマンドが選択されているとします。
その状態からカーソルが下方向へ行くということは、2番のコマンドが選択されることになります。
これを計算式で表すと

index + col_max

となります(indexは現在選択しているコマンドの番号、col_maxはコマンドの桁数)
一行に桁数の分のコマンドが並んでいるので、その分をそのまま足す感じです。

しかし、これを3番, 4番のコマンドで同じことをすると
5番, 6番と、存在しないコマンドを選択してしまうので、
index + col_maxが現在の要素数item_max以上だった場合
(条件分岐 → if (index + col_max) >= item_max )

(index + 1) % col_max

という計算をします。
%は割り算の余りであり、コマンドの番号をコマンドの桁数で割った余りを求めることで、
そのコマンドと同じ桁の、一番上のコマンドの番号を知ることが出来ます。

例えば4番のコマンドは0番の下の方に存在するコマンドです。これを
4 % 2 #=> 0
という計算式で表せます。

ジグザグにカーソルが動くとするなら、4番から一つ右桁のコマンドで、一番上列のコマンド、
つまりは1番のコマンドに移動させたい訳です。
そこで、4番の右に5番のコマンドが存在するかのように仮定して、その一番上にあるコマンドを計算します。

(index + 1) % col_max # (4 + 1) % 2 #=> 1

このようにして、4番のコマンドから下へ移動するなら1番のコマンド、ということを導き出しています。


target_index = 0 unless target_index < item_max

次のこの一行は、移動先のコマンド番号が要素数以上だった場合、
移動先を0番に設定し、コマンドの桁数を無視して先にループさせます。
item_max が col_max より小さい場合、存在しないコマンドを選択してしようとする場合があるため、
このようなコードが必要になります。(item_max >= col_maxが保証されていれば必要ない)


select(target_index) if target_index > 0 || wrap

この一行についている条件式は、↓キーを押しっぱなしにしている時に
無限にカーソル移動せず、0番のコマンドで一時停止するための機構です。
↓キーを押しなおした時はwrapが真になり、再びカーソルが動き出す仕様になっています。
(以降wrapフラグが噛んでいる場合はその機構なんだなーと思ってもらって大丈夫です。)


・cursor_up

カーソルを上へ動かす処理です。
まず、以下のようなコマンドの並びを考えてみます。

0 1
2 3
4

まず2番のコマンドが選択されているとしましょう。
そこから上へカーソルが行くということは、0番のコマンドが選択されるということになります。
つまりは現在選択しているコマンドの番号index、コマンドの桁数col_maxから計算して、

index - col_max

で移動するコマンドの番号が求められます。

しかし同じように、0番のコマンドなどから桁数を引いてしまうと、
-2番という存在しないコマンドを選択しようとしてしまいます。

なので条件分岐(if (index - col_max) < 0)を挟み、以下の計算をします。

max_index = item_max - 1

max_indexは要素数item_maxから1を引くことで
選択できるコマンドの番号の最大を求めています。

そして更に以下の計算をします。

(col_max - index + item_max % col_max) % col_max

少々複雑ですが…まずこの複雑な計算がなんのために必要か説明します
まず、以下のような偶数個のコマンドの並びを考えてみます

0 1
2 3
4 5

この場合、0番のコマンドから上を押した場合は、5番のコマンドへ、
1番のコマンドから上を押した場合は、4番のコマンドへ移動したい訳です。
そこで以下のような計算式が考えられます。

max_index - index

この計算式はコマンドに割り当てられる番号の最大値、max_indexから、
更にindexを引きます。これがうまい事(-index)と(max_index)とで対応して、
max_index = item_index - 1 #=> 5
indexが1の場合 5 - 1 #=> 4
indexが0の場合 5 - 0 #=> 5
と、ちゃんとなります。

しかし、奇数個のコマンドだった場合はどうなるかというと…

0 1
2 3
4

max_index = item_index - 1 #=> 4
indexが1の場合 4 - 1 #=> 3
indexが0の場合 4 - 0 #=> 4
と、縦方向に「真っすぐ」ループしてしまいます。本来であれば
0番→3番
1番→4番
としたい所です。

ここでよく見てみると、
「0→3、1→4」が目的なのに対して
「0→4、1→3」と、ちょうど入れ替わっていることに気が付くと思います。

このコマンドのはみ出し具合(桁数によっては何個もはみ出る)によって補正を掛けているのがさっきの式です。

(col_max - index + item_max % col_max) % col_max

まず()内のitem_max % col_maxで、コマンドが何個はみ出しているのか取得しています。
col_max - indexでは、「はみ出すと入れ替わる」現象に対応するべく
予めindexの値を反転させている、といった所でしょうか。
これをすることでindexが低い程、反転して高い値になり、indexが高い程、反転して低い値になります。
そして最後に、これらを足し合わせて、再度col_maxで割った余りを求めます。
そうすることで、はみ出したコマンドの数に連動して、元の(-index)が「回転」するイメージでしょうか。
(正直自分でもよく理解せず式を作ってるので、うまく説明できないのが申し訳ないですが…)

そして最終的に、max_indexから、上の式で求められた値を引いた値、
これが↑キーを押し、縦にループする際の、コマンドの番号になります。

target_index = max_index - (col_max - index + item_max % col_max) % col_max


次のこの行は、計算で求めた番号が小さすぎる場合に修正するコードです。

target_index = max_index if target_index < 0

こちらも(item_max < col_max)の時にのみ必要な部分です。


以上が解説になります。冗長で分かりにくい部分も多かったと思います、申し訳ないです。
他にもまだ質問等あれば受け付けます。最後までお読みいただきありがとうございました。
スパイじゃなくてクラフターだっ!
RGSS3の素材とか作ってます。
https://under-overworld.hatenablog.jp/entry/rgss3-material

“VX / Ace:質問” へ戻る