(この記事の使用環境: Unity5.4.2f2 Personal、Windows10


今回は実験した内容のメモです。

球体(Sphere)の色が揃ったら消します。落ちものパズル的な。

やってみたのがこんな感じ。

 
概要
  • 球体9個(縦3個×横3個)を上から一定間隔で降らせる
  • 隣接する球体(Sphere)の色が揃ったら消える
  • 球体はマウスクリックでランダムに色を変えることができる
  • 球体が消える際にパーティクルでエフェクトを付ける

材料はこんな感じ。
「Cubeほげほげ」っていうのがありますが、最初は球体じゃなくてCube(立方体)で作ろうとしてたので、その名残です。Cubeだと斜めに並んでいるオブジェクトにも接触してしまうため、のちに扱う対象を球体に変更しましたが、各種素材はそのまま使いまわしたため、名前がCubeのままになっています。

全画面キャプチャ 20170103 114527
 
各素材の概要
  • ColorCubeMaterial(Material): 球体の色を設定するためのマテリアル
  • ColorSphere(Prefab): 球体の設定。Material、Rigidbody、スクリプト等がアタッチされています。
  • Spheres(Prefab): 球体を9個(縦横3×3)並べたもの。これを一定間隔で降らせます
  • ParticleEffect(Prefab): 球体が消えるときに表示するエフェクト
  • Main(Scene): シーン
  • CubeController(Script): 球体の振る舞い。色を変える処理と、色が一致したら消す処理
  • DestroyByTime(Script): 一定時間でオブジェクトを消す。パーティクルオブジェクトにアタッチ
  • Generator(Script): 一定時間ごとにSpheresを降らせる
  • PlayerController(Script): マウスクリックで球体を選択し、球体の色を変える処理を呼び出す

ColorSphereのRigidbody。回転したり前後左右に位置がずれないよう、制約を付けています。
全画面キャプチャ 20170103 115719

で、CubeControllerスクリプトの内容。
前半。初期化とコルーチン呼び出し。

using UnityEngine;
using System.Collections;

public class CubeController : MonoBehaviour
{

    Material mat;
    Rigidbody rb;

    public float destroyTime;
    public ParticleSystem particle;

    void Start ()
    {
        mat = GetComponent<MeshRenderer> ().material;
        rb = GetComponent<Rigidbody> ();
        ChangeColor ();
    }

    public void ChangeColor ()
    {
        StartCoroutine (ChangeColorMain ());
    }


後半。色を変える処理と、色が一致したら消す処理。
色を変える処理では、対象の球体のスケールをいったん縮小して、その後もとに戻す処理を入れています。衝突判定を再度行わせるためと、見た目に分かりやすくするためです。実は今回のポイントかも。
消す処理ではパーティクルを生成していますが、パーティクルが消すオブジェクトと同じ色になるようにしています。

    IEnumerator ChangeColorMain ()
    {
        rb.useGravity = false;
        transform.localScale = new Vector3 (0.8f, 0.8f, 0.8f);

        yield return new WaitForSeconds (0.1f);

        int key = Random.Range (0, 6);
        switch (key)
        {
            case 0:
                mat.color = Color.blue;
                break;
            case 1:
                mat.color = Color.cyan;
                break;
            case 2:
                mat.color = Color.green;
                break;
            case 3:
                mat.color = Color.magenta;
                break;
            case 4:
                mat.color = Color.red;
                break;
            case 5:
                mat.color = Color.yellow;
                break;
        }
        transform.localScale = new Vector3 (1f, 1f, 1f);
        rb.useGravity = true;
    }

    private void OnCollisionEnter (Collision collision)
    {
        if (collision.gameObject.tag == "ColorCube")
        {
            if (mat.color == collision.gameObject.GetComponent<MeshRenderer> ().material.color)
            {
                particle.startColor = mat.color;
                GameObject ptcl = Instantiate (particle, collision.transform.position, transform.rotation) as GameObject;

                Destroy (gameObject, destroyTime);
                Destroy (collision.gameObject, destroyTime);
            }
        }
    }
}

これはPlayerControllerスクリプトの内容。
Rayで球体を選択し、ヒットした球体のChangeColor() を呼び出しています。

using UnityEngine;
using System.Collections;

public class PlayerController : MonoBehaviour {

	void Update () {

        	if (Input.GetMouseButtonDown (0))
        	{
            		Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
            		RaycastHit hit;
            		if(Physics.Raycast(ray, out hit))
            		{
                		hit.collider.gameObject.GetComponent<CubeController> ().ChangeColor ();
            		}
        	}	
	}
}

で、これはDestroyByTimeスクリプト。パーティクルオブジェクトにアタッチし、一定時間で消えるようにしています。これをしておかないとパーティクルオブジェクトが残り続けて処理負荷がどんどん高くなってしまいます。
Unity公式チュートリアルのSpaceShooterからいただいたアイデアです。簡単ですね。

using UnityEngine;
using System.Collections;

public class DestroyByTime : MonoBehaviour {

	public float lifeTime;

	void Start () {
        	Destroy (gameObject, lifeTime);	
	}
}

最後にGeneratorスクリプト。タイマー処理でSpheresを生成しているだけ。

using UnityEngine;
using System.Collections;

public class Generator : MonoBehaviour
{

    public GameObject spheres;

    public float span;
    float _timer = 0f;

    void Update ()
    {
        _timer += Time.deltaTime;

        if (_timer > span)
        {
            GenerateSpheres ();
            _timer = 0f;
        }
    }

    void GenerateSpheres ()
    {
        GameObject newObject = Instantiate (spheres, new Vector3 (0f, 5f, 0f), transform.rotation) as GameObject;
    }
}

今回はなんとなくやってみたかった実験をしてみました。
以上です。