概要(2019/08/24)
勉強会の活動においてモブプログラミングで1つのミニゲームを完成させます。勉強会の参加者もそこそこいますので、複数のチームに分かれてモブプログラミングにトライします。初心者から上級者まで、教えてもらったり逆に知らないことを教えたりしながら取り組む企画です。今回はミニゲームの企画を考える時点で、既存のアプリを真似てみることにしました。
そして対象となるゲームはFruit Ninjaになります。下から出てきた果物をフリックで切っていくという何ともバタ臭いゲームであります!
【Youtubeの参考動画】(クリックするとYoutubeに移動)
ゲーム企画段階の講師からのコメント
フリックで消したりするのがハマりそうだからちょっと厳しいかもしれないとのことでした。そして私達のチームはこう思ったのです。「なめんなよ!!」と。そんなわけで果敢にも企画を変更せずに突進することになりました。
なんとか無事に完成
なんとか完成させることができました。企画の決定が早かったのと処理実装における意思決定が早かったのが功を奏したと思います。
例えば「果物が浮き上がってくるのはどうする?」と悩んだりすると、個人個人でいろんなアプローチが出ては消えてモブプログラミングで回していくと進まない現象が発生します。
そこに「これをこういう風に作って組み合わせると実現できるぴょん」という助言があると方針が定まって非常にやりやすくなってきます。ここは講師の方が良い助言をしたと思います。ニンテンドースイッチで遊んでいるぴょんぴょん野郎ではなかったわけですね。
ちなみに作成したゲーム画面は以下のようなカンジです。スクリーンショットの連射だとフリック操作が映らないので動画からGIFを生成しました。
結構イイカンジに作成できたと思います。フリック入力も時間制限が設けてあり一定の間隔しかフルーツが切れないようになっています。込み入った処理内容については以降に記載します。
処理内容
フルーツが飛び出す処理
以下のコードがフルーツが飛び出すスクリプトになります。そこまで複雑な処理ではないですが簡単に説明を書いていきます。
オブジェクトの向きを調整する
ゲームオブジェクトで「Center」という名前のオブジェクトを見つけて変数に格納しています。この「Center」というオブジェクトに対し、アタッチされたオブジェクト(自分自身のオブジェクト)の向きを変更します。「Center」と名前指定している部分は変数・定数にした方が良いですね。
向きが変わったら「AddForce」でその向きに力を加えます。力の加え方も注意が必要で瞬間的な力が掛かる「ForceMode2D.Impulse」にしています。意外に忘れがちなのが「2D」な部分です。普段から「3D」に慣れていると「3D」の記述でしてしまい「2D」だと動いてくれません。
他のオブジェクトを参照するので2つセットの組み合わせになって若干ドン臭いです。
あと、「Update」には徐々にゆっくり回転していく処理が書いており自然なカンジにしてます。ちょっとした気遣いを入れてイイカンジですね。
このスクリプトをフルーツのオブジェクトにアタッチしてプレハブ化で準備しておきます。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FruitsMove : MonoBehaviour
{
public float power = 15f;
private int rotation;
Rigidbody2D rb2d;
// Start is called before the first frame update
void Start()
{
rb2d = gameObject.GetComponent<Rigidbody2D>();
GameObject targetObj = GameObject.Find("Center");
var vec = (targetObj.transform.position - gameObject.transform.position).normalized;
gameObject.transform.rotation = Quaternion.FromToRotation(Vector3.up, vec);
rb2d.AddForce(transform.up * power, ForceMode2D.Impulse);
int x = (int)gameObject.transform.position.x * 10;
rotation = x;
Destroy (gameObject, 30.0f);
}
// Update is called once per frame
void Update()
{
transform.Rotate(new Vector3(0, 0, rotation) * Time.deltaTime);
}
}
フリックしてフルーツを消す
以下の「EffectControl」がフリックしたときに表示される小さな白丸のオブジェクトになります。タッチした軌跡にオブジェクトが生成される見た目から「Effect」という名前になっています。これで生成されたオブジェクトがフルーツに接触することでフルーツを削除するわけです。
あと消したオブジェクトをスコアとして計算したいため「GameControl.cutCnt」という「static」な変数にインクリメントしています。
こちらも上記のフルーツ同様にプレハブ化して準備しておきます。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EffectControl : MonoBehaviour
{
private int targetNum = 10;
private int cutCnt;
// Start is called before the first frame update
void Start()
{
Destroy(gameObject, 0.1f);
}
// Update is called once per frame
void Update()
{
}
private void OnTriggerEnter2D(Collider2D other) {
GameControl.cutCnt +=1;
Debug.Log(GameControl.cutCnt + " : " + targetNum);
if (GameControl.cutCnt >= targetNum) {
Debug.Log("Game Clear !!");
}
Destroy(other.gameObject);
}
}
以下の「GameControl」は上記でプレハブ化した「Effect」のオブジェクトをフリックした位置に生成します。「timeElapsed」と「timeAppear」などでフリックした時間がある程度経過したらオブジェクトが生成されないようにしています。この処理がないとタッチしている間はずっとエフェクトが出るのでゲーム性が下がってしまいます。
ちなみに上記のタッチ時間経過の処理は1人の方がザーっと勢いで仕上げました。まさに神が降臨した瞬間でノリにノッてたカンジです。あとそれは講師の方でないことをお伝えしておきます。
少しイケていない部分はゲームクリアやスコアの制御部分も賄っているので、今の状態だとそこまでではないんですが、いろいろ処理を入れすぎて肥大化しそうな気配はあります。分けすぎるのも微妙ですが、そこらへんは意識していくことが大切ですね。あと、「Update」は何か使うだろうというミンナの気持ちが消すことを忘れさせたようです。こういうのも若干モブプロの弊害かもしれませんね。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameControl : MonoBehaviour
{
static public int cutCnt;
public Sprite[] fruitsImgList;
public GameObject fruits;
public GameObject effect;
public float timeAppear;
private float timeElapsed;
public float cutTime = 0.5f;
private float cutTimeElapsed;
private GameObject appearObj;
// Start is called before the first frame update
void Start()
{
cutCnt = 0;
}
// Update is called once per frame
void Update()
{
timeElapsed += Time.deltaTime;
if(timeElapsed >= timeAppear){
appearObj = Instantiate(fruits, new Vector3(Random.Range(-4.5f, 4.5f), -10, 0), Quaternion.identity);
appearObj.GetComponent<SpriteRenderer>().sprite = fruitsImgList[Random.Range(0,fruitsImgList.Length)];
timeElapsed = 0.0f;
}
if(Input.GetMouseButtonDown(0)){
cutTimeElapsed = 0.0f;
}
if(Input.GetMouseButton(0)){
if(cutTimeElapsed <= cutTime){
cutTimeElapsed += Time.deltaTime;
Vector3 screenPos = Input.mousePosition;
Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);
worldPos.z = 0;
Instantiate(effect, worldPos, Quaternion.identity);
}
}
}
}
総評
今回このチームはみんなノッていたカンジです。最初に講師の方がナメたカンジがみんなの意識を高揚させたみたいですね。少ない時間で1人ずつ交代交代でプログラミングしていくのでソースを整理したりするのが、あとに回しになって最終的に残ってしまっていることは反省点です。
ただ、フリックの時間間隔の処理やフルーツの向きを変えて飛ばす処理などは行き詰まることなくスムーズに実装できていました。これまでのモブプログラミングの経験値や、情報の探し方なども上手くなってきたように思います。