Kuina-chan

くいなちゃん2018年10月19日


プログラミング言語Kuin」の基本構文を学ぶチュートリアル3です。 今回は簡単なミニゲームを作ってみます。

ミニゲームの作成

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

座標クラスの定義

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

プレイヤーと敵の表示

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

敵とプレイヤーの移動

ここから先は、「while(wnd@act())」から「end while」の間だけを書き換えるため、その部分のプログラムだけを抜粋して掲載します。 敵の移動処理を加えます(kui_jump4.kn)。
  while(wnd@act())
    do @enemy.x :+ @enemy.veloX
    if(@enemy.x < -40.0)
      do @enemy.x :: 1640.0
      do @enemy.veloX :: -lib@rndFloat(6.0, 50.0)
    end if
    do draw@circle(@player.x, @player.y, 50.0, 50.0, 0xFFFFFF00)
    do draw@circle(@enemy.x, @enemy.y, 40.0, 40.0, 0xFFFF0000)
    do draw@render(60)
  end while
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」は、小数の乱数を返す関数で、括弧内は順に「乱数の最小値」「乱数の最大値」です。
このプログラムを実行すると、敵が速度を変えながら何度も迫り来ることがわかります。

プレイヤーのジャンプ

さて次は、プレイヤー側も敵をよけられるように、ジャンプできるようにします(kui_jump5.kn)。
  while(wnd@act())
    do @enemy.x :+ @enemy.veloX
    if(@enemy.x < -40.0)
      do @enemy.x :: 1640.0
      do @enemy.veloX :: -lib@rndFloat(6.0, 50.0)
    end if
    do @player.veloY :+ 1.0
    do @player.y :+ @player.veloY
    if(@player.y > 800.0)
      do @player.y :: 800.0
      do @player.veloY :: 0.0
    end if
    if(input@pad(0, %a) = 1 & @player.y = 800.0)
      do @player.veloY :: -20.0
    end if
    do draw@circle(@player.x, @player.y, 50.0, 50.0, 0xFFFFFF00)
    do draw@circle(@enemy.x, @enemy.y, 40.0, 40.0, 0xFFFF0000)
    do draw@render(60)
  end while
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キー」や「スペースキー」などにも割り当てられているため、キーボードでも操作できます。
実行するとくいじゃん3のようになります。
くいじゃん3
くいじゃん3

衝突

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