Processingでタイピングゲームを作ろう！

## Processingとは

Javaをベースにした言語で、グラフィックを使ったプログラミングが簡単に作成できます。
私は、Javaのグラフィックを使うのが苦手だったので、Processingに感動を覚えました。

ここでは、私が作成した簡単なタイピングゲームの作り方を説明していきます。

できるだけ細かく説明していくので、プログラミングが苦手な人でも作れると思います。
(if文やfor文など最低限のプログラミングの知識はあるものとして書きました。)

作るものはこれです！
↓　↓　↓

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Processingでタイピングゲームを作りました！ <a href="https://t.co/fF56CFCQdF">pic.twitter.com/fF56CFCQdF</a></p>&mdash; まゆ (@Mayu_snba19) <a href="https://twitter.com/Mayu_snba19/status/1258648461949321218?ref_src=twsrc%5Etfw">May 8, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 事前に読んだ本

田中賢一郎著『[ドリル形式で楽しく学ぶ Processing-Java](https://www.amazon.co.jp/%E3%83%89%E3%83%AA%E3%83%AB%E5%BD%A2%E5%BC%8F%E3%81%A7%E6%A5%BD%E3%81%97%E3%81%8F%E5%AD%A6%E3%81%B6-Processing-Java-Future-Coders%EF%BC%88NextPublishing%EF%BC%89-%E7%94%B0%E4%B8%AD-%E8%B3%A2%E4%B8%80%E9%83%8E-ebook/dp/B07KKBS2BS/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=Processing+java&qid=1588925553&sr=8-1)』を半分くらい読みました！
サクサクゲームが作れるのでかなりおすすめです。(2020/5/8現在 kindle unlimitedで読めます。)

## Processingのインストール方法

私はこちらの記事を参考にさせて頂きました。
[Processingの導入方法 - Qiita](https://qiita.com/greatnekochan/items/010237ab3313757d0b0b)

## データ
私の下手な絵で申し訳ないのですが、こちらの4枚を保存してください。
こんなダサいイラスト使いたくないよ！！と言う人は適当に画像を探してくるか、自分で描いてください。

![back.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505211/3092cb27-8831-5d36-474f-0bdf345634f7.png)
back.PNG 900×600ピクセル

![cat.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505211/6ac7eff4-43cf-c272-65ec-3d416c959dd1.png)
cat.PNG 200×133ピクセル

![endneko.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505211/dd937ad9-d089-cf7c-db78-4a5550b4435a.png)
endneko.PNG 900×600ピクセル

![sakana.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505211/4b21c2cf-9e3e-2705-2a8d-51871afb5595.png)
sakana.PNG 900×600ピクセル


## Processingの起動

開いたら、<b>ファイル→名前を付けて保存</b>を押して保存してしまいましょう。
名前はなんでも良いです。私はtypingと名前を付けました。

typingフォルダが作られ、その中にtyping.pdeが作られます。(このtyping.pdeを編集していくことになります。)

<img width="766" alt="スクリーンショット 2020-05-08 17.53.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505211/a483c3c3-6889-b694-1a12-edafaaf62fec.png">

この隣に新たにdataフォルダを作って、その中に先ほどの4枚のイラストを保存しましょう！

<img width="770" alt="スクリーンショット 2020-05-08 17.57.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505211/b4e9190b-2fa3-2d25-b186-6c437b702816.png">

## 基本的な命令

まず以下のように入力してください。(//以下の一文はコメントなので書かなくても大丈夫です。)

```java

//最初に呼ばれる関数
void setup(){
    size(900,600); //900×600pxのウィンドウを作成する
}

//1秒間に60回呼び出される関数
void draw(){

}

//キーボードが押されたら呼ばれる関数
void keyPressed() {

}
```
- size(横の長さ,縦の長さ)

これを実行してみると(▶︎っぽいボタンを押してください！)ウィンドウが出てくると思います。<b>setup関数</b>は実行した時に最初に一度だけ呼び出される関数です。その中にウィンドウを生成するsizeを書きました。

その下に書いたdraw関数は、1秒間に60回呼び出されるものです。この関数の中に表示させる位置を変えながらオブジェクトを表示していくことでアニメーションになります！

一番下のkeyPressed関数はキーボードが押されると反応して呼び出されます。


## イメージを読みこもう！

次にこのようにしてください。

```java

//ここで変数名を宣言することによって、どの関数でも使えるようになります。
PImage back; //<--new 背景
PImage cat; //<--new 下の方でちょこちょこ動いてる猫ちゃん
PImage fish; //<--new ミスタイプしたときに表示される予定の魚
PImage cat2; //<--new 最後の結果の画面で出てくる猫ちゃん

//最初に呼ばれる関数
void setup(){
    size(900,600);
    //イメージを読み込んで、変数にいれていきます。
    back=loadImage("back.PNG"); //<--new
    cat=loadImage("cat.PNG"); //<--new
    fish=loadImage("sakana.PNG"); //<--new
    cat2=loadImage("endneko.PNG"); //<--new
}

//1秒間に60回呼び出される関数
void draw(){
    image(back,0,0); //<--new 背景を表示する
    image(cat,frameCount*5-100,400); //<--new 移動する猫ちゃん
    if(frameCount>=180) frameCount=0; //<--new 猫が右に到達したら左から再スタートする
}

//キーボードが押されたら呼ばれる関数
void keyPressed() {

}
```
画像はPImage型で宣言します。
setup関数の中で、loadImage関数を用いて画像ファイルを読みこんで代入しています。

実際に画像を表示しているのは、draw関数の中のimage関数となります。

`image(表示する画像,x座標,y座標)`

で画像を表示することができます。
移動する猫の画像以外は900×600pxで作成したので、表示位置は0,0でOKです！

移動する猫はimage(cat,frameCount*5-100,400)と書きました。
frameCountというのは、drawが呼び出されるたびに数字が1ずつ増えていくものです。
これが180以上になったら0にする、とすることで、猫が右に到達したらリスタートされます。


## 文字列を表示する

ここでは、タイピングする文字を表示します。

かなり増えましたが、次のように記述してください。

```java
PImage back;
PImage cat;
PImage fish;
PImage cat2;
//<--new タイピングに使う文字
String letters[]={"Java","PHP","Javascript","Python","C++",
"C#","COBOL","Swift","Brainfuck","Go","Haskell","Julia",
"Kotlin","Mathematica","Perl","Processing","R","Ruby","Rust"}; 
String letter; //<--new  現在の文字列
int now=0; //<--new 現在の文字の位置

//最初に呼ばれる関数
void setup(){
    size(900,600); 
    back=loadImage("back.PNG"); 
    cat=loadImage("cat.PNG");
    fish=loadImage("sakana.PNG");
    cat2=loadImage("endneko.PNG");
    letter=letters[(int)random(letters.length)];
    textSize(50);
    textAlign(CENTER); //<--new テキストを中央揃えにする
}

//1秒間に60回呼び出される関数
void draw(){
    image(back,0,0);
    image(cat,frameCount*5-100,400);

    text(letter,450,200); //<--new 文字列を表示する
    //<--new これから打つ一文字を表示する
    text("now : "+letter.charAt(now),450,260);
    
    if(frameCount>=180){ //3秒毎に文字列を変える
        letter=letters[(int)random(letters.length)]; //<--new lettersの中のランダムな一文字を取得する
        now=0; //<--new 文字位置を0に戻す
        frameCount=0;
    }
}

//キーボードが押されたら呼ばれる関数
void keyPressed() {
    if(key==letter.charAt(now)){ //<--new 正しいタイピングだったら
        now++; //<--new 文字位置を増やす(次に進める)
        if(now==letter.length()){ //<--new 現在の文字列を打ち終わったら
            
          letter=letters[(int)random(letters.length)]; //<--new lettersの中のランダムな一文字を取得する
          now=0; //<-- new 文字位置を0に戻す
          frameCount=0; //<-- new 猫が左側に戻るようにする
        }
    }
}
```

`letter=letters[(int)random(letters.length)];`で、letters配列に格納したランダムな１文字を取り出してletterに入れています。randomはfloat型なので、int型に変換してあげましょう。

`random(数字)`　・・・　0〜数字-1をランダムに1つ取得する


テキストを表示するには、text関数を使います。

`text(文字列,x座標,y座標)`

nowという変数で現在の文字位置を管理します。
KeyPressed関数の中で正しいタイピングだったらnowを1増やすようにしましょう。
nowが文字列の長さと等しくなったら、random関数で文字を変えます！


KeyPressedの中でもframeCountを0にしているのは、猫ちゃんの位置で現在の文字列の残り時間がわかるようにしたからです。(猫ちゃんが右に到達すると次の文字列になる)

## ゲームっぽくする

ここまでのコードでタイピングはできるようになりました。
ここからスコアや制限時間、ミスタイプ数などを表示させたいと思います！

```java
PImage back; 
PImage cat;
PImage fish; 
PImage cat2;
String letters[]={"Java","PHP","Javascript","Python","C++",
"C#","COBOL","Swift","Brainfuck","Go","Haskell","Julia",
"Kotlin","Mathematica","Perl","Processing","R","Ruby","Rust"}; 
String letter; 
int now=0; 
int time=1800; //<--new 時間
int missCount=0; //<--new ミスタイプ数
int cnt=0; //<--new 連続タイプ(10ごとに0になる)
boolean error=false; //<--new 間違えたかどうか
int errorCount=0; //<--new 間違い画像を表示する回数
int score=0; //<--new スコア
boolean continuous=false; //<--new 連続タイプができたかどうか
int continuousCount=0; //<--new タイム延長のテキストを表示する回数

//最初に呼ばれる関数
void setup(){
    size(900,600); 
    back=loadImage("back.PNG"); 
    cat=loadImage("cat.PNG");
    fish=loadImage("sakana.PNG");
    cat2=loadImage("endneko.PNG");
    letter=letters[(int)random(letters.length)];
    textSize(50);
    textAlign(CENTER);

    //<--new 日本語のフォントにする
    PFont font = createFont("Osaka",50);
    textFont(font);
}

//1秒間に60回呼び出される関数
void draw(){
    image(back,0,0); 
    image(cat,frameCount*5-100,400); 

    if(error){//<--new ミスタイプをしたら呼び出される。
        image(fish,0,0); //<--new ミスタイプ時に呼び出される画像
        errorCount++; //<--new この部分が呼び出される回数を数える
        if(errorCount>=30) { //<--new 0.5秒間画像を表示する
            error=false; //<--new ミスはなかったことに...
            errorCount=0; //<--new 回数は0に戻しておきましょう
        }
    }
    fill(255,255,255);
    text(letter,450,200);
    text("now : "+letter.charAt(now),450,260);

    if(time<=0) {//<--new タイムが0になったら
        image(back,0,0); //<--new 背景を読み込み直す
        fill(255,255,255); //<--new テキストの色を白にする
        text("SCORE:"+score+"  ミスタイプ数 :"+missCount,450,200); //<--new結果のテキストを表示する
        image(cat2,0,0); //<--new　大きい方の猫を表示する
        noLoop(); //<--new　draw関数の動きが止まる
    }

    // 残り時間を四角形で表示する
    fill(141,217,163); //<--new 色を設定
    noStroke(); //<--new 縁の色はなしにする
    rect(0,0,time/2,20); //<--new 四角形を書く命令

    if(continuous){
        fill(0,212,166); //<--new テキスト色の変更
        text("PLUS",800,80); //<--new テキストを表示
            continuousCount++; //<--new 何回表示されたかを記録
        if(continuousCount>=50){ //<--new PLUSを表示するのは50回まで！
            continuousCount=0; //<--new 回数を0に戻す
            continuous=false; //<--new 呼び出されなくする
        }
    }

    if(frameCount>=180){
        letter=letters[(int)random(letters.length)]; 
        now=0; 
        frameCount=0; 
    }
    time--; //<--new 残り時間を減らす
}

//キーボードが押されたら呼ばれる関数
void keyPressed() {
    if(key==letter.charAt(now)){
        cnt++; //<--new 連続タイプ数を増やす
        score++; //<--new スコアを増やす
        now++;
        if(now==letter.length()){
          letter=letters[(int)random(letters.length)];   
          now=0; 
          frameCount=0; 
        }
        if(cnt>=10){ //<--new 連続タイプが10以上になると
              cnt=0;
              time+=60; //<--new タイムが1秒増える
              continuous=true; //<--new 連続タイプできたことを伝える
          }
    }else if(keyCode==SHIFT){//<--new シフトキーはミスではない
        cnt++; //<--new 正しいタイピングなので数える
    }else{ //<--new ミス
        missCount++; //<--new ミスタイプを数える
        cnt=0; //<--new ミスすると連続タイプ数が0に戻る
        error=true; //<--new 間違えたことを伝える
    }
}
```

間違えたときには、最初に読み込んだ魚の画像を使いたいので、error(boolean型)をtrueにして、間違ったことをdraw関数のif(error)の部分に伝えます。

draw関数の一回呼び出しだけでは、魚の画像が一瞬で消えてしまうので、しばらく残るようにしましょう。ここでは、30回呼び出されるまで消えないようにしました！(30回までは呼び出される。その後errorをfalseにして間違いがなかったことになる。)

連続タイプも、ミスタイプと同じようにしばらく表示されるようにします。

テキストや、文字の色を変えるには、fill関数を使います。fill関数の引数はRGBです。
fill関数は一度設定すると、もう一度設定するまで同じ色が使われます。

`fill(red,green,blue)`

また、日本語の文字列が表示できるようにしたいので、setup関数でPFont font = createFont("Osaka",50);と記述しました。



## サウンドを設定しよう

残念ながらサウンドについては、このゲームを作り始めてから知ったのでまだあまり知識がありません...

という訳で[Processing で音を扱う - 小学校教員のためのプログラミング入門](http://wiki.bmoon.jp/wiki.cgi/Programming?page=%B2%BB%A4%F2%BB%C8%A4%A6)
を参照してください。

ここにも書いてあるのですが、minimを使えるようにするためには<b>スケッチ→ライブラリをインポート→ライブラリを追加</b>からminimをインストールする必要があります！

音源は、
[The circus - フリーBGM](https://dova-s.jp/bgm/play12732.html)　こちらをBGMに、
[猫の鳴き声(3種) - フリーBGM](https://dova-s.jp/se/play419.html)　こちらを間違えた時の効果音に、
[生活(1)- 効果音ラボ](https://soundeffect-lab.info/sound/various/) のレジスターで精算を連続タイプの効果音に使用させて頂きました。

これらの音源をダウンロードして、dataフォルダにいれると読み込めるようになります！

## 完成！！

```java
import ddf.minim.*; //<--new 
Minim minim; //<--new 
AudioPlayer nekosound; //<--new 
AudioPlayer backsong; //<--new 
AudioPlayer money; //<--new 
PImage back; 
PImage cat; 
PImage fish;
PImage cat2;
String letters[]={"Java","PHP","Javascript","Python","C++",
"C#","COBOL","Swift","Brainfuck","Go","Haskell","Julia",
"Kotlin","Mathematica","Perl","Processing","R","Ruby","Rust"}; 
String letter; 
int now=0; 
int time=1800;
int missCount=0;
int cnt=0;
boolean error=false; 
int errorCount=0;
int score=0;
boolean continuous=false;
int continuousCount=0;

//最初に呼ばれる関数
void setup(){
    size(900,600);
    back=loadImage("back.PNG"); 
    cat=loadImage("cat.PNG");
    fish=loadImage("sakana.PNG");
    cat2=loadImage("endneko.PNG");

    //サウンドの読み込み
    minim = new Minim( this ); //<--new 
    backsong = minim.loadFile( "back.mp3" ); //<--new 
    nekosound=minim.loadFile("nekosound.mp3"); //<--new 
    money=minim.loadFile("money.mp3"); //<--new 

    letter=letters[(int)random(letters.length)];
    textSize(50);
    
    PFont font = createFont("Osaka",50);
    textFont(font);

    textAlign(CENTER);

    //BGMを流す
    backsong.play(); //<--new 
}

//1秒間に60回呼び出される関数
void draw(){
    image(back,0,0); 
    image(cat,frameCount*5-100,400);

    //ミスタイプをしたら呼び出される。
    if(error){
        image(fish,0,0);
        errorCount++; 
        if(errorCount>=30) { 
            error=false;
            errorCount=0;
        }
    }
    fill(255,255,255); 
    text(letter,450,200);
    text("now : "+letter.charAt(now),450,260);

    if(time<=0) { 
        image(back,0,0); 
        fill(255,255,255); 
        text("SCORE:"+score+"  ミスタイプ数 :"+missCount,450,200); 
        image(cat2,0,0);
        noLoop();
        backsong.close(); //<--new 
    }

    //残り時間を四角で表示する
    fill(141,217,163); 
    noStroke(); 
    rect(0,0,time/2,20); 

    
    if(continuous){
        fill(0,212,166);
        text("PLUS",800,80); 
        continuousCount++;
        if(continuousCount>=50){ 
            continuousCount=0;
            continuous=false;
        }
    }

    //3秒経ったら文字列を変える
    if(frameCount>=180){
        letter=letters[(int)random(letters.length)]; 
        now=0;
        frameCount=0; 
    }
    time--;
}

//キーボードが押されたら呼ばれる関数
void keyPressed() {
    if(key==letter.charAt(now)){
        score++;
        now++; 
        cnt++;
        if(now==letter.length()){
          letter=letters[(int)random(letters.length)];
          now=0;
          frameCount=0; 
        }
        if(cnt>=10){
              cnt=0;
              time+=60;
              continuous=true;
              money.rewind(); //<--new 
              money.play(); //<--new 
          }
    }else if(keyCode==SHIFT){
        cnt++; 
    }else{
        missCount++; 
        cnt=0;
        error=true; 
        nekosound.rewind(); //<--new 
        nekosound.play(); //<--new 
    }
}

//<--new 
void stop(){
  nekosound.close(); //<--new 
  backsong.close(); //<--new 
  minim.stop(); //<--new 
  super.stop(); //<--new 
}
```
これで完成です！！
(コンソールにJavaSound Minim Errorが出ていると思います。再生自体には問題はないのですが、気になる方は[音楽の再生準備を行う（minim編）](http://mslabo.sakura.ne.jp/WordPress/make/processing%E3%80%80%E9%80%86%E5%BC%95%E3%81%8D%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9/16124-2/)を参照してください。)

lettersの中にたくさん文字列を追加したらもう少しタイピングゲームっぽくなると思います。(日本語には対応していません💦)

Processingは使い始めたばかりなので間違っているところ等ありましたらコメントお願いします！