ソースはこちらで公開しています。
改良版: https://github.com/matsushima-terunao/unity_toride

実際のゲームはこちら
PC 改良版: http://ter.dip.jp/blog/unity_toride/build/build.html
Android 改良版: https://github.com/matsushima-terunao/unity_toride/raw/master/build/toride.apk

爆破エフェクトの変更

爆破エフェクトは Particle System から作成しましたが、用意されている素材を利用するように修正します。
 Project タブ内の Assets を右クリック -> Import New Package -> Particles
 Importing package ダイアログで Import をクリック
 Assets -> Standard Assets -> Particles -> Fire -> Fire1 を Hierarchy にドロップ、さらに Project -> Assets にドロップして Prefab 化、Hierarchy -> Fire1 は削除
 Fire1 配下の OuterCore, InnerCore, smoke に対して、Inspector -> Ellipsoid Particle Emitter に Min Size, Max Size, Ellipsoid の値を3倍にし、大きくする



爆破効果音の追加

爆破エフェクト発生時に効果音を鳴らすようにします。
 wav ファイルを用意し、Project -> Assets にドロップ
 Fire1 に Inspector 上で Add ComponentAudio Source, Audio Listener を追加
 Audio Source -> Audio Clip に Assets に追加した効果音をドロップ



地形の自動生成

スクリプトで、ゲームが始まるたびに地形を自動生成するようにします。
ソースはこちら
https://github.com/matsushima-terunao/unity_toride/blob/master/Assets/terrainScript.js

スクリプトは Hierarchy -> Terrain に関連付けます。Inspector タブ上に表示されるスクリプト内変数のうち以下のものを関連付けます。
 Particle Prefab: Project -> Assets -> Fire1
 Player: Hierarchy -> cannon(自分側)
 Other: Hierarchy -> cannon(相手側)

スクリプトで地形の高さデータを変更するには、高さデータの配列を作成し、TerrainData.SetHeights() に渡します。
高さデータの配列は Start() で TerrainData.GetHeights() て取得しています。
地形データはフラクタルアルゴリズムで作成しています。プログラムは Oh!X 1995年5月号の記事を参考にしました。
/** 変更前の高さマップ */
var heightsOrg: float[,];
/** 高さマップ */
var heights: float[,]; // 高さマップ

function Start () {
	var terrain: Terrain = gameObject.GetComponent(Terrain);
	var w: int = terrain.terrainData.heightmapWidth;
	var h: int = terrain.terrainData.heightmapHeight;
	Debug.Log("w=" + w + ",h=" + h);
	heightsOrg = terrain.terrainData.GetHeights(0, 0, w, h); // 変更前の高さマップ
	heights = terrain.terrainData.GetHeights(0, 0, w, h); // 高さマップ
}

/**
 * 初期化。
 */
function Init() {
	var terrain: Terrain = gameObject.GetComponent(Terrain);
	var w: int = terrain.terrainData.heightmapWidth;
	var h: int = terrain.terrainData.heightmapHeight;
	Debug.Log("w=" + w + ",h="  + h);
	fractal(512, 1.5, 0.3, heights);
	terrain.terrainData.SetHeights(0, 0, heights);
}

地形を削る

砲弾が地形に当たったときに地形を削るようにします。
衝突イベント内で、衝突地点を中心に高さ配列のデータを変更します。なお、Inspector 上で Pixel Error を 1 にしておかないと、周辺の地形にも影響が出てしまいます。
また、範囲内に砲台があったときにヒットするようにします。
function OnCollisionEnter(collision: Collision) {
	MainScript.Log("terrain collision", "terrain collision: " + collision.gameObject.name + "," + collision.contacts[0].point);
	if ("ball(Clone)" == collision.gameObject.name) {
		Destroy(collision.gameObject);
		// 破壊エフェクト。
		var particle: GameObject = Instantiate(particlePrefab, collision.transform.position - Vector3(0, 5, 0), Quaternion.identity); // インスタンスの生成
		particle.tag = "Respawn";
		Destroy(particle, 3);
		// 穴を掘る
		var terrain: Terrain = gameObject.GetComponent(Terrain);
		var R: int = 5; // 穴の半径
		var D: float = 0.002; // 穴の深さ
		var w: int = terrain.terrainData.heightmapWidth;
		var h: int = terrain.terrainData.heightmapHeight;
		var mapX: int = collision.contacts[0].point.z * w / terrain.terrainData.size.x; // heightmap 上の座標
		var mapZ: int = collision.contacts[0].point.x * h / terrain.terrainData.size.z; // heightmap 上の座標
		var mapR: int = R * w / terrain.terrainData.size.z; // heightmap 上の座標
		// z -> x
		var z1: int = Mathf.Max(-mapR, -mapZ);
		var z2: int = Mathf.Min(mapR, -mapZ + h - 1);
		for (var z: int = z1; z <= z2; ++ z) {
			// x -> y
			var mapW: int = Mathf.Sqrt(mapR * mapR - z * z);
			var x1: int = Mathf.Max(-mapW, -mapX);
			var x2: int = Mathf.Min(mapW, -mapX + w - 1);
			for (var x: int = x1; x <= x2; ++ x) {
				heights[x + mapX, z + mapZ] = Mathf.Max(0, heights[x + mapX, z + mapZ] - D * Mathf.Sqrt(mapW * mapW - x * x));
			}
		}
		terrain.terrainData.SetHeights(0, 0, heights);
		// ヒット。
		for (var cannon: GameObject in [player, other]) {
			if ((cannon.transform.position - collision.transform.position).magnitude
					< cannon.transform.FindChild("Sphere").transform.localScale.x / 2 + R) {
				cannon.GetComponent(cannonScript).Hit();
			}
		}
	}
}

視点の変更

マウス、タッチ操作で視点を変えられるようにします。
赤枠内で、クリック、タッチ位置が Player 砲台上かを判定します。砲台上のときは従来の砲台操作、それ以外の場合は視点操作にします。
青枠内で、ホイール、ピンチ量をズーム値とします。
緑枠内で実際の視点の変更を行います。
 地形の中心(50, 0, 50)からカメラへのベクトルを求める
 横方向の角度、縦方向の角度、長さの極座標に変換
 横移動量、縦移動量、ズーム値をそれぞれ加算
 再度直交座標に変換し、カメラの位置を更新
 カメラを地形の中心(50, 0, 50)に向ける
	var inputResolution: float = 90.0 / Mathf.Min(Screen.width, Screen.height); // 操作回転量: 画面短辺 -> 90度
	var inputDistance: Vector3 = Vector3.zero; // 入力移動量
	var fire: boolean = false; // 発射
	if (gameObject.CompareTag("Player")) {
		// 自分
		// mouse drag: 移動量を求める
		var mousePosPrev: Vector3 = mousePos;
		mousePos = Input.mousePosition;
		if (mouseDown) {
			inputDistance = mousePos - mousePosPrev;
		}
// mouse down if (Input.GetMouseButtonDown(0)) { mouseDown = true; // player 上判定 var hit: RaycastHit; var ray: Ray = Camera.main.ScreenPointToRay(mousePos); mouseOnPlayer = (Physics.Raycast(ray, hit, 100) && (gameObject == hit.collider.gameObject || hit.collider.transform.IsChildOf(transform))); }
// mouse up: 発射 if (Input.GetMouseButtonUp(0)) { fire = mouseOnPlayer; mouseDown = false; }
// mouse wheel: ズーム inputDistance.z = Input.GetAxis("Mouse ScrollWheel") * 10;
// touch var touchPosPrev: Vector3 = touchPos; if (1 == Input.touchCount) { var touch:Touch = Input.GetTouch(0); switch (touch.phase) {
// touch down case TouchPhase.Began: touchDown = true; touchPos = touch.position; // player 上判定 ray = Camera.main.ScreenPointToRay(mousePos); touchOnPlayer = (Physics.Raycast(ray, hit, 100) && (gameObject == hit.collider.gameObject || hit.collider.transform.IsChildOf(transform))); break;
// touch swipe: 移動量を求める case TouchPhase.Moved: touchPos = touch.position; inputDistance = touchPos - touchPosPrev; break; // touch up: 発射 case TouchPhase.Ended: fire = touchDown; touchDown = false; touchPos = touch.position; if (fire) { inputDistance = touchPos - touchPosPrev; } break; }
} else if (Input.touchCount >= 2) { // touch pinch: ズーム touchDown = false; var touch0:Touch = Input.GetTouch(0); var touch1:Touch = Input.GetTouch(1); if (TouchPhase.Began == touch1.phase) { touchPos.z = Vector2.Distance(touch0.position, touch1.position); } else if (TouchPhase.Moved == touch0.phase || TouchPhase.Moved == touch1.phase) { touchPos.z = Vector2.Distance(touch0.position, touch1.position); inputDistance = Vector3(0, 0, -(touchPos.z - touchPosPrev.z) / 10); } }
// mouse or touch 移動量 -> 回転 // 縦方向: x軸回転 // 横方向: y軸回転 if (Vector3.zero != inputDistance) { if (mouseOnPlayer || touchOnPlayer) { var angle: Vector3 = transform.eulerAngles - Vector3(inputDistance.y, inputDistance.x, 0) * inputResolution; angle.x = Mathf.Max(0, Mathf.Min(80, angle.x)); angle.y = Mathf.Max(80, Mathf.Min(190, (angle.y + 180) % 360)); // -100..10 angle.y = (angle.y + 180) % 360; transform.eulerAngles = angle; MainScript.Log("eulerAngles", "eulerAngles=" + angle + "," + transform.eulerAngles); } else {
// 地形の中心からカメラへのベクトル var terrainPos: Vector3 = Vector3(terrain.terrainData.size.x / 2, 0, terrain.terrainData.size.z / 2); var pos: Vector3 = Camera.main.transform.position - terrainPos; // 極座標に変換 var a: float = Mathf.Atan2(pos.z, pos.x); var b: float = Mathf.Atan2(pos.y, Mathf.Sqrt(pos.x * pos.x + pos.z * pos.z)); var c: float = Mathf.Sqrt(pos.x * pos.x + pos.y * pos.y + pos.z * pos.z); // 回転、ズーム a -= inputDistance.x * inputResolution / 30; b = Mathf.Max(0, Mathf.Min(Mathf.PI / 2.1, b - inputDistance.y * inputResolution / 30)); c = Mathf.Max(terrainPos.x, c + inputDistance.z); // 直交座標に変換 pos.x = c * Mathf.Cos(b) * Mathf.Cos(a); pos.z = c * Mathf.Cos(b) * Mathf.Sin(a); pos.y = c * Mathf.Sin(b); Camera.main.transform.position = pos + terrainPos; Camera.main.transform.LookAt(terrainPos);
} }

→ 3-1. Unity でユニティちゃんアクションゲームを作る - キャラクターの配置
← 2-2. Unity で砲台ゲームを作る - スクリプトの作成
↑ 一覧