SVX日記

2004|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|11|12|
2022|01|02|03|04|05|06|07|08|09|10|11|12|
2023|01|02|03|04|05|06|07|08|09|10|11|12|
2024|01|02|03|04|05|06|07|08|09|10|11|12|
2025|01|02|03|04|05|06|

2025-06-11(Wed) ゲームパッドの入力処理が「理解」されてしまう

  自製のレースゲーム「TopDrivin'」の開発を地味に進めているのだが、一風変わった操作方法にするつもりだ。オイラはずっと前から、レースゲームに「アクセルボタン」って要るか? って思ってた。

  クルマの運転には、主にステアリングとペダルとギアチェンジレバーを使う。実車では手と足を使うのに、ゲームコントローラだと手だけ。足りるわけがない。だからオートマになってしまう。違うだろ。大事なのはギアチェンジだ。むしろアクセルペダルなんて基本ベタ踏みなんだからいらない。ギアチェンジで速度調整、ブレーキ(アクセルオフ)で微調整。だから、シフトダウン、ブレーキ、シフトアップの3ボタンでの操作のがベターだと思うのだ。

  で、いきなり細かい話になるが、レバー操作とブレーキ操作はレベルセンスでいいが、シフトのアップ、ダウン操作のためにはエッジセンスが必要だ。つまり、押した瞬間だけを検知する。離したこと、押し続けていること、は検知しない。実際のF1マシンのパドルシフトもそうだ。押し(引い?)たら1段階シフト。「ドゥ、ドゥ、ドゥ、ドゥーン」という連続シフトダウンの操作は、絶対に再現したい。

  キーボードの入力処理は簡単だ。キーダウンイベントの発生を検知すればいい。しかし、ゲームパッドのボタンにはダウンイベントはない。レベルを見てエッジセンスする必要がある。とりあえず、汎用性は必要ないから、処理効率よくコンパクトに書きたい。で、書いた。うまく動いた。

  書き上げたところで、ふと、AIを試してみたくなった。これ、何の処理を書いたのか、わかる? って。

以下のコードについて説明して下さい。
p_dirs = ['PLEFT', 'PRIGHT', 'PUP', 'PDOWN']; p_ndirs = p_dirs.length
p_trgs = ['PTRGA', 'PTRGB', 'PTRGC']; p_ntrgs = p_trgs.length
padsense = ->                                                   # ゲームパッドからの入力
    pads = navigator.getGamepads()
    (il = inputs['LEVEL'])['PLEFT'] = il['PUP'] = il['PRIGHT'] = il['PDOWN'] = il['PTRGA'] = il['PTRGB'] = il['PTRGC'] = false; ies = inputs['EDGE_SENSE']
    for n in [0...pads.length]
        continue unless(pad = pads[n])
        dn = 0; for an in [0..pad.axes.length - 1]
            pad.axes[an] < -0.5 and il[p_dirs[dn % p_ndirs]] = true; dn++
            pad.axes[an] >  0.5 and il[p_dirs[dn % p_ndirs]] = true; dn++
        tn = 0; for bn in [0..pad.buttons.length - 1]
            pad.buttons[bn].pressed and il[p_trgs[tn % p_ntrgs]] = true and ies[p_trgs[tn % p_ntrgs]] |= 1; tn++
    for trg in p_trgs
        (ies[trg] &= 0b11) == 0b01 and inputs['EDGE'].push(trg)
        ies[trg] <<= 1
このコードは、**ゲームパッドからの入力を取得して、入力状態やエッジ(押
された瞬間)を検出する**処理をしています。CoffeeScript風の構文で書かれ
ているため、JavaScriptに似ていますが、少し簡潔な書き方になっています。
 
### 🔧 **全体概要**
 
* ゲームパッドの入力(十字キーやボタン)を取得し、
* どの方向やボタンが押されているかを判定し、
* 「押された瞬間」(立ち上がりエッジ)を検出して `inputs['EDGE']` に追加しています。
 
### 🔹 **定義と初期化**
 
```coffeescript
p_dirs = ['PLEFT', 'PRIGHT', 'PUP', 'PDOWN']
p_ndirs = p_dirs.length
 
p_trgs = ['PTRGA', 'PTRGB', 'PTRGC']
p_ntrgs = p_trgs.length
```
 
* `p_dirs`: アナログスティックや十字キーの入力方向を定義。
* `p_trgs`: 押しボタンに対応する識別子(トリガー)。
* `p_ndirs`, `p_ntrgs`: それぞれの数。
 
### 🔹 **関数定義 `padsense`**
 
```coffeescript
padsense = ->
```
 
* ゲームパッドの状態を調べて入力情報を更新する無名関数です。
 
### 🔹 **ゲームパッドの取得と初期化**
 
```coffeescript
pads = navigator.getGamepads()
(il = inputs['LEVEL'])['PLEFT'] = il['PUP'] = il['PRIGHT'] = il['PDOWN'] = il['PTRGA'] = il['PTRGB'] = il['PTRGC'] = false
ies = inputs['EDGE_SENSE']
```
 
* `pads`: ブラウザから接続中のゲームパッド配列を取得。
* `inputs['LEVEL']`: 現在の入力状態を保持(どの方向やボタンが押されているか)。
* `inputs['EDGE_SENSE']`: 押された直後を検出するためのバッファ。
 
### 🔹 **アナログスティック/方向キーの処理**
 
```coffeescript
for n in [0...pads.length]
  continue unless(pad = pads[n])
  dn = 0
  for an in [0..pad.axes.length - 1]
    pad.axes[an] < -0.5 and il[p_dirs[dn % p_ndirs]] = true; dn++
    pad.axes[an] >  0.5 and il[p_dirs[dn % p_ndirs]] = true; dn++
```
 
* 各ゲームパッドを確認し、
* スティックの軸(axes)ごとに、
 
  * -0.5 未満 → 方向の一つ(例: 左)を押されたとみなす
  * 0.5 超 → 別の方向(例: 右)を押されたとみなす
 
※ `dn % p_ndirs` によって 4方向にうまく割り振っている。
 
### 🔹 **ボタンの処理**
 
```coffeescript
tn = 0
for bn in [0..pad.buttons.length - 1]
  pad.buttons[bn].pressed and il[p_trgs[tn % p_ntrgs]] = true and ies[p_trgs[tn % p_ntrgs]] |= 1; tn++
```
 
* 押されているボタンに対応する `PTRGA`, `PTRGB`, `PTRGC` に `true` をセット。
* 同時に `EDGE_SENSE` ビットを立ててエッジ検出に備える(OR 1)。
 
### 🔹 **エッジ検出処理**
 
```coffeescript
for trg in p_trgs
  (ies[trg] &= 0b11) == 0b01 and inputs['EDGE'].push(trg)
  ies[trg] <<= 1
```
 
* 直近2フレーム分の押下状態を 2bit で追跡。
* `0b01`(前回は押されていなかった、今回押された)=立ち上がりエッジ → `inputs['EDGE']` に追加。
* 次のチェックのために1ビット左シフト。
 
### 🧠 **補足:ビットシフトでのエッジ検出**
 
`EDGE_SENSE` を2bitとして使っており、
 
* 過去2回分の押下状態を記録:
  例)
 
  * `00`: 押されていない
  * `01`: 今回押された → エッジ(立ち上がり)
  * `11`: 押され続けている
  * `10`: 離された(エッジ検出なし)
 
### ✅ **まとめ**
 
このコードは、ゲームパッド入力を毎フレーム更新して:
 
1. スティックやボタンの現在の入力状態を `inputs['LEVEL']` に反映
2. 立ち上がりエッジ(押された瞬間)を `inputs['EDGE']` に追加
 
する仕組みです。リアルタイムなゲーム操作などに使える基本的な入力処理パターンになっています。
 
もし `inputs` の構造や、この関数が使われる文脈についても知りたい場合は、補足のコードを共有していただければ、より詳しく解説できます。

  ……おぃおぃ……完璧な説明じゃないか、これは。特に、見りゃわかるレベルセンスの処理への言及は軽く、ちょっとわかりづらいエッジセンスの処理について丁寧に正しく説明している。そもそも「エッジセンス」を正しく理解しているということ自体が驚愕だわ。

  そりゃ「EDGE_SENSE」とは書いちゃってるけど……変数名も隠しておけばよかったかなぁ……なんだかくやしい。つうか、上で「理解している」なんて書いたけど、AIに「理解」を感じてしまっている時点で負けてんじゃねぇか。だって、AIに「理解」なんて概念はないだろう?

  以前に「多項式からその回答が導き出される理由がわからない」と書いたけど、逆に大層に感じているオイラが頭の中の「理解」こそ、たかがその程度の「現象」に過ぎないのだろう。AIが数行の指示だけでそれっぽいものを作り出すという事実は、人間が虫の習性を読み解くかのように、AIが人間の習性を読み解きつつあることの証明なのかもしれない。はぁ……。