【プチコン4講座】あっちむいてホイを作ろう:操作制御編

プチコン4

プチコン4 ミニゲーム

こんにちは。継続の錬金術士なおキーヌです。

ブログ毎日更新は253日目になります。

前回はあっちむいてホイの基本的な仕組みを構築しました。

しかし1フレーム毎に処理が動いてしまうのでとてもゲームとは言えない状況です。

今のところストッパーのような役割を持っている処理がないので、
ボタンを押そうが押さまいが進み続けてしまいます。

理想の形としてはコチラが向きを選択する時に止まって、
向きを選択したらゲームが進むという感じでしょう。

今回はゲームのボタン制御を覚えてみましょう。

それではプチコンミニゲーム講座第2回目を始めようと思います。

  1. コントローラーの押し続けを防止する
  2. プレイヤーが選択するまで何もしない
  3. お互いのライフを実装してゲームらしくする
  4. 第2回のまとめと次回について

コントローラーの押し続けを防止する

現状1回押しただけで何度もボタンが押されてしまう状態になっています。

というのも1フレームずつボタンが押されているかどうかのチェックをしているので、
人間がボタンをポンと押して放す間に数フレーム、多ければ十数フレーム押している状態になるためです。

これでは選択式のゲームとか1回の選択が命取りになるゲームの場合、
ただのクソゲーとなってしまうのでボタンの制御が必要です。

仕組みだけ最初に言っておくと、そのボタンをが押されている時間をカウントする
変数を作ってそのカウント変数が1の時だけ実行する。

とすれば1回しかボタンが押されていない状態を作り出すことが出来ます。

もちろん、色んなやり方はあるのですがボタンカウント式が一番便利です。

一定時間押して力を溜める。

といったゲームを作るときにカウント数を見て条件分岐をすれば色々なことが出来ますね。

それではカウント用の変数を作りましょう。

ボタンは4つなので変数を4つ作ります。

もしここで〇〇を使えばシンプルに書けるよ!って人はそちらでも大丈夫です。

ひとまず今は変数を4つ作りましょう。

VAR A_BTN = 0, B_BTN = 0, X_BTN = 0, Y_BTN = 0

このように変数はカンマで区切れば一々VARを書かなくても宣言できます。

もちろん可読性は落ちてしまうのでプログラミングに慣れていないうちは、
出来れば1つ1つVARをした方が良いかもしれませんね。

それではこのカウント用変数を利用してボタンカウント関数を作ってみました。

これもちょっと難しいかもしれないのでそのまま書き写しでOKです。

' ボタンカウント関数
'───────────────────────────────
def D_BtnPressCount A_Button

  ' ボタンカウント分岐
  case A_Button
    ' 渡されたボタンがXだったら
    when #B_RUP
      ' ボタンカウント配列の数が256未満かどうか調べる
      if X_BTN < 256 then 
        ' 条件に一致したら+1する
        INC X_BTN
      endif

    ' 渡されたボタンがBだったら
    when #B_RDOWN
      ' ボタンカウント配列の数が256未満かどうか調べる
      if B_BTN < 256 then 
        ' 条件に一致したら+1する
        INC B_BTN
      endif

    ' 渡されたボタンがYだったら
    when #B_RLEFT
      ' ボタンカウント配列の数が256未満かどうか調べる
      if Y_BTN < 256 then 
        ' 条件に一致したら+1する
        INC Y_BTN
      endif

    ' 渡されたボタンがAだったら
    when #B_RRIGHT
      ' ボタンカウント配列の数が256未満かどうか調べる
      if A_BTN < 256 then 
        ' 条件に一致したら+1する
        INC A_BTN
      endif
  endcase
end

ここでかなり無駄な書き方してんなぁって思えたらプログラミングレベルが上がっています。

今は個別の変数で管理しているのでこの書き方になりますが、
配列という変数のセットのような機能を使えばもっとシンプルにコードが書けます。

ですが配列はちょっとクセが強いので今はまだ使いません。

覚えたらとても便利でゲーム開発には欠かせない機能なので、そのうちしっかりと説明します。

続いて、コントローラー関数にこのボタンカウント関数を組み込んでみましょう。

これも書き写しでOKです。

全て書き替えるので、前回のコントローラー関数は一回消してもらってOKです。

' コントローラー
'───────────────────────────────
def D_Controller
  ' 0番目のコントローラー(つまり1コン)の押されているボタンを取得
  VAR B = BUTTON(0)


  ' Xボタン処理
  if (B AND 1 << #B_RUP) != 0 then
    ' ボタンカウント命令に#B_RUPを渡す
    D_BtnPressCount #B_RUP
  else
    ' ボタンが離されたらカウントが1以上の時
    if X_BTN >= 1 then
      ' ボタンカウントを0にする
      X_BTN = 0
    endif
  endif

  ' Bボタン処理
  if (B AND 1 << #B_RDOWN) != 0 then
    ' ボタンカウント命令に#B_RDOWNを渡す
    D_BtnPressCount #B_RDOWN
  else
    ' ボタンが離されたらカウントが1以上の時
    if B_BTN >= 1 then
      ' ボタンカウントを0にする
      B_BTN = 0
    endif
  endif

  ' Yボタン処理
  if (B AND 1 << #B_RLEFT) != 0 then
    ' ボタンカウント命令に#B_RLEFTを渡す
    D_BtnPressCount #B_RLEFT
  else
    ' ボタンが離されたらカウントが1以上の時
    if Y_BTN >= 1 then
      ' ボタンカウントを0にする
      Y_BTN = 0
    endif
  endif


  ' Aボタン処理
  if (B AND 1 << #B_RRIGHT) != 0 then
    ' ボタンカウント命令に#B_RRIGHTを渡す
    D_BtnPressCount #B_RRIGHT
  else
    ' ボタンが離されたらカウントが1以上の時
    if A_BTN >= 1 then
      ' ボタンカウントを0にする
      A_BTN = 0
    endif
  endif

end

コメントにも書いてある通り、それぞれのボタンを監視しつつ
押されたら先ほど作ったボタンカウント関数を呼び出しています。

ボタンカウント関数を呼び出すとそれぞれ対応するボタンカウントが1つ増えます。

これだけだと今まで通りボタンが押された分カウントされ続けるのですが、
今どれだけの数そのボタンが押されているのかを調べることが可能になっていますね。

もしAボタンが1の時だけ何かをしたいとなったら、
ifでA_BTN==1で条件を作れば1回だけ動かすことが可能になっています。

ちょっと駆け足でしたが、ボタンに関しては一旦ふーん程度で覚えておいてください。

プログラミングに慣れて来たらもう一度ボタン関数をよく見てもらって

あー!そういうことだったんだ!

ってなってくれればものすごく成長しているので、
チュートリアルを完成させたら一度戻って見に来てください。

プレイヤーが選択するまで何もしない

これで選択が連打状態にならなくなったので、さっそくゲームを改良してみましょう。

とりあえず一旦Aボタンが押されるまで次に進まないという仕組みにしてみます。

全部書くと長くなるので、ループの部分だけかコードを表記します。


' ループ開始
LOOP

  ' プレイヤーのキー入力監視
  D_Controller

  ' 相手の向きをきめる
  ENEMY_DIRECT = RND(4) + 1

  ' Aボタンカウントが1の場合向きを1にする
  IF A_BTN == 1 THEN
    DIRECT = 1
  ENDIF

  ' もしプレイヤーの向きが決定されていたら
  IF DIRECT > 0 THEN
    ' もしプレイヤーの向きと相手の向きが同じなら
    IF ENEMY_DIRECT == DIRECT THEN
      ' ライフを1つ減らす
      LIFE = LIFE - 1
    ENDIF
  ENDIF

  ' もし ライフが 0 になったときはループを抜ける
  IF LIFE == 0 THEN BREAK

  ' 現在のライフの数値を表示
  PRINT "現在のテキのライフ:"+ LIFE

  ' プレイヤーの向きをリセットする
  DIRECT = 0

  ' 垂直同期
  VSYNC
ENDLOOP

まず前回と変わったのが向きの数値が0~3ではなく1~4にしました。

というのも0が未入力状態にしたかったので1から開始することにしました。

そして1回ループが終わったらプレイヤーの向きを0に戻して、
もしボタンが押し続けていてもボタンカウントが1にならないと向きが変更されないので
連続してゲームが進んでしまわないように制御することに成功しました。

ボタンカウントはボタンを放したらリセットされる仕組みなので、
ボタンは1回押しただけしか効果がないという感じになっています。

この時点でかなり運ゲーですが、前回よりゲームらしくなりました。

お互いのライフを実装してゲームらしくする

現在相手のライフしかない上に500という途方もない数値なので、
お互いのライフを実装してどちらも5にしてみましょう。

5回先取りすれば勝ちです。

' プレイヤーと相手のライフを5に設定
VAR MY_LIFE = 5,  ENEMY_LIFE = 5

これでプレイヤーと相手のライフが準備出来ました。

この変数を使って勝負が決まったときに負けたほうのライフを減らし、
両方の残りライフを表示するように改良してみましょう。

' 勝敗フラグ
VAR WIN = #FALSE

' ループ開始
LOOP

  ' プレイヤーのキー入力監視
  D_Controller

  ' 相手の向きをきめる
  ENEMY_DIRECT = RND(4) + 1

  ' Aボタンカウントが1の場合向きを1にする
  IF A_BTN == 1 THEN
    DIRECT = 1
  ENDIF

  ' もしプレイヤーの向きが決定されていたら
  IF DIRECT > 0 THEN
    ' もしプレイヤーの向きと相手の向きが同じなら
    IF ENEMY_DIRECT == DIRECT THEN
      ' 相手のライフを1つ減らす
      ENEMY_LIFE = ENEMY_LIFE - 1
    ELSE
      ' 自分のライフを1つ減らす
      MY_LIFE = MY_LIFE - 1
    ENDIF

    ' 現在のライフの数値を表示
    PRINT "----"
    PRINT "じぶんのHP:"+ MY_LIFE
    PRINT "あいてのHP:"+ ENEMY_LIFE
    PRINT "----"

  ENDIF

    ' プレイヤーの向きをリセットする
    DIRECT = 0

  ' もし 自分のライフが0になったら
  IF MY_LIFE == 0 THEN
    ' 負け情報を記憶
    WIN = #FALSE
    ' ループを強制的に抜ける
    BREAK
  ELSEIF ENEMY_LIFE == 0 THEN
    ' 勝ち情報を記憶
    WIN = #TRUE
    ' ループを強制的に抜ける
    BREAK
  ENDIF

  ' 垂直同期
  VSYNC
ENDLOOP

' 勝敗チェック
IF WIN == #TRUE THEN
  PRINT "WINNER!"
ELSE
  PRINT "GAME OVER"
ENDIF

こっちが買った時しか相手のライフは減らないのでかなり理不尽なゲームですが、
ちゃんとコチラと相手のライフの増減とライフチェックを行っています。

どちらかのライフが0になったら勝ち負けの変数を変更して、
ループを強制的に抜けて勝ち負け判定で勝ちか負けを表示します。

これで基本的なあっち向いてホイゲームの仕組みは完成しました!

現状、Aボタンしか効かないのでXBYと残りのボタンも実装すれば、
黒い画面上でのゲームは完成です。

プチコン4 出力結果その1

プチコン4 出力結果その2

第2回のまとめと次回について

あっち向いてホイゲームがほぼ完成しましたね!

あと一歩であなたもゲームクリエイターの仲間入りです!!

第1回目は基本的なゲームの仕組みを作りました。

第2回目ボタンの制御を行い、よりゲームらしくしました。

第3回目はゲームの完成を目指します。

コントローラ関数とボタンカウント関数についてはちょっと複雑でしたね。

実は過去の講座で作ったものを流用しています。

もうちょっとシンプルに書けたかもしれませんが、
時間の都合と省略しすぎると何をしているのかわかり辛くなるため
あえて冗長に書いて何をしているのかをわかりやすくしました。

しかしボタンの処理を最初に作っておかないと、
折角ゲーム機でゲームを作るのが台無しになってしまいます。

ボタンの処理が出来てしまえば、あとは考え方で色んなゲームが作れますよ。

現状相手が有利すぎるゲームになっているので、
ボタンを実装したり、お互いのターンを実装して攻守の仕組みを作りましょう。

ライフを減らせるのは攻撃のターンに同じ方向に向いたときだけにすれば、
かなりフェアなゲームになるのではないでしょうか。

とはいっても、乱数なので完全運ゲーと言えば運ゲーなんですが(笑)

最後に今回までで作ったコードをすべて載せておきます。

さらにコードの最初に画面全体をクリアする「ACLS」を入れました。

これが無いと画面に文字が残ったままになりますのでゲーム開始時は画面お掃除です。

それでは3回目でお会いしましょう。

' 全画面クリア
ACLS

' 勝敗フラグ
VAR WIN = #FALSE
' プレイヤーと相手のライフを5に設定
VAR MY_LIFE = 5,  ENEMY_LIFE = 5
' 向き変数の準備
VAR DIRECT = 0, ENEMY_DIRECT = 0
' ボタンカウント変数
VAR A_BTN = 0, B_BTN = 0, X_BTN = 0, Y_BTN = 0

' ループ開始
LOOP

  ' プレイヤーのキー入力監視
  D_Controller

  ' 相手の向きをきめる
  ENEMY_DIRECT = RND(4) + 1

  ' Aボタンカウントが1の場合向きを1にする
  IF A_BTN == 1 THEN
    DIRECT = 1
  ENDIF

  ' もしプレイヤーの向きが決定されていたら
  IF DIRECT > 0 THEN
    ' もしプレイヤーの向きと相手の向きが同じなら
    IF ENEMY_DIRECT == DIRECT THEN
      ' 相手のライフを1つ減らす
      ENEMY_LIFE = ENEMY_LIFE - 1
    ELSE
      ' 自分のライフを1つ減らす
      MY_LIFE = MY_LIFE - 1
    ENDIF

    ' 現在のライフの数値を表示
    PRINT "----"
    PRINT "じぶんのHP:"+ MY_LIFE
    PRINT "あいてのHP:"+ ENEMY_LIFE
    PRINT "----"

  ENDIF

  ' プレイヤーの向きをリセットする
  DIRECT = 0

  ' もし 自分のライフが0になったら
  IF MY_LIFE == 0 THEN
    ' 負け情報を記憶
    WIN = #FALSE
    ' ループを強制的に抜ける
    BREAK
  ELSEIF ENEMY_LIFE == 0 THEN
    ' 勝ち情報を記憶
    WIN = #TRUE
    ' ループを強制的に抜ける
    BREAK
  ENDIF

  ' 垂直同期
  VSYNC
ENDLOOP

' 勝敗チェック
IF WIN == #TRUE THEN
  PRINT "WINNER!"
ELSE
  PRINT "GAME OVER"
ENDIF


' コントローラー関数
'───────────────────────────────
def D_Controller
  ' 0番目のコントローラー(つまり1コン)の押されているボタンを取得
  VAR B = BUTTON(0)


  ' Xボタン処理
  if (B AND 1 << #B_RUP) != 0 then
    ' ボタンカウント命令に#B_RUPを渡す
    D_BtnPressCount #B_RUP
  else
    ' ボタンが離されたらカウントが1以上の時
    if X_BTN >= 1 then
      ' ボタンカウントを0にする
      X_BTN = 0
    endif
  endif

  ' Bボタン処理
  if (B AND 1 << #B_RDOWN) != 0 then
    ' ボタンカウント命令に#B_RDOWNを渡す
    D_BtnPressCount #B_RDOWN
  else
    ' ボタンが離されたらカウントが1以上の時
    if B_BTN >= 1 then
      ' ボタンカウントを0にする
      B_BTN = 0
    endif
  endif

  ' Yボタン処理
  if (B AND 1 << #B_RLEFT) != 0 then
    ' ボタンカウント命令に#B_RLEFTを渡す
    D_BtnPressCount #B_RLEFT
  else
    ' ボタンが離されたらカウントが1以上の時
    if Y_BTN >= 1 then
      ' ボタンカウントを0にする
      Y_BTN = 0
    endif
  endif


  ' Aボタン処理
  if (B AND 1 << #B_RRIGHT) != 0 then
    ' ボタンカウント命令に#B_RRIGHTを渡す
    D_BtnPressCount #B_RRIGHT
  else
    ' ボタンが離されたらカウントが1以上の時
    if A_BTN >= 1 then
      ' ボタンカウントを0にする
      A_BTN = 0
    endif
  endif

end


' ボタンカウント関数
'───────────────────────────────
def D_BtnPressCount A_Button

  ' ボタンカウント分岐
  case A_Button
    ' 渡されたボタンがXだったら
    when #B_RUP
      ' ボタンカウント配列の数が256未満かどうか調べる
      if X_BTN < 256 then 
        ' 条件に一致したら+1する
        INC X_BTN
      endif

    ' 渡されたボタンがBだったら
    when #B_RDOWN
      ' ボタンカウント配列の数が256未満かどうか調べる
      if B_BTN < 256 then 
        ' 条件に一致したら+1する
        INC B_BTN
      endif

    ' 渡されたボタンがYだったら
    when #B_RLEFT
      ' ボタンカウント配列の数が256未満かどうか調べる
      if Y_BTN < 256 then 
        ' 条件に一致したら+1する
        INC Y_BTN
      endif

    ' 渡されたボタンがAだったら
    when #B_RRIGHT
      ' ボタンカウント配列の数が256未満かどうか調べる
      if A_BTN < 256 then 
        ' 条件に一致したら+1する
        INC A_BTN
      endif
  endcase
end