【プチコン4講座】あっちむいてホイを作ろう:完成度アップ編
こんにちは。継続の錬金術士なおキーヌです。
ブログ毎日更新は254日目になります。
あっちむいてホイゲームを今回で完成させましょう。
前回はボタンカウントを実装したことででゲーム全体の制御を行いました。
だいぶゲームらしくなりましたが、今のままでは理不尽極まりないゲームなので
もうちょっとゲーム性を持たせて完成度を高めましょう。
それではプチコンミニゲーム講座第3回目を始めようと思います。
Aボタン以外の処理を実装する
現状、Aボタンだけでしかゲームを進められないので1つの方向しか決定ができません。
他のボタンも押せるようにして上下左右の方向を向けるようにしましょう。
ループの中の以下の部分を書き換えます。
' Xボタンカウントが1の場合向きを1にする
IF X_BTN == 1 THEN
DIRECT = 1
' Bボタンカウントが1の場合向きを1にする
ELSEIF B_BTN == 1 THEN
DIRECT = 2
' Yボタンカウントが1の場合向きを1にする
ELSEIF Y_BTN == 1 THEN
DIRECT = 3
' Aボタンカウントが1の場合向きを1にする
ELSEIF A_BTN == 1 THEN
DIRECT = 4
ENDIF
前回まではAが1でしたが、順番的に上下左右:XBYA:1234の順で書きました。
条件式はELSEIFで増やすことができるので覚えておきましょう。
これで上下左右を向くことができるようになりました。
攻守のを実装してフェアなゲームにする
しかし1回でも相手と同じ向きにならない場合理不尽にもこちらのライフが減ってしまいます。
これではフェアではないので
- 攻撃の時だけ相手にダメージを与える。
- 守備の時だけダメージを受けるようにする。
この仕組みを作るためには、今何がどうなのかという判断が必要です。
今回でいうなら今自分は「攻撃」なのか「守備」なのかを判断したいですね。
これなら0か1かの2択でいけます。
0か1というのはコンピューターは得意中の得意です!
しかもすでに勝った・負けたでフラグという仕組みを使っていますね。
あれをそのままもう1つ作って攻守フラグを作ります。
' 攻守フラグ
VAR ATTACK_FLAG = RND(2)
対戦は1:1なので1つのフラグがあればどちらが攻守なのか判断がつきますね。
コントローラー関数とかみると、ちらほらシャープ「#」がついたものが出てきていますね。
これは定数と言ってプログラムが始まる前に設定されます。
定数は基本的には変数と同じしくみですが、1回代入するとそれ以降代入も可減算もできません。
変数の劣化版じゃん!って思いますが、
プログラミングをしていると値が変更されては困るようなときが多々出てくるので便利です。
ちなみに#TRUEは数値の1が入っています。
#FALSEは0が入っているので別に変数に入れるのは0か1かでも大丈夫です。
宣言時にRND(1)を使って「0か1か」を入るようにしています。
その後はわかりやすいように#TRUEか#FALSEで値を入れ替えてやりましょう。
攻守システムを実装する
フラグを作ったので早速攻守システムを導入してみましょう。
' もしプレイヤーの向きが決定されていたら
IF DIRECT > 0 THEN
' もしプレイヤーの向きと相手の向きが同じなら
IF ENEMY_DIRECT == DIRECT THEN
' 攻守チェック
IF ATTACK_FLAG == 0 THEN
' 攻撃側なら相手のライフを1つ減らす
ENEMY_LIFE = ENEMY_LIFE - 1
ELSE
' 守備側なら自分のライフを1つ減らす
MY_LIFE = MY_LIFE - 1
ENDIF
ELSE
' 向きがお互い違う場合はノーカウント
IF ATTACK_FLAG == 0 THEN
' 攻撃側なら
PRINT "ハズレ!"
ELSE
' 守備側なら
PRINT "セーフ!"
ENDIF
ENDIF
' 現在のライフの数値を表示
PRINT "----"
PRINT "じぶんのHP:"+ MY_LIFE
PRINT "あいてのHP:"+ ENEMY_LIFE
PRINT "----"
' 攻守を反転させる
ATTACK_FLAG = !ATTACK_FLAG
ENDIF
ライフが増減する処理の部分を改良しました。
これでフェアなゲームになりましたね。
ターン数カウントを実装してみる
おまけに今何ターン目なのかが判るようにしてみましょう。
' ターンカウント
VAR TURN_COUNT = 0
ループ前に1回カウントするようにしてみます。
' ターンカウント増加
INC TURN_COUNT
' ループ開始
LOOP
~~~ 省略
ENDLOOP
後はボタンが押されたときにカウントを増やしていくといいでしょう。
あれ?1つ増やすのって「変数=変数+1」じゃなくていいの?
って思った方も居たり既にボタンカウント関数で気づいてた方もいるかもしれませんが
一応「INC」という命令について説明します。
INCはインクリメントという意味で指定した変数の数を1つ増やすことができます。
ここで重要なのは変数が「数値」じゃなければエラーが出ます。
なんせ数値を1つ増やす処理なので「文字列」だと何を増やせばいいのかってなりますよね。
カンマを付けて数値を直接指定するとその数値を増やすことができます。
以下の処理は同じ意味です。
NUM = NUM + 2
INC NUM, 2
ちなみに減らすのは「DEC」を使います。
単純に演算子がプラスだったのがマイナスにかわるだけですね。
今まで通りINCとかを使わなくても別に大丈夫ですが、
結構便利なので覚えておくとプログラミング力がアップします!
もう一つ攻守逆転の処理の「!ATTAC_FLAG」というビックリマーク(エクスクラメーションマーク)は
「NOT」という意味になるので現状ATTAC_FLAGに1が入っていたら
NOT 1
という意味になるので、1ではないということになります。
コンピューターの世界では0か1かの2つしかないので、
1じゃなければ0、0じゃなければ1となります。
こうすることでわざわざ1か0かを代入する処理を書かなくても、
「自分に現在の自分の逆を代入する」ことでスッキリしたコードになります。
HPの増減表示だけではつまらないので状況が判るようにする
現状、数値の上限がわかるだけでなんか見た目的にゲームぽくないですよね。
しかもログがどんどん増えて画面が文字だらけになるのもまるでネットゲームの会話ログみたいです。
これではゲームらしくないので、ターンごとに画面をクリアして
RPGの戦闘みたいな感じにしてみましょう。
' 攻守の表示
IF ATTACK_FLAG == 0 THEN
PRINT "【プレイヤーのターン】"
ELSE
PRINT "【エネミーのターン】"
ENDIF
' ループ開始
LOOP
' プレイヤーのキー入力監視
D_Controller
' 相手の向きをきめる
ENEMY_DIRECT = RND(4) + 1
' Xボタンカウントが1の場合向きを1にする
IF X_BTN == 1 THEN
DIRECT = 1
' Bボタンカウントが1の場合向きを1にする
ELSEIF B_BTN == 1 THEN
DIRECT = 2
' Yボタンカウントが1の場合向きを1にする
ELSEIF Y_BTN == 1 THEN
DIRECT = 3
' Aボタンカウントが1の場合向きを1にする
ELSEIF A_BTN == 1 THEN
DIRECT = 4
ENDIF
' 何かボタンが押されたら
IF DIRECT != 0 THEN
' 画面全体の文字をクリアする
GCLS
' 攻守の表示
IF ATTACK_FLAG == 0 THEN
PRINT "【プレイヤーのターン】"
ELSE
PRINT "【エネミーのターン】"
ENDIF
PRINT "---------------------------------"
PRINT "あっちむいてホイ!"
ENDIF
' もしプレイヤーの向きが決定されていたら
IF DIRECT > 0 THEN
' お互いの方向を表示する
PRINT "プレイヤーのほうこう:" + STR$(DIRECT)
PRINT "エネミーのほうこう :" + STR$(ENEMY_DIRECT)
PRINT "---------------------------------"
' もしプレイヤーの向きと相手の向きが同じなら
IF ENEMY_DIRECT == DIRECT THEN
' 攻守チェック
IF ATTACK_FLAG == 0 THEN
' 攻撃側なら相手のライフを1つ減らす
ENEMY_LIFE = ENEMY_LIFE - 1
PRINT "エネミーに 1 のダメージを あたえた!"
ELSE
' 守備側なら自分のライフを1つ減らす
MY_LIFE = MY_LIFE - 1
PRINT "プレイヤーは 1 のダメージを うけた!"
ENDIF
ELSE
' 向きがお互い違う場合はノーカウント
IF ATTACK_FLAG == 0 THEN
' 攻撃側なら
PRINT "しまった!こうげきをはずしてしまった!"
ELSE
' 守備側なら
PRINT "プレイヤーはエネミーのこうげきをうけながした!"
ENDIF
ENDIF
' 現在のライフの数値を表示
PRINT "----"
PRINT "【げんざいのじょうきょう】"
PRINT "じぶんのHP:"+ MY_LIFE
PRINT "あいてのHP:"+ ENEMY_LIFE
PRINT "----"
' 攻守を反転させる
ATTACK_FLAG = !ATTACK_FLAG
' 次は誰のターンかを表示
IF ATTACK_FLAG == 0 THEN
PRINT "つぎは プレイヤーのターン です"
ELSE
PRINT "つぎは エネミーのターン です"
ENDIF
PRINT "☆ボタンにゅうりょくたいきちゅう...☆"
ENDIF
' プレイヤーの向きをリセットする
DIRECT = 0
' もし 自分のライフが0になったら
IF MY_LIFE == 0 THEN
' 負け情報を記憶
WIN = #FALSE
' ループを強制的に抜ける
BREAK
ELSEIF ENEMY_LIFE == 0 THEN
' 勝ち情報を記憶
WIN = #TRUE
' ループを強制的に抜ける
BREAK
ENDIF
' 垂直同期
VSYNC
ENDLOOP
これで1ターン目が終わったらボタン入力まではダメージログが残って、
ボタンが押されたら画面をクリアして次のターンのログが表示されるようになります。
実はこれを応用するとドラクエのようなターン制バトルを作ることができます。
というかほぼ4つの手があるじゃんけん風バトルになっていますね。
現状、向きが数値で表示されているのでどの方向を向いているのかわかりづらいですね。
これまでやってきたことを使えば難なく実装できるので、
一度自分後からで「IF」や「CASE」の条件分岐命令を使って表示を数値から上下左右に変更してみてください。
ゲームの完成とこれからの改善
あっちむいてホイを完成させることができました。
色々荒は目立ちますが、1つのゲームとして確立していますね。
いきなり壮大なRPGやアクションゲームを作ろうとすると技術力やモチベーションが追い付かず
挫折してしまうので、まずはこういった簡単な仕組みのゲームから作るのがベストです。
実は私も昔はFFやドラクエにあこがれていきなり大作RPGを作ろうと挫折しまくったものです。
今回作ったあっちむいてホイの仕組みは先ほども言いましたがドラクエの戦闘とそっくりです。
ドラクエといえば今では多彩な攻撃がありますが初代は
- こうげき
- まほう
- ぼうぎょ
- にげる
しかありませんでした。
今回も上下左右の4つのコマンドを使ってバトルをしていますね。
それらを置き換えるだけでRPGのバトルになります。
なので今回のあっちむいてホイ作りは、RPGのバトルを作るための準備運動みたいなものですね。
そしてFFのようなバトルにしたい場合はゲージを作って、
たまったら攻撃するという仕組みに変更するだけで作れてしまいます。
千里の道も一歩から。
このような小さな積み重ねがあなたの力になります。
しょぼいからといってプライドを気にして作らないのは、あなたの成長をとめてしまうのでもったいないです。
しょぼくても作って、改良を重ねていけば素晴らしいゲームになります。
今回作った「あっちむいてホイ」を改良してみてRPGのバトル風にしてみるのも面白いですね。
このまま次の講座を進めてもいいですがこのゲームを改良してみるのもいいでしょう。
最後に完成版コードを置いておきますので、各自ご自由に改変してください。
それでは。
' 全画面クリア
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
' 攻守フラグ
VAR ATTACK_FLAG = RND(2)
' ターンカウント
VAR TURN_COUNT = 0
' 攻守の表示
IF ATTACK_FLAG == 0 THEN
PRINT "【プレイヤーのターン】"
ELSE
PRINT "【エネミーのターン】"
ENDIF
' ターンカウント増加
INC TURN_COUNT
' ループ開始
LOOP
' プレイヤーのキー入力監視
D_Controller
' 相手の向きをきめる
ENEMY_DIRECT = RND(4) + 1
' Xボタンカウントが1の場合向きを1にする
IF X_BTN == 1 THEN
DIRECT = 1
' Bボタンカウントが1の場合向きを1にする
ELSEIF B_BTN == 1 THEN
DIRECT = 2
' Yボタンカウントが1の場合向きを1にする
ELSEIF Y_BTN == 1 THEN
DIRECT = 3
' Aボタンカウントが1の場合向きを1にする
ELSEIF A_BTN == 1 THEN
DIRECT = 4
ENDIF
' 何かボタンが押されたら
IF DIRECT != 0 THEN
' ターンカウント増加
INC TURN_COUNT
' 画面全体の文字をクリアする
GCLS
' 攻守の表示
IF ATTACK_FLAG == 0 THEN
PRINT "【プレイヤーのターン】"
ELSE
PRINT "【エネミーのターン】"
ENDIF
PRINT "---------------------------------"
PRINT "あっちむいてホイ!"
ENDIF
' もしプレイヤーの向きが決定されていたら
IF DIRECT > 0 THEN
' お互いの方向を表示する
PRINT "プレイヤーのほうこう:" + STR$(DIRECT)
PRINT "エネミーのほうこう :" + STR$(ENEMY_DIRECT)
PRINT "---------------------------------"
' もしプレイヤーの向きと相手の向きが同じなら
IF ENEMY_DIRECT == DIRECT THEN
' 攻守チェック
IF ATTACK_FLAG == 0 THEN
' 攻撃側なら相手のライフを1つ減らす
ENEMY_LIFE = ENEMY_LIFE - 1
PRINT "エネミーに 1 のダメージを あたえた!"
ELSE
' 守備側なら自分のライフを1つ減らす
MY_LIFE = MY_LIFE - 1
PRINT "プレイヤーは 1 のダメージを うけた!"
ENDIF
ELSE
' 向きがお互い違う場合はノーカウント
IF ATTACK_FLAG == 0
' 攻撃側なら
PRINT "しまった!こうげきをはずしてしまった!"
ELSE
' 守備側なら
PRINT "プレイヤーはエネミーのこうげきをうけながした!"
ENDIF
ENDIF
' 現在のライフの数値を表示
PRINT "----"
PRINT "【げんざいのじょうきょう】"
PRINT "じぶんのHP:"+ MY_LIFE
PRINT "あいてのHP:"+ ENEMY_LIFE
PRINT "----"
' 攻守を反転させる
ATTACK_FLAG = !ATTACK_FLAG
' 次は誰のターンかを表示
IF ATTACK_FLAG == 0 THEN
PRINT "つぎは プレイヤーのターン です"
ELSE
PRINT "つぎは エネミーのターン です"
ENDIF
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