ぐーるらいふ

底辺。

【Unity】第7回 unity1week「当てる」参加作品「SATELLITE ONE」の開発

お疲れ様です。ぐーるです。

体調最悪のコンディションでしたが、なんとか提出することが出来ました。

思い出と実装をメモっておきたいと思います。

お題について

「当てる」かー、今回は神題だなと。
前提を考えなければアイデアはいくらでも出せそうだ、と。
デザインをほとんど必要とせずサクッと出来る、シンプルなものがいいなぁとか漠然と考えていて
降ってきたアイデアがSATELLITE ONEです。

内部設計について

いつもそうだけど物理的なunity.sceneは一つだけ。

内部的な画面は

  • OpeningScene
  • GameScene
  • ResultScene

の3つ。

各画面間の切り替えはシンプルなステートマシンで管理。

public interface IState{
	void OnStateEnter();
	void OnStateExit();
}

public class StateMachine : MonoBehaviour {

	public List<IState> states = new List<IState>();
	private IState prevState;
	private IState currState;

	public void AddState(IState state)
	{
		foreach (var st in states)
		{
			if (st == state)
			{
				return;
			}
		}
		states.Add(state);
	}

	public void RemoveState(IState state)
	{
		states.Remove(state);
	}

	public void ChangeState<T>() where T : IState
	{
		if (currState is T)
		{
			return;
		}
		if (currState != null) { 

			prevState = currState;
			prevState.OnStateExit(); // 抜ける前にOnStateExit()を呼ぶ
		}
		foreach (var st in states)
		{
			if (st is T)
			{
				currState = st;
				currState.OnStateEnter(); // 切り替わると同時にOnStateEnter()を呼ぶ
			}
		}
	}
}
/**
 * // Zenjectで紐付け
 * [Inject]
 * private MainScene _mainScene;
 * [Inject]
 * private StateMachine _stateMachine;
 * // ~~
 * _stateMachine.AddScene(_mainScene); // 起動時にStateMachineに登録しておく
 * // ~~
 * _stateMachine.ChangeScene<MainScene>(); // _mainScene画面へ切り替え
 */
public class MainScene : MonoBehavior , IState{
	//~~~~~
}

こんな感じでZenjectと組み合わせ。
楽か?って言われるとこの規模だとそこまででも無いけど
Zenject化するのが癖になってしまった。

ターゲットサイトについて

これは円形のプレーンな板をBlenderで用意しておいて、
その上で円を描くShaderを動かしています。

f:id:ghoul_life:20180226153048p:plain
f:id:ghoul_life:20180226231345g:plain

Shader "Custom/Ring" {
	Properties{
		_TargetX("Target X", float) = 0
		_TargetY("Target Y", float) = 0
		_TargetZ("Target Z", float) = 0
		_Alpha("Alpha" , float) = 0
		_Color("Color" , Color) = (0,0,0,0)
		_BG("BG" , Color) = (0,0,0,0)
	}

	SubShader{
		Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" } // alphaに対応するのに必要
		Blend SrcAlpha OneMinusSrcAlpha // alphaに対応するために必要
		LOD 200

		CGPROGRAM
#pragma surface surf Standard alpha:fade
#pragma target 3.0

		float _TargetX;
		float _TargetY;
		float _TargetZ;
		float _Alpha;
		float4 _Color;
		float4 _BG;
		struct Input {
			float3 worldPos;
		};

		void surf(Input IN, inout SurfaceOutputStandard o) {
			float dist = distance(fixed3(_TargetX, _TargetY, _TargetZ), IN.worldPos);
			float val = abs(sin(dist*3.0 - _Time * 100));
			float alpha = _Alpha;
			if (val > 0.98 && alpha > 0){
				o.Albedo = _Color;
				o.Alpha = alpha;
			}
			else {
				// ↓何でも良い
			
				//discard;
				o.Alpha = 0;
				//o.Albedo = _BG;
				//o.Alpha = _BG.a;
			}
		}
		ENDCG
	}
	FallBack "Diffuse"
}
	[SerializeField]
	private Material _ringMat;
	// Update is called once per frame
	void Update () {
		// 適当なinputのラッパーだと思って下さい。なんでもいいです。
		if(_inputManager.isTouchDownOnly(0)){
			RaycastHit hit;
			Ray ray = Camera.main.ScreenPointToRay(_inputManager.GetTouchDownPosition(0));
			if (Physics.Raycast(ray, out hit)){
				var p = hit.point;
				p.y += 0.1f;
				this.transform.position = p;
				// タッチしたポイントをShaderに渡して中心点を移動した位置にする
				_ringMat.SetFloat(Shader.PropertyToID("_TargetX") , p.x);
				_ringMat.SetFloat(Shader.PropertyToID("_TargetY"), p.y);
				_ringMat.SetFloat(Shader.PropertyToID("_TargetZ"), p.z);
			}
		}
	}

ステージ制作について

SATELLITE ONEのステージは以下の2つの要素から成る。

  • フロア

そして敵は以下の要素を持っている

  • 移動ルートポイント
  • ルートポイントごとの移動速度
  • 移動イージング

回転とかは賑やかしなので割愛。

ステージ作りがとにかく面倒だなと。
フロアはしょうがないとして、敵の移動ルートを量産出来れば、
ステージがどんどん作れそうだなと考えた。

ステージ制作画面

別でステージ製作専用のエディタを用意した。

f:id:ghoul_life:20180226231457p:plain

  1. 敵を選択して選択状態に
  2. 動かしたい所をクリック
  3. AddPointボタンを押下でルートポイントを追加

という流れでどんどんルートポイントを置いていくことが出来る。
Playで動きの確認、そしてSaveでcsv出力している。
loadももちろん完備。

ここまで必要なのか?と思ったが
結局ゲーム側でも必要な機能がほとんどだったのでついでで。

csvのロードについて

ステージを追加量産するために
Application.dataPathを利用してパスでファイルを読むようにしてたら
当たり前だけどWebGLに出力したら動かない。
(開発中は紐付けなくてもcsvファイルを新規追加読み込み可能なので便利だった)

FileInfo fi = new FileInfo(Application.dataPath + "/Resources/stage/data/" + stageId + ".csv");
var result = new List<string>();
StreamReader reader = new StreamReader(fi.OpenRead());
while (reader.Peek() > -1) {
	string line = reader.ReadLine();
	result.Add(line);
}
return result;

Editor上だけ使って実ゲームではTextAsset化して泥臭く紐付けて使うことに…。
とほほ。

[SerializeField]
private TextAsset[] _stageDatas;

おまけ

人工衛星なんですが、これはUnity標準のプリミティブオブジェクトの組み合わせで作ってます。
最初は無かったんですが、余りに味気なさすぎたので急遽追加しました。
適当に置いているだけなのですが、ちょっとゲームらしくなってくれたかなと。

f:id:ghoul_life:20180226232144p:plain

感想

後はBGMのON/OFF付けたり、Post Processing Stack付けて微妙な色合いを出したり。頭痛と闘いながら調整。

docs.unity3d.com

今回もどうにか参加できました。
何故インフルエンザなんてかかるのか!?
ちゃんと予防接種受けてるのに。

前回は可愛い感じだったので、今回は硬めに。
みなさんお疲れ様でしたー!
次は可愛めで行きたい!