2024年04月25日くいなちゃん


プログラミング言語Kuin」の基本機能を学ぶ、実行環境がexe用のチュートリアル3です。 今回は簡単なミニゲームを作ってみます。

1ミニゲームの作成

それでは簡単なプログラムを書いてみましょう。 迫りくる敵をジャンプでかわすミニゲームです。
前回紹介したスニペットの機能から、「mainとdrawコントロール」のコードを挿入してください。 図1-1の通りです。
  1. var wndMain: wnd@Wnd
  2. var drawMain: wnd@Draw
  3.  
  4. func main()
  5.   do @wndMain :: wnd@makeWnd(null, %aspect, 1600, 900, "Title")
  6.   do @drawMain :: wnd@makeDraw(@wndMain, 0, 0, 1600, 900, %scale, %scale, false)
  7.  
  8.   while(wnd@act())
  9.     do draw@render(60)
  10.   end while
  11. end func
図1-1: kui_jump1.kn
大まかには、前回のウインドウの作成と同様のプログラムになっていて、5行目でウインドウを作成しています。
6行目では、「wnd@makeDraw」関数によってドローコントロール作成しています。 「ドローコントロール」とは、高度なグラフィックスが描画できるコントロールです。
9行目の「draw@render」関数は、描画結果を画面に転送する関数です。 「60」という値を渡すと、60FPS(1秒間に60回描画)になるように待機します。
ドローコントロールに描画したものはバックバッファという目に見えない領域に描画されます。 このため、それを目に見える画面に転送するには最後に「draw@render」関数を呼ぶ必要があります。 いったんバックバッファに描画することで、描画途中の状態が画面に現れず、画面のちらつきを防ぐことができます。
実行すると図1-2のようになります。
くいじゃん1
図1-2: くいじゃん1
まだ何も描画していないため真っ黒です。

2座標クラスの定義

今回は座標を扱うためのクラスを定義してみます。 「クラス」とは、いくつかの変数や関数をひとまとめにして、新たな型を作ったものです。
図2-1のプログラムを追加してください。
  1. class Pos()
  2.   +var x: float
  3.   +var y: float
  4.   +var veloX: float
  5.   +var veloY: float
  6. end class
  7.  
  8. var player: @Pos
  9. var enemy: @Pos
  10. var wndMain: wnd@Wnd
  11. var drawMain: wnd@Draw
  12.  
  13. func main()
  14.   do @wndMain :: wnd@makeWnd(null, %aspect, 1600, 900, "Title")
  15.   do @drawMain :: wnd@makeDraw(@wndMain, 0, 0, 1600, 900, %scale, %scale, false)
  16.  
  17.   while(wnd@act())
  18.     do draw@render(60)
  19.   end while
  20. end func
図2-1: kui_jump2.kn
8~9行目では、「@Pos」という型の2つの変数playerとenemyを作成していますが、この「@Pos」は1~6行目で定義しています。 「class クラス名()」と「end class」で挟んだ部分がクラスで、いくつかの変数や関数を持った新たな型となります。
このプログラムでは、4つの変数x、y、veloX、veloYをクラスに持たせています。 それぞれの変数は「float」という型ですが、float型は小数を入れることができる型です。

3プレイヤーと敵の表示

それではプレイヤーと敵を表示します。 プレイヤー「@player」と敵「@enemy」の変数に座標を代入し、その座標情報に基づいて描画します(図3-1)。
  1. class Pos()
  2.   +var x: float
  3.   +var y: float
  4.   +var veloX: float
  5.   +var veloY: float
  6. end class
  7.  
  8. var player: @Pos
  9. var enemy: @Pos
  10. var wndMain: wnd@Wnd
  11. var drawMain: wnd@Draw
  12.  
  13. func main()
  14.   do @wndMain :: wnd@makeWnd(null, %aspect, 1600, 900, "Title")
  15.   do @drawMain :: wnd@makeDraw(@wndMain, 0, 0, 1600, 900, %scale, %scale, false)
  16.  
  17.   do @player :: #@Pos
  18.   do @player.x :: 400.0
  19.   do @player.y :: 800.0
  20.   do @enemy :: #@Pos
  21.   do @enemy.x :: 1500.0
  22.   do @enemy.y :: 800.0
  23.   do @enemy.veloX :: -10.0
  24.  
  25.   while(wnd@act())
  26.     do draw2d@circle(@player.x, @player.y, 50.0, 50.0, 0xFFFFFF00)
  27.     do draw2d@circle(@enemy.x, @enemy.y, 40.0, 40.0, 0xFFFF0000)
  28.     do draw@render(60)
  29.   end while
  30. end func
図3-1: kui_jump3.kn
17~23行目で、変数@player、@enemyに値を設定しています。
17、20行目では「#@Pos」を代入していますが、「#クラス名」と書くことで、そのクラスの実体が作成されます。 クラス型の変数は、使う前に必ず実体を作成しておかなければいけません。
18行目のように「.x」と書くと、そのクラス型の変数が持つ「x」という変数が読み書きできます。 この「x」とは、「@Pos」クラスが持っている、2行目で定義していた変数です。
このプログラムでは、「.x」「.y」にそれぞれX座標、Y座標の値を入れ、「.veloX」「.veloY」にそれぞれ横方向の速度、縦方向の速度を入れることにします。
26、27行目では、それらの位置情報に基づき「draw2d@circle」関数を使って、プレイヤーと敵を円で描画しています。
「draw2d@circle」関数の括弧内はそれぞれ「円の中心のX座標」「円の中心のY座標」「半径X」「半径Y」「色」です。 色は16進数で指定し、「0x不透明度」のように(加法混色で)書きます。 例えばこの「0xFFFFFF00」は黄色で、「0xFFFF0000」は赤を表します。
実行すると図3-2のようになります。
くいじゃん2
図3-2: くいじゃん2

4敵とプレイヤーの移動

ここから先は、「while(wnd@act())」から「end while」の間だけを書き換えるため、その部分のプログラムだけを抜粋して掲載します。 敵の移動処理を加えます(図4-1)。
  1.   while(wnd@act())
  2.     do @enemy.x :+ @enemy.veloX
  3.     if(@enemy.x < -40.0)
  4.       do @enemy.x :: 1640.0
  5.       do @enemy.veloX :: -lib@rndFloat(6.0, 50.0)
  6.     end if
  7.     do draw2d@circle(@player.x, @player.y, 50.0, 50.0, 0xFFFFFF00)
  8.     do draw2d@circle(@enemy.x, @enemy.y, 40.0, 40.0, 0xFFFF0000)
  9.     do draw@render(60)
  10.   end while
図4-1: kui_jump4.kn
2行目では位置X「@enemy.x」に速度X「@enemy.veloX」を加えています。 「@enemy.x :+ @enemy.veloX」とは「@enemy.x :: @enemy.x + @enemy.veloX」の略記で、@enemy.xと@enemy.veloXを足したものを@enemy.xに代入しています。 これを繰り返すことで、敵は徐々に横に移動します。
3~6行目は、敵が画面の左端まで行ってしまったときに、右端に戻す処理です。 「if()」と書くと、その値が真だったときは「end if」に挟まれた行を順に実行してから次に進み、値が偽だったときは「end if」までを飛ばして次に進みます。
つまり3行目の「if(@enemy.x < -40.0)」は、敵のX座標が画面左端の座標「-40.0」未満になったときに、「end if」で挟まれた処理を行います。 挟まれた部分では、4行目で敵のX座標「@enemy.x」を画面右端「1640.0」に移動させ、5行目で速度「@enemy.veloX」を「-6.0~-50.0」の範囲の乱数で変更しています。
「lib@rndFloat」は、小数の乱数を返す関数で、括弧内は順に「乱数の最小値」「乱数の最大値」です。
このプログラムを実行すると、敵が速度を変えながら何度も迫り来ることがわかります。

5プレイヤーのジャンプ

さて次は、プレイヤー側も敵をよけられるように、ジャンプできるようにします(図5-1)。
  1.   while(wnd@act())
  2.     do @enemy.x :+ @enemy.veloX
  3.     if(@enemy.x < -40.0)
  4.       do @enemy.x :: 1640.0
  5.       do @enemy.veloX :: -lib@rndFloat(6.0, 50.0)
  6.     end if
  7.     do @player.veloY :+ 1.0
  8.     do @player.y :+ @player.veloY
  9.     if(@player.y > 800.0)
  10.       do @player.y :: 800.0
  11.       do @player.veloY :: 0.0
  12.     end if
  13.     if(input@pad(0, %a) = 1 & @player.y = 800.0)
  14.       do @player.veloY :: -20.0
  15.     end if
  16.     do draw2d@circle(@player.x, @player.y, 50.0, 50.0, 0xFFFFFF00)
  17.     do draw2d@circle(@enemy.x, @enemy.y, 40.0, 40.0, 0xFFFF0000)
  18.     do draw@render(60)
  19.   end while
図5-1: kui_jump5.kn
7行目ではプレイヤーの速度Y「@player.veloY」に下向きの加速度「1.0」を加えています。 これは重力に相当します。
8行目では位置Y「@player.y」に速度Y「@player.veloY」を加えています。 この段階でプレイヤーには重力が加わり、下向きにどんどん落下していきます。
9~12行目は、プレイヤーが地面より下に貫通しないように接地させる処理です。 地面(800.0)よりも下にいた場合、位置Yを地面の位置「800.0」に補正し、速度Yも「0.0」にリセットしています。
13~15行目は、ジャンプの処理です。 「input@pad(0, %a)」は、ゲームパッドのAボタンが押されていないなら0が返り、押された瞬間に1が返り、押され続けると2以上が返ります。 「input@pad」関数の括弧内は、順に「ゲームパッドの番号」「ゲームパッドのボタン」です。
13行目にある「&」は、日本語の「かつ」に相当し、左右の値が両方とも真なら真、それ以外なら偽になります。 つまり13行目は、ゲームパッドの「Aボタン」が押された瞬間に、かつプレイヤーが接地(@player.y = 800.0)していたら、14行目で速度Y「@player.veloY」を「-20.0」に設定することで、上向きに投げ上げるようなジャンプ運動をさせています。
ゲームパッドの「Aボタン」は、初期設定でキーボードの「Zキー」や「スペースキー」などにも割り当てられているため、キーボードでも操作できます。
実行すると図5-2のようになります。
くいじゃん3
図5-2: くいじゃん3

5.1衝突



最後に衝突判定をします(図5-3)。
  1. while(wnd@act())
  2.   if(lib@dist(@enemy.x, @enemy.y, @player.x, @player.y) < 50.0 + 40.0)
  3.     do draw2d@rect(0.0, 0.0, 1600.0, 900.0, 0xFFCC3333)
  4.     do draw2d@circle(@player.x, @player.y, 50.0, 50.0, 0xFFFFFF00)
  5.     do draw2d@circle(@enemy.x, @enemy.y, 40.0, 40.0, 0xFFFF0000)
  6.     do draw@render(0)
  7.     do lib@sleep(800)
  8.     do @wndMain.close()
  9.   end if
  10.   do @enemy.x :+ @enemy.veloX
  11.   if(@enemy.x < -40.0)
  12.     do @enemy.x :: 1640.0
  13.     do @enemy.veloX :: -lib@rndFloat(6.0, 50.0)
  14.   end if
  15.   do @player.veloY :+ 1.0
  16.   do @player.y :+ @player.veloY
  17.   if(@player.y > 800.0)
  18.     do @player.y :: 800.0
  19.     do @player.veloY :: 0.0
  20.   end if
  21.   if(input@pad(0, %a) = 1 & @player.y = 800.0)
  22.     do @player.veloY :: -20.0
  23.   end if
  24.   do draw2d@circle(@player.x, @player.y, 50.0, 50.0, 0xFFFFFF00)
  25.   do draw2d@circle(@enemy.x, @enemy.y, 40.0, 40.0, 0xFFFF0000)
  26.   do draw@render(60)
  27. end while
図5-3: kui_jump6.kn
2行目の「lib@dist」関数は、2点間の距離を求める関数です。 敵とプレイヤーの距離を求め、敵とプレイヤーの半径の和よりも小さくなれば、両者は近づきすぎて衝突していることになります。 lib@dist関数の括弧内は順に「1点目のX座標」「1点目のY座標」「2点目のX座標」「2点目のY座標」で、2点間の距離の値が返ります。
3~8行目は衝突したときの処理です。 3行目の「draw2d@rect」関数で背景を赤く塗りつぶし、4~6行目でプレイヤーと敵を描画し、7行目の「lib@sleep」関数で800ミリ秒(0.8秒)待ったのち、8行目の「@wndMain.close」メソッドによってウインドウを閉じて終了しています。
本来なら、ゲームオーバー画面を出してタイトル画面に戻すとより親切です。
ちなみにdraw2d@rect関数の括弧内は、順に「左座標」「上座標」「幅」「高さ」「色」です。 ドローコントロールのサイズが1600x900だったため、3行目の「do draw2d@rect(0.0, 0.0, 1600.0, 900.0, 0xFFCC3333)」で画面全体が塗りつぶされます。
実行すると図5-4のようになります。
くいじゃん4
図5-4: くいじゃん4
以上で、ミニゲームの完成です。
今回作ったゲームは単純な見栄えになりましたが、円の代わりに画像を使ってキャラクターを描画するだけで本格的な見た目になります。 次回は、画像を使ったアクションゲームを作ります。
1714004772jaf