マネて学ぶプログラミング!人気のゲームdiep.ioを作って学習するProcessing①~作ってみた編~
(こんなの作っていきます)
なにをしたのか
今回はプログラミング言語のProcessingを用いて、あの人気のゲームdiep.ioを作りながらプログラミングの勉強をしていきました。
プログラミングが全くわからない方でも楽しめるようにしました!diep.ioを遊んだことがある方やプログラミングに少しでも興味のある方はぜひ読んでみてください。
一番面白いところはdiep.ioっぽいものを改造するところです!後半の方にあるので興味ある方はそこまで飛ばしてもオッケー!
作ったきっかけ
趣味がプログラミングの私ですが、いざプログラミングしようと思ってもネタがないことが結構あります。
何かしらテーマを決めないと始められないので、中々プログラミングを始められないこともしばしば。
最近もそういう状態だったのですが、diep.ioをみて「これなら俺でも作れそう!」(失礼)と思ったので挑戦してみた次第です。
準備
diep.ioとは
diep.ioはブラウザだけで遊べる簡単無料オンラインゲームです。 戦車版Agar.ioとも呼ばれるゲーム。
詳しくは下の記事をご覧ください。
短ければ1プレイ数分で終わりますし、任意のタイミングでやめることも可能なので遊んだことがない方はぜひ一度やってからこの記事をみてください。
Processingとは
Javaをグラフィックスに特化させ簡単にしたようなプログラミング言語です。Javaが分かればスラスラとかけますし、プログラムが全く分からんというかたでも簡単にできるので勉強しやすいものになっています。
Processingをインストールしていない方は以下のURLからダウンロードしておいてください。
downloadで寄付するか聞かれたらNo Donationを選択してください。
今回つくったプログラム
実行画面
百聞は一見に如かず ということでまずは今回作ったプログラムの実行画面をみせます。
じゃん!!
ちなみに比較のために本家diep.ioの画面も載せておきます。
どうでしょうか。UI以外について、パッと見は凄い似てますね。(もし、公式から怒られたらこの記事は消します←)
実装した内容
今回実装したのは次の通りです~
- 三種のブロック(黄, 赤, 青)がランダムで出現させる
- 矢印キーで自機を操作できるようにした
- マウスの方に砲台が向くようにした
- 左クリックで弾を発射できるようにした
簡単に実装できるわりにはそれっぽいものになりそうなものだけ実装していきました。要はハリボテにもならないようなものです。
主な未実装の内容
- あたり判定
- スコアとレベル機能
- ブロックの動きなどのアニメーション
ゲーム性は皆無。あたり判定とScoreくらいは頑張って次回くらいに実装する。アニメーションもちょっとはつける。diep.ioの公式はすげぇなァ~
本当はp5.jsやNode.jsを用いてマルチオンライン対戦可能なものも作れるっちゃ作れて公開もできると思うんだけど、それやったらさすがにヤバイ問題になりそうなのでやめておきます。
普通に遊んでみる
今回はプログラミングやったことない方へ向けてあまり詳しい解説をせず、変更したら面白い場所だけコメントに書いておきました。(解説がめんどうだったり...でもいつか解説編つくりたい) 一番最後にずら~って書いてあるのがソースコードと言われるものですが、"//"の後ろに書いてある言葉をみていろいろ値を変更させて楽しんでみてください。
- さっきダウンロードしたProcessingを起動する
- このページの最後に記載したソースコードをすべてコピーしてProcessingの画面にはりつける
- 左上の再生ボタンっぽいやつを押す
これだけですぐ実行可能です!
(2でソースコードをはり終えたあとの状態です。あとは左上の実行ぼたんを押せば動きます。)
実行
こんな風に動いたらオッケーです。WASDキーでは動かないので注意。
改造して遊んでみる!
いよいよ一番面白いところにやってきました! プログラミングとはなんぞや、という方でも簡単に改造できる方法をのせておきますので、下のを参考にいろいろいじってみてください。
超連射
Streamlinerがこの前実装されて、超連射を見せていましたがそれ以上の連射も見せることが可能!
ソースコードの中のint reloadTime = 60;
をint reloadTime = 0;
としてみてください。探し方はCtrl-fを押すと検索画面がでるのでint reloadTime = 60;
を入力して検索ボタンを押してみてください。
変更するとこのようになります(gifファイル)↓
Reloadにかかる時間を1秒(60フレーム)から0秒にした結果です。
Streamlinerもびっくりですね。
最強の弾
今度はprivate int r = 15;
をprivate int r = 120;
にしてみましょう。
巨大な弾をうてるようになります。
まだあたり判定を実装していないので微妙ですが、こんなのが実際にあったら逃げられませんね。
最速王
スピードならだれにも負けない機体を作っていきます。
int movementSpeed = 2;
をint movementSpeed = 20;
に、int bulletSpeed = 5;
をint bulletSpeed = 30;
にします。
お使いのパソコンは正常です。一切早送りはしていません。速いのはいい事ですが操作しづらいったらありゃしない...
ダークカラー
agar.ioだと背景を黒くすることができますが、diep.ioだとできませんよね?でもこれならできるんです。
final color BACKGROUND_COLOR = color(205);
をfinal color BACKGROUND_COLOR = color(50);
に、
final color LINE_COLOR = color(189);
をfinal color LINE_COLOR = color(70);
にしてみました。
↓は画像(not gifファイル)
これくらいだと目が疲れにくくていいですよね。ちなみに自分の色も紫いろに変えてみました。final color FILL_COLOR = color(0,178,225);
⇒final color FILL_COLOR = color(155, 89, 182);
新しいブロック
最初は上の3つ同様入れる予定はありませんでしたが、せっかくなのでデフォルトで6角形が出現するようにしておきました。
割と簡単にこれくらいのブロック実装はすることができます。
おわりに
今回はスペースの関係や時間の関係でソースコードの解説をのせまんせんでしたが、もしソースコードの解説をしてほしいだとかここが分からないというコメントがあればぜひ遠慮せずコメントしてください。何とか時間を作って解説編を作っていきたいと思います。
最近diep.ioに関する記事をあげているため作った節もありましたが、作っていく中でProcessingの入門に丁度良いんじゃないかと感じるところもありました。ちまちま記事を書いていくのもありなのかもな~
とりあえず次にあげるプログラミング系の記事としては、今回のプログラムの発展形をのせていこうかなと思います。要はまた「作ってみた編」になってしまうのですが。解説編の需要があれば先にそちらをあげるかもです。
それでは、今回はここらへんで。以後はすべてソースコードになっています。
ありがとうございました。
ソースコード
下に書いてあるソースコードをProcessingに貼り付けてください。
// 下の値を変えると自分好みにゲームを変えられるよ final int MAX_BLOCK_COUNT = 30; //画面全体に表示されるブロックの最大数 final color BACKGROUND_COLOR = color(205); //背景色。現在はグレースケール。rgb値での指定も可能 ex)color(255,0,0) final color LINE_COLOR = color(189); //背景のグリッドの色。 final int GRID_INTERVAL = 25; //背景のグリッドの間隔。 final int GENERATION_TIME = 60*2; //ブロックの生成時間。今回はframeRateを60にしているので60にすると1秒かかる。現在は120なのでブロック生成に2秒かかる。 final int BULLET_EXISTENCE_TIME = 60*3; //弾の存在時間。弾を発射してから3秒後に消える。値を300に変更したら5秒間存在する。 // ここは変えないこと ArrayList<Block> allBlocks = new ArrayList<Block>(); //ブロックの情報を保持する場所 Player player; //自機 // 最初に一度だけ呼ばれる関数 void setup(){ size(800,600); //sizeの中身は幅と高さになっており、ここの値を変更することで画面の大きさを変えられる. size(800,600)だと幅が800px. 高さが600pxになります player = new Player(width/2, height/2, 30); //30は自機の直径です。ここを大きくすればプレイヤも大きくなる } // 1秒に60回呼ばれる関数 void draw( ) { drawBackground(); drawAllBlocks(); drawPlayer(); if(frameCount % GENERATION_TIME == 0 && allBlocks.size() < MAX_BLOCK_COUNT){ addBlock(); } } // 背景描画を行う関数 void drawBackground(){ background(BACKGROUND_COLOR); strokeWeight(1); //1の値を大きくするとグリッドが太くなる //下の二つのfor文2つでグリッドを描いています。 for(int i = 0; i < width /GRID_INTERVAL; i++){ stroke(LINE_COLOR); line(GRID_INTERVAL*i, 0, GRID_INTERVAL * i, height); } for(int i = 0; i < height /GRID_INTERVAL; i++){ stroke(LINE_COLOR); line(0, GRID_INTERVAL*i, width, GRID_INTERVAL*i); } } //ブロックの描画を行う関数 void drawAllBlocks(){ for(int i = 0; i < allBlocks.size(); i++){ allBlocks.get(i).display(); } } void drawPlayer(){ player.display(); } void addBlock(){ switch(int(random(4))){ case 0: allBlocks.add(new Rectangle(random(width), random(height)));break; case 1: allBlocks.add(new Triangle(random(width), random(height)));break; case 2: allBlocks.add(new Pentagon(random(width), random(height)));break; case 3: allBlocks.add(new Hexagon(random(width), random(height)));break; default: break; } } // 機体の情報を管理する大枠。変えないこと abstract class Tank { protected float x, y; protected int r; protected float angle; protected ArrayList<Bullet> bullets; abstract void display(); abstract void move(); } //自機の情報を管理する場所。ここは変えると面白いところがある。 class Player extends Tank{ //ここからしたは変えると面白い。 final color FILL_COLOR = color(0,178,225); //自機の色。color()の中の数字を変えると色が変わる。 final color BATTERY_COLOR = color(153); //砲台の色。 int movementSpeed = 2; //移動速度。値を増やせば速くなるし減らせば遅くなります。 int bulletSpeed = 5; //弾の速度。 int reloadTime = 60; //リロードにかかる時間。60なので次の発射までに1秒かかる。ここを0にすれば超速連射できます。 int elapsedTimeAfterFire = 60; //前回発射してからどれくらいの時間が経ったか保持する場所。ここは変更しないこと。 //これ以降はPlayerの中身を変えないこと Player(float x, float y, int r){ this.x = x; this.y = y; this.r = r; this.bullets = new ArrayList<Bullet>(); } //ここは変更しないこと int test = 0; boolean isBack = true; void display(){ drawBullets(); stroke(93); strokeWeight(3); pushMatrix(); translate(x,y); rotate(angle); fill(BATTERY_COLOR); rect(test,0-r/4,r, r/2,1); /*アニメーション部分*/ // if(isBack && test > -5){ // test--; // }else if(isBack && test <= -5){ // isBack = false; // }else if(!isBack && test < 0){ // test++; // }else if(!isBack && test >= 0){ // isBack = true; // } fill(FILL_COLOR); ellipse(0,0,r,r); popMatrix(); updateAngle(); move(); if(mousePressed && mouseButton == LEFT && player.elapsedTimeAfterFire > player.reloadTime){ player.shot(); } elapsedTimeAfterFire++; } void shot(){ bullets.add(new Bullet(x, y, bulletSpeed, angle, FILL_COLOR)); elapsedTimeAfterFire = 0; } void move(){ float addPos = movementSpeed; if((leftKeyDown || rightKeyDown) && (upKeyDown || downKeyDown)){ addPos = movementSpeed / sqrt(2); } if(leftKeyDown){ x -= addPos; } if(rightKeyDown){ x += addPos; } if(upKeyDown){ y -= addPos; } if(downKeyDown){ y += addPos; } } private void updateAngle(){ this.angle = atan2(mouseY - this.y, mouseX - this.x); } private void drawBullets(){ for(int i = 0; i < bullets.size(); i++){ Bullet bullet = bullets.get(i); bullet.display(); if(!bullet.isExist()){ bullets.remove(bullet); } } } } //弾の情報を保持する場所 class Bullet { float x, y; private float speedX, speedY; private int r = 15; //弾の直径。大きくしたら大きくなります。 private color fillColor; private int life; Bullet(float x, float y, int speed, float angle, color fillColor){ this.x = x; this.y = y; this.speedX = speed * cos(angle); this.speedY = speed * sin(angle); this.fillColor = fillColor; this.life = BULLET_EXISTENCE_TIME; } void display(){ fill(fillColor); stroke(93); strokeWeight(2); ellipse(x,y,r,r); move(); this.life--; } private void move(){ x += speedX; y += speedY; } boolean isExist(){ if(life < 0){ return false; }else{ return true; } } } boolean leftKeyDown, rightKeyDown, upKeyDown, downKeyDown; void keyPressed(){ if(keyCode == LEFT){ leftKeyDown = true; }else if(keyCode == RIGHT){ rightKeyDown = true; }else if(keyCode == UP){ upKeyDown = true; }else if(keyCode == DOWN){ downKeyDown = true; } } void keyReleased(){ if(keyCode == LEFT){ leftKeyDown = false; }else if(keyCode == RIGHT){ rightKeyDown = false; }else if(keyCode == UP){ upKeyDown = false; }else if(keyCode == DOWN){ downKeyDown = false; } } abstract class Block { protected float x, y; protected float angle; abstract void display(); } class Rectangle extends Block { final int WIDTH = 30; color fillColor = color(255,232,105); Rectangle(float x, float y){ this.x = x; this.y = y; this.angle = random(TWO_PI); } void display(){ fill(fillColor); stroke(93); strokeWeight(2); pushMatrix(); translate(x,y); rotate(angle); rect(0,0,WIDTH,WIDTH,1); popMatrix(); } } class Triangle extends Block { final int WIDTH = 20; color fillColor = color(252,118,119); Triangle(float x, float y){ this.x = x; this.y = y; this.angle = random(TWO_PI); } void display(){ fill(fillColor); stroke(93); strokeWeight(2); pushMatrix(); translate(x,y); rotate(angle); triangle(WIDTH+cos(0),WIDTH*sin(0),WIDTH*cos(TWO_PI/3),WIDTH*sin(TWO_PI/3),WIDTH*cos(TWO_PI/3*2),WIDTH*sin(TWO_PI/3*2)); popMatrix(); } } class Pentagon extends Block { final int WIDTH = 25; color fillColor = color(118,141,252); Pentagon(float x, float y){ this.x = x; this.y = y; this.angle = random(TWO_PI); } void display(){ fill(fillColor); stroke(93); strokeWeight(2); pushMatrix(); translate(x,y); rotate(angle); beginShape(); for(int i = 0; i < 5; i++){ vertex(WIDTH*cos(TWO_PI/5*i), WIDTH*sin(TWO_PI/5*i)); } endShape(CLOSE); popMatrix(); } } class Hexagon extends Block { final int WIDTH = 30; color fillColor = color(46, 204, 113); Hexagon(float x, float y){ this.x = x; this.y = y; this.angle = random(TWO_PI); } void display(){ fill(fillColor); stroke(93); strokeWeight(2); pushMatrix(); translate(x,y); rotate(angle); beginShape(); for(int i = 0; i < 6; i++){ vertex(WIDTH*cos(TWO_PI/6*i), WIDTH*sin(TWO_PI/6*i)); } endShape(CLOSE); popMatrix(); } }