Processingで作る単位キャッチャー

この記事は学生時代にサークルで発表した資料を再編したものです。
随時直していく予定です

## 事前準備

### Processingのインストール

[公式サイト]からダウンロード

[公式サイト]: https://processing.org/download/

### 画像データのダウンロード

いらすとやから拝借、画像名を以下に変更する

- [chimpanzee.png](https://www.irasutoya.com/2017/02/blog-post_267.html)
- [school.png](https://www.irasutoya.com/2017/11/blog-post_545.html)
- [tanni.png](https://www.irasutoya.com/2016/10/blog-post_99.html)


## 単位キャッチャーとは

友人が学生時代に開発した、チンパンジーが必死に単位をとる悪魔的Webゲーム

<img width="1260" alt="1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505541/9122581b-19d8-6c4e-b148-88f33477c049.png">

下記のサイトから遊べる

https://ukya.tk/


## Processingとは

オープンソースのグラフィック開発環境、およびプログラミング言語

### 強み

- Javaをベースにした言語でシンプルでわかりやすい
- 動作が軽い
- プロジェクト構成がシンプル
- 実行ファイルに簡単に出力できる

### 弱み

- 開発の制約があるため、大規模な開発には向いていない


## Hello World

```java
// 1回だけ実行
void setup() {
  // 1280x720の画面を作成
  size(1280, 720);
  // コンソール画面に表示
  println("Hello, World");
  // 実行画面に表示
  text("Hello, World", 100, 100);
}
```


## 基本

### 線を描画

```java
void setup() {
  size(1280, 720);
  // (X, Y) = (0, 0)から
  // (X, Y) = (300, 300)まで線を描画
  line(0, 0, 300, 300);
}
```

### 楕円を描画

```java
void setup() {
  size(1280, 720);
  // (X, Y) = (80, 200)に
  // 幅50px, 高さ80pxの円を作成
  ellipse(80, 200, 50, 80);
}
```

### 背景を描画
```java
void setup() {
  size(1280, 720);
  // 0（黒）〜255（白）
  background(0);
}
```

### カラー
```java
void setup() {
  size(1280, 720);
  // R, G, B
  background(240, 180, 200);
  // R, G, B, A（透明度）
  background(240, 180, 200, 75);
}
```

### アニメーション

基本描画頻度は60fps

```java
// 1秒間に60回実行（更新）
void draw() {
  //...
}
```

### マウスについてくる正円を描画

毎フレーム円が描画される

```java
void setup() {
  size(1280, 720);
}

void draw() {
  ellipse(mouseX, mouseY, 50, 50);
}
```

背景塗りつぶしで前フレームの描画を消す

```java
void setup() {
  size(1280, 720);
}

void draw() {
  background(255);
  ellipse(mouseX, mouseY, 50, 50);
}
```


### 変数

```java
// 整数が入れられる変数を用意
int number = 10;
// 値を変更（整数のみ代入可能）
number = -50;
// 変数の値を1増やすコード
number = number + 1;
number += 1;
number++;
```

#### 変数の種類

```java
float   // 浮動小数
boolean // 真偽値
String  // 文字列
PImage  // 画像データ（Processingオリジナル）
```


### 移動する円

```java
// 変数の宣言
int positionX;

void setup() {
  // 変数の初期化
  positionX = 0;

  size(500, 500);
}

void draw() {
  // 1フレームごとに値が1増える
  positionX += 2;
  
  background(255);
  // 数値の代わりに変数を使用
  ellipse(positionX, 100, 50, 50);
}
```

### 条件分岐

```java
int positionX;
// 左にに動くかどうか
boolean leftMove;

void setup() {
  positionX = 0;
  size(500, 500);
  leftMove = false;
}

void draw() {
  // 右に動くかで値の計算を変える
  if (leftMove == false) {
    positionX += 2;
  } else {
    positionX -= 2;
  }
  
  // 500を超えたら左に動かす
  if (500 < positionX) {
    leftMove = true;
  }

  background(255);
  ellipse(positionX, 100, 50, 50);
}
```

### 条件分岐

#### 値の評価注意点

```java
a == b // 「=」が1つだと代入になるので注意
a <= b // 大なりイコール。「=」は右
```

#### 多重評価

```java
S && T             // SかつT
S || T             // SまたはT
(a < b) && (b < c) // a < b < c と同義
```

### 同一値の条件分岐

#### if / else文

```java
if (number == 0) {
  // ...
} else if (number == 1) {
  // ...
} else {
  // ...
}
```

#### switch文

```java
switch (number) {
  case: 0 {
    // ...
    break; // つけ忘れに注意！
  }
  case: 1 {
    // ...
    break;
  }
  default: {
    // ....
  }
}
```

for文は今回使わないので省略


## ゲームを作っていこう

### 画像データを置く

`（プロジェクトファイル）/data`の中に画像を置く

<img width="543" alt="2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505541/17ad4e21-576d-da0c-d64a-40c86acea1a5.png">

#### 画像の読み込み方

```java
PImage image = loadImage(ファイル名);

```

#### 画像の表示サイズ変更

```java
image.resize(幅, 高さ);
```

### 画面の生成＆背景設定

```java
// 画像用の変数を用意
PImage backgroundImage;

void setup() {
  // パフォーマンス重視のP2Dモードで起動
  size(1280, 720, P2D);
  // 画像データを読み込み
  backgroundImage = loadImage("school.jpg");
  // 画面サイズと同じ大きさに加工
  backgroundImage.resize(width, height);
}

void draw() {
  // 背景を描画
  background(backgroundImage);
}
```

`width` / `height`は実行画面の幅 / 高さ、
`size()`で指定した値と同じ

### プレーヤー画像の配置

```java
/** 下記を追記 */
PImage playerImage;

void setup() {
  /** 下記を追記 */
  playerImage = loadImage("chimpanzee.png");
  playerImage.resize(200, 200);
}

void draw() {
  /** 下記を追記 */
  // 表示するPImage, X座標, Y座標
  image(playerImage, 640, 520);
}
```

### マウスで横移動が出来るようにする

```java
void draw() {
  // image(playerImage, 640, 520);
  /** 上記を下記に変更 */
  image(playerImage, mouseX, 520);
}
```

### 画像描画の座標の基準点を中心点に変更

```java
void setup() {
  /** 下記を追記 */
  imageMode(CENTER);
}
```

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505541/a25b4386-d8da-a3bf-d0cb-68c097c8b132.png)

### 単位の設置

```java
/** 下記を追記 */
PImage tanniImage;

void setup() {
  /** 下記を追記 */
  tanniImage = loadImage("tanni.png");
  tanniImage.resize(100, 100);
}

void draw() {
  /** 下記を追記 */
  image(tanniImage, 600, 300);
}
```

### 単位を落とす

```java
/** 下記を追記 */
float tanniY;

void setup() {
  /** 下記を追記 */
  // 0だとはみ出るので(画像縦サイズ / 2)上に設置
  tanniY = -50;
}

void draw() {
  /** 下記を追記 */
  // 毎フレーム8px下に移動する
  tanniY += 8;

  // image(tanniImage, 600, 300);
  /** 上記を下記に変更 */
  image(tanniImage, 600, tanniY);
}
```

### 単位を再出現させる

```java
void draw() {
  /** 下記を追記 */
  // 画像が画面から消えたら
  if (height < tanniY - 50) {
    // 初期値を代入
    tanniY = -50;
  }
}
```

### 出現位置をランダムにする

#### ランダム数値生成メソッド

```java
random(100);        // 0以上100未満のfloat値
random(-40.5, 10);  // -40.5以上10未満のfloat値
```

#### 出現位置を毎回ランダムにする

```java
/** 下記を追記 */
float tanniX;

void setup() {
  /** 下記を追記 */
  // 画像が隠れないように範囲を指定
  tanniX = random(50, width - 50);
}

void draw() {
  // image(tanniImage, 600, tanniY);
  /** 上記を下記に変更 */
  image(tanniImage, tanniX, tanniY);

  if (height < tanniY - 50) {
    tanniY = -50;
    /** 下記を追記 */
    tanniX = random(50, width - 50);
  }
}
```

### プレーヤーとの衝突判定

#### 2つの座標の距離を算出するメソッド

```java
dist(Aのx座標, Aのy座標, Bのx座標, Bのy座標)
```

distance（距離）の略

### プレーヤーとの衝突判定をつける

```java
void draw() {
  /** 下記を追記 */
  // 判定距離は80ピクセルに設定
  if (dist(mouseX, 520, tanniX, tanniY) < 80) {
    tanniY = -50;
    tanniX = random(50, width - 50);
  }
}
```

### スコア機能をつける

```java
/** 下記を追記 */
// 何も指定しなければ0がセットされる
int score;

void draw() {
  if (dist(mouseX, 520, tanniX, tanniY) < 80) {
    tanniY = -50;
    tanniX = random(50, width - 50);
    /** 下記を追記 */
    score++;
    println("score: " + score);     
  }
}
```

コンソールにスコアが表示される

### スコアを実行画面に表示

```java
void setup() {
  /** 下記を追記 */
  // 黒色に塗りつぶす
  fill(0);
  // 文字の大きさを指定
  textSize(50);
  // 文字の基準点を右上に設定
  textAlign(RIGHT, TOP);
}

void draw() {
  if (dist(mouseX, 520, tanniX, tanniY) < 80) {
    tanniY = -50;
    tanniX = random(50, width - 50);
    score++;
    /** 下記を削除 */
    // println("score: " + score);     
  }
  /** 下記を追記 */
  text("score: " + score, width, 0);
}
```

### 同じコードをまとめよう

以下のメソッドを追加

```java
/** 下記を追記 */
void resetTanni() {
  tanniY = -50;
  tanniX = random(50, width - 50);
}
```

3箇所ある以下のコーディングをメソッドに置き換え

```java
  tanniY = -50;
  tanniX = random(50, width - 50);
  /** 上記を下記に変更 */
  resetTanni();
```

### タイトル画面とリザルト画面を作ろう

どの画面にいるかを変数に入れて管理

```java
/** 下記を追記 */
int state;
```

stateは下記のように管理する

```java
state == 0 // => タイトル画面
state == 1 // => ゲーム画面
state == 2 // => リザルト画面
```

### 各状態呼ばれる関数の作成

```java
/** 下記を追記 */

void titleScene() {
  
}

void gameScene() {
  
}

void resultScene() {
  
}
```

### `gameScene()`に一部処理を移動

```java
void setup() {
  /** 下記を削除 */
  textSize(50);
  textAlign(RIGHT, TOP);  
}

void draw() {
  background(backgroundImage);
  /** 下記を全て削除 */
  tanniY += 8;
  image(playerImage, mouseX, 520); 
  // （以下省略）
}

void gameScene() {
  /** 下記を追記 */
  textSize(50);
  textAlign(RIGHT, TOP);  
  tanniY += 8;
  image(playerImage, mouseX, 520); 
}
```

## 各シーンに文字を表示
```java
void titleScene() {
  textSize(70);
  // 座標の基準点をテキストの中央に設定
  textAlign(CENTER, CENTER);
  // テキストを画面中央に描画
  text("Title Scene", width / 2, height / 2);
}

void resultScene() {
  textSize(70);
  textAlign(CENTER, CENTER);
  text("Result Scene", width / 2, height / 2);
}
```

### Q.日本語バグってない？

A.フォントの読み込みが必要です

```java
void setup() {
  /** 下記を追記 */
  // サイズ50のArialフォントを生成し読み込む
  textFont(createFont("Arial", 50));
}
```

### state変数の値で分岐を行う

```java
void draw() {
  /** 下記を追記 */
  // breakのつけ忘れに注意
  switch(state) {
    case 0:
      titleScene();
      break;
    case 1:
      gameScene();
      break;
    case 2:
      resultScene();
      break;
  }
}
```

stateの初期値が0なので、起動するとタイトル画面が表示される

### キーボードの判定

#### キーが押されているか

```java
isPressed()

```
#### 入力されている文字キーの比較

```java
// 下記の2つは別扱いなので注意
key == 'a'
key == 'A'
```

#### 入力されている修飾キーの比較

```java
keyCode == ENTER
keyCode == SHIFT
```

### タイトル画面 -> ゲーム画面

Enterキーで進む

```java
void titleScene() {
  /** 下記を追記 */
  // 小さな文字でタイトルの下に表示
  textSize(30);
  text("ENTER to Start", width / 2, height / 2 + 80);
  // 押されたキーがENTERキーの時に値を変更
  if (keyCode == ENTER) {
    state = 1;
  }
}
```

### ゲーム画面 -> リザルト画面

10単位取得したらで進む

```java
void gameScene() {
  /** 下記を追記 */
  // 1回でscoreが2増えた時のために≦をしよう
  if (10 <= score) {
    state = 2;
  }
}
```

### リザルト画面

Enterキーでゲーム画面、ESCキーで終了

```java
void resultScene() {
  /** 下記を追記 */
  textSize(30);
  text("ENTER to play again / ESC to exit", width / 2, height / 2 + 80);
  // 押されたキーがENTERキーの時に値を変更
  if (keyCode == ENTER) {
    // スコアをリセット
    score = 0;
    state = 1;
  } else if (keyCode == ESC) {
    // ゲームを終了
    exit();
  }
}
```

スコアをリセットしないとループするので注意

### 実行ファイルをエクスポートしよう

「ファイル」 / 「アプリケーションとしてエクスポート」


## まとめ

Processingを、使って簡単なゲームが作れる

### ここからさらに拡張するなら…

- 制限時間をつける
- 単位を複数落とす
- 落とせない必修単位を作る

などなど…

### 振り返り

Processingは私が初めてまともにやり込んだプログラミング言語でしたが、Javaをラップしてくれているため言語的に優しく、何よりグラフィカルなアプリがすぐに作れるため楽しく学ぶプログラミング言語として優れていると思いました。