MATLAB でゲームを作る ～3. アクションゲームでジャンプ編～

MATLAB でゲーム制作シリーズ
- [0 章：グラフィックスオブジェクト](https://qiita.com/macht/items/05e848f2fa9fab0a538e)
- [1 章：コールバック](https://qiita.com/macht/items/5853875dce0381e33c3a)
- [2 章：メインループ](https://qiita.com/macht/items/de86b46a22f4109df0c1)
- 3 章：アクションゲームでジャンプ（この記事）

# はじめに

さっき家で MATLAB を起動したら久しぶりすぎてアクティベーション画面が出てきましたが、
そんなことは忘れて今日も MATLAB でゲームを作っていきます。

前回はコールバックとメインループを組みわせて、「キーが押されている間だけ移動する」プログラムを作成しました。

今回は予告通りアクションゲームにスポットを当てた記事を書いていきます。具体的にはマ〇オのようなジャンプ処理の実装を目指します。


# アクションゲームのフィールド作り

　いままでの記事では適当な座標軸上でオブジェクトを動かしていましたが、今回はアクションゲームっぽいフィールドを作っていきましょう。

　まず、座標軸の設定から行います。今回は例として、 1 ドットを 1 x 1 のサイズ、16 x 16ドットを 1 マス、1 画面のサイズを 24 x 18 マスとしてフィールドを作っていきます。操作キャラは前回と同じ `rectangle` です。

```MATLAB:
function jump_test()
fig = figure;
ax = axes;
axis equal;
axis ij;

sqSize = 16;

axmax = [sqSize*24 sqSize*18];
axis([0 axmax(1) 0 axmax(2)]);

rec = rectangle('Position',[0 0 sqSize sqSize]);

end
```

こんなかんじ。ブロックサイズや画面のサイズは後々使うので変数に入れています。最初に画面サイズ等を決めておくと、後々衝突判定などが作りやすくなるためオススメです。

　つぎに、下 1 行に地面となるブロックを作っていきます。こちらも同様に `rectangle` を用いますが、地面っぽさを演出するために黒で塗りつぶします。

```MATLAB:
for i = 1:24
  rectangle('Position',[(i-1)*sqSize axmax(2)-sqSize sqSize sqSize],'FaceColor','k');
end
```

![field.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482141/a1f8b71d-3cea-1482-42d6-9c4b85a409ab.png)



`for` で 24 個の塗りつぶされた `rectangle` を作成しました。すでにそれっぽい雰囲気が出てます。

　ただ、このままだとせっかく地面を作ったのに、操作キャラが宙に浮いてしまっています。そこで、ちゃんと地面に落ちるように重力を作っていきましょう。重力は「地面に接地してない限り下方向に加速する」という処理になりますので、「地面に接地しているか」を判断する処理と「下方向に加速する」処理の 2 つを作ります。

　しかし、前者の処理を厳密に作ろうとすると非常に難しいので、ここでは地面の y 座標は一番下の行に固定されていると考え、操作キャラの y 座標がその値より小さければ接地していない、その値以上なら接地している、とします。

　どのようなプログラムを追加すればいいでしょうか？
。
。
。

```MATLAB:
vx = 0;
vy = 0;

g = 2;

while(isgraphics(fig))
  pos = rec.Position;
  if pos(2) < axmax(2)-sqSize*2
    vy = vy + g;
  end
  pos(1) = min(max(pos(1) + vx,0),axmax(1)-sqSize);
  pos(2) = min(max(pos(2) + vy,0),axmax(2)-sqSize*2);
  rec.Position = pos;
  pause(1/30);
end
```

前回の記事で紹介したメインループ処理を使えば簡単です。速度変数 `vx`, `vy` を用意し、操作キャラが地面につくまで毎ループごとに `vy` が 重力加速度 `g` だけ大きくなる処理になっています。実行すると

![jumpFirst.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482141/9b2689f6-bd35-b785-97e0-20dc0ca117c3.gif)


ちゃんと地面まで落ちてくれました。~~これでニュートンも一安心です。~~

# ジャンプ処理

　それではお待ちかねのジャンプ処理です。今回は一般的な PC ゲームと同じように、ジャンプボタンを Z キーとしましょう。

　「ジャンプして落ちてくる」という処理として考えると難しそうですが、すでに重力の処理は作ってあるので、キャラが地面から離れれば勝手に重力が働いて落ちてくれます。そのため、単純にジャンプボタンが押されたときに一定の上向き速度を出すだけで、ある程度上に行ったあと落ちてくる挙動ができそうです。

```MATLAB:
function jump_test()
fig = figure;
ax = axes;
axis equal;
axis ij;

sqSize = 16;

axmax = [sqSize*24 sqSize*18];
axis([0 axmax(1) 0 axmax(2)]);

rec = rectangle('Position',[0 0 sqSize sqSize]);
for i = 1:24
  rectangle('Position',[(i-1)*sqSize axmax(2)-sqSize sqSize sqSize],'FaceColor','k');
end

vx = 0;
vy = 0;

g = 2;
fig.WindowKeyPressFcn = @wkpf;

while(isgraphics(fig))
  pos = rec.Position;
  if pos(2) < axmax(2)-sqSize*2
    vy = vy + g;
  end
  pos(1) = min(max(pos(1) + vx,0),axmax(1)-sqSize);
  pos(2) = min(max(pos(2) + vy,0),axmax(2)-sqSize*2);
  rec.Position = pos;
  pause(1/30);
end

  function wkpf(src,data)
    if strcmp(data.Key,'z')
      vy = -12;
    end
  end

end
```


前回、前々回も使った `WindowKeyPressFcn` コールバックを用いることで、z キーが押されたときに上向きに `8` の速度を出すようにしました。

![jumpFirst.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482141/b4bd56bd-b89d-825e-c6bd-d0c8d9055b73.gif)

見たかんじしっかりジャンプっぽくなっていますね。

　が、このままだと空中でもジャンプができてしまいます~~（Pパタかな？）~~。今回は操作キャラに翼が生えているわけでもないので、ちゃんと地面にいるときだけジャンプできるようにコールバック関数 `wkpf` を修正しましょう。

```MATLAB:
function wkpf(src,data)
    if strcmp(data.Key,'z') && pos(2) >= axmax(2)-sqSize*2
      vy = -12;
    end
  end
end
```
`y` 座標が地面の上にいるときだけ速度が変わるようにしました。これで無限空中ジャンプも起こらなくなりました。


# 大ジャンプ処理

　さて、単純なジャンプ処理ができたので、続いて「ジャンプキーが押されている時間が長いほどジャンプの距離が伸びる」、いわゆる「大ジャンプ」の処理を作っていきます。アクションゲームなのにジャンプ距離が伸びないとつまらないですからね。

　……とはいっても、ジャンプキーが押されている時間をジャンプ距離に反映させるにはどうすればよいのでしょうか？キーが押されている時間を測るのはそこまで難しくないですが、それをジャンプ距離に反映しようとすると、簡単にはいきません。

　ここで発想の転換です。「キーが押されている時間がジャンプに反映する」処理を考えるのではなく、「**キーが押されている間だけあまり落ちない**」処理だと考えてしまいましょう。これならそこまで難しくありません。ジャンプキーが押されている間は上に少し加速させればいいだけですからね。

```MATLAB:
function jump_test()
fig = figure;
ax = axes;
axis equal;
axis ij;

sqSize = 16;

axmax = [sqSize*24 sqSize*18];
axis([0 axmax(1) 0 axmax(2)]);

rec = rectangle('Position',[0 0 sqSize sqSize]);
for i = 1:24
  rectangle('Position',[(i-1)*sqSize axmax(2)-sqSize sqSize sqSize],'FaceColor','k');
end

vx = 0;
vy = 0;

g = 2;
fig.WindowKeyPressFcn = @wkpf;
fig.WindowKeyReleaseFcn = @wkrf;

jump = 0; % 大ジャンプフラグ

while(isgraphics(fig))
  pos = rec.Position;
  if pos(2) < axmax(2)-sqSize*2
    vy = vy + g;
    if jump % ジャンプキーが押されている間は上に加速度が発生
      vy = vy - 1;
    end
  end
  if vy > 0
    jump = 0; % 落ちるときはジャンプフラグOFF
  end
  pos(1) = min(max(pos(1) + vx,0),axmax(1)-sqSize);
  pos(2) = min(max(pos(2) + vy,0),axmax(2)-sqSize*2);
  rec.Position = pos;
  pause(1/30);
end

  function wkpf(src,data)
    if strcmp(data.Key,'z') && pos(2) >= axmax(2)-sqSize*2
      vy = -12;
      jump = 1; % ジャンプフラグON
    end
  end

  function wkrf(src,data)
    if strcmp(data.Key,'z')
      jump = 0; % キーが離れたらジャンプフラグOFF
    end
  end
end
```

まず、ジャンプキーが押されたら `jump` というフラグが ON になります。このフラグが ON のときは、**上向きに 1 の加速度が追加で発生します**（常に下向きに 2 の重力が働いているので、結果的には下向きに 1 だけ加速します）。このフラグは、ジャンプキーが離れる、またはジャンプが終わって落下が始まったら OFF になります。「キーが離れる」については、前回触れた `WindowKeyReleaseFcn` を使用すれば簡単ですね。
![jumpTest.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482141/44d8e83c-0ab1-dc06-ca78-61027632e3ab.gif)

実際に動かしてみても、見た目にはまったく違和感がありません。


　ちなみにこの「ジャンプキーが押されている間だけ落ちにくくする（重力加速度が減る）」という方法は、**本家マ〇オで用いられている**方法です（詳しく知りたい方は、「マ〇オ　重力加速度」で検索してみましょう）。
　
# 仕上げ

　最後に前回作った移動処理を入れて、横方向に移動できるようにしましょう。

```MATLAB:
function jump_test()
fig = figure;
ax = axes;
axis equal;
axis ij;

sqSize = 16;

axmax = [sqSize*24 sqSize*18];
axis([0 axmax(1) 0 axmax(2)]);

rec = rectangle('Position',[0 0 sqSize sqSize]);
for i = 1:24
  rectangle('Position',[(i-1)*sqSize axmax(2)-sqSize sqSize sqSize],'FaceColor','k');
end

vx = 0;
vy = 0;

g = 2;
fig.WindowKeyPressFcn = @wkpf;
fig.WindowKeyReleaseFcn = @wkrf;

jump = 0;

currentKey = '';

while(isgraphics(fig))
  pos = rec.Position;
  if pos(2) < axmax(2)-sqSize*2
    vy = vy + g;
    if jump
      vy = vy - 1;
    end
  end
  if vy > 0
    jump = 0;
  end
  pos(1) = min(max(pos(1) + vx,0),axmax(1)-sqSize);
  pos(2) = min(max(pos(2) + vy,0),axmax(2)-sqSize*2);
  rec.Position = pos;
  pause(1/30);
end

  function wkpf(src,data)
    if strcmp(data.Key,'z') && pos(2) >= axmax(2)-sqSize*2
      vy = -12;
      jump = 1;
    elseif strcmp(data.Key,'leftarrow')
      vx = -5;
      currentKey = data.Key;
    elseif strcmp(data.Key,'rightarrow')
      vx = 5;
      currentKey = data.Key;
    end
  end

  function wkrf(src,data)
    if strcmp(data.Key,'z')
      jump = 0;
    elseif strcmp(currentKey,data.Key)
      vx = 0;
    end
  end
end
```
![jumpFinal.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482141/4aa769a5-b4d4-8b72-3445-753badbf7d03.gif)

ひと昔前のフラッシュゲームくらいの見た目にはなりましたね！


# まとめ

　今回の記事のまとめです。

- ゲーム画面はドットやマスをあらかじめ決めておくと後の処理が書きやすい
- 重力を作っておくと、「キーが押されたら上に速度を出す」処理だけで簡単にジャンプが実装できる
- 大ジャンプは「キーが押されている間だけ落ちにくくなる」処理と考えるとそこまで苦労せずに実装できる

　
　次回はおそらく「地面以外のブロックに乗る/ぶつかる」処理を作っていく予定です。結構面倒くさい処理のオンパレードなので、更新に時間かかりそう……。
　
　
　
　「いいね」が増えると次回の記事が豪華になるかも