Unityでストップウォッチを作る その8 リセットボタンを作る


今回はリセットボタンを作りたいと思います。ボタンと言ってもBigStopWatchではいかにも押せるような普通のボタンではないのですが、一応ボタンと表現しておきます。個人的にはリセットボタンの無い状態の方がスマートで好きなのですが、さすがにストップウォッチの機能として単純すぎるかなと思って機能を追加しています。ただ、そういう積算できないアナログのストップウォッチというのも存在しています。

オブジェクトの作成

リセットボタンを置くスペースを作りたいのでシーンの「Root」オブジェクトのPositionのXを「-60」にしてください。Gameウィンドウで見てこのくらいでしょうか。

unitysw_8_1.png

新たにゲームオブジェクトを作成して「ResetButtonRoot」と名前を変更してください。置き場所はそのまま一番上の階層で大丈夫です。「Position」を「X = 170 / Y = 110 / Z = 0」あたりにします。

unitysw_8_2.png

「ResetButtonRoot」の子に3DTextを作成して「ResetText」と名前を変更し、インスペクタのパラメータを以下の様にしてください。

・Text > RESET
・Character Size > 4
・Font Size > 50

unitysw_8_3.png

この「RESET」の文字あたりをタップしたことを知りたいのでコリジョンを作ります。四角い領域のBox Colliderとかでもいいのですが、四角より円にしたいと思います。Sphere Colliderでも円の判定は作れそうですが、ちょっとメッシュの量が無駄な気がしますので、オリジナルのメッシュでMesh Colliderを作ります。

新たに「Button.cs」と「CircleMesh.cs」というスクリプトをProjectウィンドウの「Script」フォルダに作成してください。コードは以下になります。

//
// Button.cs
//
using UnityEngine;
using System.Collections;
public enum ButtonType {
 Background,
 Reset
}
public class Button : MonoBehaviour {
 
 public ButtonType type = ButtonType.Reset;
 
}
//
// CircleMesh.cs
//
using UnityEngine;
using System.Collections;
[ExecuteInEditMode()]
[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshFilter))]
public class CircleMesh : MonoBehaviour {
 
 public float radius = 50.0f;
 public int triangleCount = 6;
 public int textureOffsetX = 40;
 public int textureOffsetY = 0;
 
 float prevRadius = 0.0f;
 int prevTriangleCount = 0;
 int prevTextureOffsetX = -1;
 int prevTextureOffsetY = -1;
 Vector2 uvPoint;
 
 MeshFilter meshFilter;
 
 // Use this for initialization
 void Start () {
   UpdateMesh();
 }
 
 // Update is called once per frame
 void Update () {
   if (IsPropertyChanged()) {
     UpdateMesh();
   }
 }
 
 bool IsPropertyChanged() {
   
   if (radius != prevRadius ||
     triangleCount != prevTriangleCount ||
     textureOffsetX != prevTextureOffsetX ||
     textureOffsetY != prevTextureOffsetY) {
     
     return true;
   }
   
   return false;
 }
 
 void UpdateMesh() {
   
   if (meshFilter == null) {
     meshFilter = GetComponent<MeshFilter>();
   }
   
   Mesh mesh = meshFilter.sharedMesh;
   if (mesh == null) return;
   
   mesh.Clear();
   
   Material mat = renderer.sharedMaterial;
   if (mat == null) return;
   
   Texture tex = mat.mainTexture;
   if (tex == null) return;
   
   Texture2D tex2d = (Texture2D)tex;
   if (tex2d == null) return;
   
   // Draw Texture
   
   int texWidth = tex2d.width;
   int texHeight = tex2d.height;
   Vector2 size = Vector2.zero;
   int padding = 1;
   int border = 0;
   int scale = 1;
   
   uvPoint = new Vector2(
     (float)(textureOffsetX + padding) / (float)texWidth,
     (float)(textureOffsetY + padding) / (float)texHeight);
   
   Rect clearRect = BSWUtility.CreateRectForClear(textureOffsetX, textureOffsetY, size, padding, border, scale);
   BSWUtility.DrawRect(tex2d, clearRect, Color.clear);
   
   tex2d.Apply();
   
   // Create Mesh
   
   if (triangleCount < 1) {
     triangleCount = 1;
   }
   
   int verticesCount = triangleCount + 2;
   Vector3[] vertices = new Vector3[verticesCount];
   int[] triangles = new int[triangleCount * 3];
   Vector2[] uv = new Vector2[verticesCount];
   
   for (int i = 0; i < verticesCount; i++) {
     
     float phase = Mathf.PI * 2.0f / (float)verticesCount * (float)i;
     vertices[i] = new Vector3(Mathf.Sin(phase) * radius, Mathf.Cos(phase) * radius, 0.0f);
     uv[i] = uvPoint;
     
   }
   
   for (int i = 0; i < triangleCount; i++) {
     
     triangles[i * 3] = 0;
     triangles[i * 3 + 1] = i + 1;
     triangles[i * 3 + 2] = (i + 2) % verticesCount;
     
   }
   
   mesh.vertices = vertices;
   mesh.triangles = triangles;
   mesh.uv = uv;
   
   mesh.RecalculateNormals();
   mesh.RecalculateBounds();
   mesh.Optimize();
   
   prevRadius = radius;
   prevTriangleCount = triangleCount;
   prevTextureOffsetX = textureOffsetX;
   prevTextureOffsetY = textureOffsetY;
 }
}

ResetButtonRootオブジェクトの子に「ResetButton」という名前でゲームオブジェクトを作成してください。作成できたら「CircleMesh.cs」と「Button.cs」を「ResetButton」オブジェクトに追加します。

またここでも新たにメッシュを作ります。Projectウィンドウの「Create / Empty Mesh」を選択してメッシュを作成し、「Mesh」フォルダの中に「ResetButtonMesh」と名前をつけて入れてください。この「ResetButtonMesh」を先ほど作成した「ResetButton」オブジェクトのMeshFilterにアサインします。マテリアルもいままで使ってきたものと同じ「Material」をMeshRendererにアサインしてください。さらに同じく「ResetButton」オブジェクトに「Mesh Collider」を追加します。「ResetButton」オブジェクトを選択した状態で、メニューの「Component / Physics / Mesh Collider」を選択してください。

unitysw_8_4.png

「Stopwatch.cs」を以下のコードに差し替えてください。

//
// Stopwatch.cs
//
using UnityEngine;
using System;
using System.Collections;
public class Stopwatch : MonoBehaviour {
 
 enum StopwatchState {
   Zero,
   Play,
   Pause
 }
 
 public TextMesh timeText;
 public TimeCircleController timeCircleController;
 
 StopwatchState state = StopwatchState.Zero;
 TimeSpan lastStopTimeSpan;
 DateTime startDateTime;
 
 void Update () {
   
   bool circleAnim = false;
   
   if (Input.GetMouseButtonDown(0)) {
     ChangeState(ref circleAnim);
   }
   
   UpdateTime(circleAnim);
 }
 
 void ChangeState(ref bool circleAnim) {
   
   // 追加ここから ->
   ButtonType buttonType = ButtonType.Background;
     
   Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
   RaycastHit hit;
   
   if (Physics.Raycast(ray, out hit, 2.0f)) {
     Button button = hit.collider.gameObject.GetComponent<Button>();
     if (button != null) {
       buttonType = button.type;
     }
   }
   
   if (buttonType == ButtonType.Reset && state == StopwatchState.Pause) {
     // <- 追加ここまで
     lastStopTimeSpan = new TimeSpan(0);
     startDateTime = DateTime.UtcNow;
     
     state = StopwatchState.Zero;
     
     circleAnim = true;
     
   } else if (state == StopwatchState.Play) {
     
     TimeSpan ts = DateTime.UtcNow - startDateTime;
     lastStopTimeSpan = ts + lastStopTimeSpan;
     
     state = StopwatchState.Pause;
     
   } else {
     
     startDateTime = DateTime.UtcNow;
     
     state = StopwatchState.Play;
     
   }
   
 }
 
 void UpdateTime(bool circleAnim) {
   
   TimeSpan currentTs;
   
   if (state == StopwatchState.Play) {
     
     TimeSpan ts = DateTime.UtcNow - startDateTime;
     currentTs = ts + lastStopTimeSpan;
     
   } else {
     
     currentTs = lastStopTimeSpan;
     
   }
   
   if (timeText != null) {
     
     timeText.text = ConvertTimeSpanToString(currentTs);
     
   }
   
   if (timeCircleController != null) {
     
     timeCircleController.SetTime(currentTs, circleAnim);
     
   }
   
 }
 
 static public string ConvertTimeSpanToString(TimeSpan ts) {
   
   if (ts.Hours > 0 || ts.Days > 0) {
     return string.Format("{0}:{1:D2}:{2:D2}.{3}", ts.Days * 24 + ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds.ToString("000").Substring(0, 2));
   } else {
     return string.Format("{0}:{1:D2}.{2}", ts.Minutes, ts.Seconds, ts.Milliseconds.ToString("000").Substring(0, 2));
   }
 }
}

これで、ストップウォッチを停止中にRESETの文字付近をクリックorタップしたときにはゼロに戻り、その他の領域をタップしたときには続きを再生する様になります。

解説

「Button.cs」クラスは特に機能がある訳ではなく、どの種類のボタンかのタグを付けているだけのものです。ボタンの種類はButtonTypeというenumでを作って表す様にしました。今回のチュートリアルのように単純な場合はUnity標準機能のタグを使ってもいいのですが、もしボタン以外にも何かタグを付けて判別をしたくなった場合に1種類のタグを使い回すのは良くないかなと思い、このように別のものを作っています。

「Stopwatch.cs」に追加したのは、クリックした位置がRESETに当たったかどうかを判定するコードです。ストップウォッチの停止中にRaycastがRESETのコリジョンに当たって、そのオブジェクトについている「Button」コンポーネントのButtonTypeが「Reset」である場合にリセットするというような処理です。

Input.mousePositionでクリックしたスクリーン上の位置を取得して、表示しているMain CameraでRaycastを飛ばしています。カメラの奥行きは-1〜1なのでRaycastの距離は2にしています。

今回のWebPlayerビルド

これで計測時間が積算できる様になりましたが、RESETが機能しないときも表示しっぱなしなので、次回はストップウォッチのステートの状態に合わせてRESETの表示・非表示を切り替える様にしたいと思います。

前へ | 次へ

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です