Unityでストップウォッチを作る その6 針を作る


今回はサークル上で計測時間を指し示す針を作っていきます。

スクリプトの追加と修正

まず、Projectウィンドウに「Needle.cs」という名前でC#スクリプトを作り、以下のコードをコピーしてください。

//
// Needle.cs
//
using UnityEngine;
using System.Collections;
[ExecuteInEditMode()]
[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshFilter))]
public class Needle : MonoBehaviour {
 
 public Vector2 size = new Vector2(10.0f, 30.0f);
 public float bottomMargin = 4.0f;
 public Color color = Color.white;
 public int textureOffsetX = 0;
 public int textureOffsetY = 0;
 
 Vector2 prevSize = Vector2.zero;
 float prevBottomMargin = 0.0f;
 Color prevColor = Color.clear;
 int prevTextureOffsetX = -1;
 int prevTextureOffsetY = -1;
 
 MeshFilter meshFilter;
 
 void Start () {
   UpdateNeedle();
 }
 
 void Update () {
   
   if (IsNeedlePropertyChanged()) {
     UpdateNeedle();
   }
 }
 
 bool IsNeedlePropertyChanged()
 {
   if (size != prevSize ||
     color != prevColor ||
     bottomMargin != prevBottomMargin ||
     textureOffsetX != prevTextureOffsetX ||
     textureOffsetY != prevTextureOffsetY) {
     
     return true;
   }
   return false;
 }
 
 void UpdateNeedle()
 {
   if (meshFilter == null) {
     meshFilter = GetComponent<MeshFilter>();
   }
   
   Mesh mesh = meshFilter.sharedMesh;
   if (mesh == null) return;
   
   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 padding = 1;
   int border = 1;
   int scale = 1;
   
   float nHeight = size.y;
   float nHalfWidth = size.x * 0.5f;
   
   int drawHeight = (int)Mathf.Sqrt(nHeight * nHeight + nHalfWidth * nHalfWidth * 0.25f);
   float rad = Mathf.Atan2(nHalfWidth, nHeight);
   int drawWidth = Mathf.CeilToInt(nHeight * Mathf.Sin(rad));
   
   Vector2 drawSize = new Vector2(drawWidth, drawHeight);
   
   int texWidth = tex2d.width;
   int texHeight = tex2d.height;
   
   Rect clearRect = BSWUtility.CreateRectForClear(textureOffsetX, textureOffsetY, drawSize, padding, border, scale);
   BSWUtility.DrawRect(tex2d, clearRect, Color.clear);
   
   Rect drawRect = BSWUtility.CreateRectForDraw(textureOffsetX, textureOffsetY, drawSize, padding, border, scale);
   BSWUtility.DrawRect(tex2d, drawRect, color);
   
   float bottomRate = bottomMargin / size.y;
   Vector2[] needleUv = CreateUv(textureOffsetX, textureOffsetY, drawSize, texWidth, texHeight, padding, border, scale, bottomRate);
   
   tex2d.Apply();
   
   
   // Create Mesh
   
   float halfHeight = size.y * 0.5f;
   float halfWidth = size.x * 0.5f;
   float yBorder = Mathf.Sin(rad);
   float xBorder = Mathf.Cos(rad);
   float bottomY = - halfHeight + size.y * bottomRate;
   float bottomHalfWidth = halfWidth * bottomRate;
   
   Vector3[] vertices = new Vector3[] {
     
     // triangle 0 ~ 3
     new Vector3(0.0f, bottomY, 0.0f),
     new Vector3(- halfWidth, halfHeight, 0.0f),
     new Vector3(0.0f, halfHeight, 0.0f),
     new Vector3(halfWidth, halfHeight, 0.0f),
     
     // left border 4 ~ 5
     new Vector3(- bottomHalfWidth - xBorder, bottomY - yBorder, 0.0f),
     new Vector3(- xBorder - halfWidth, halfHeight - yBorder, 0.0f),
     
     // right border 6 ~ 7
     new Vector3(bottomHalfWidth + xBorder, bottomY - yBorder, 0.0f),
     new Vector3(xBorder + halfWidth, halfHeight - yBorder, 0.0f),
     
     // top border 8 ~ 10
     new Vector3(- halfWidth, halfHeight + border, 0.0f),
     new Vector3(0.0f, halfHeight + border, 0.0f),
     new Vector3(halfWidth, halfHeight + border, 0.0f),
     
     // bottom side 11 ~ 12
     new Vector3(- bottomHalfWidth, bottomY, 0.0f),
     new Vector3(bottomHalfWidth, bottomY, 0.0f),
     
     // bottom border 13 ~ 15
     new Vector3(- bottomHalfWidth, bottomY - border, 0.0f),
     new Vector3(bottomHalfWidth, bottomY - border, 0.0f),
     new Vector3(0.0f, bottomY - border, 0.0f)
   };
   
   int[] triangles = new int[] {
     // triangle
     11, 1, 2,
     11, 2, 0,
     0, 2, 3,
     0, 3, 12,
     // side
     4, 5, 11, 11, 5, 1,
     12, 3, 6, 6, 3, 7,
     // top
     1, 8, 2, 2, 8, 9,
     2, 9, 3, 3, 9, 10,
     // bottom
     13, 11, 0, 13, 0, 15,
     15, 0, 12, 15, 12, 14,
     // corner
     11, 13, 4,
     14, 12, 6,
     1, 5, 8,
     3, 10, 7
   };
   
   Vector2[] uv = new Vector2[] {
     // triangle
     needleUv[7],
     needleUv[1],
     needleUv[2],
     needleUv[1],
     // side
     needleUv[3],
     needleUv[4],
     needleUv[3],
     needleUv[4],
     // top
     needleUv[5],
     needleUv[6],
     needleUv[5],
     // bottom side
     needleUv[0],
     needleUv[0],
     // bottom border
     needleUv[8],
     needleUv[8],
     needleUv[9]
   };
   
   mesh.vertices = vertices;
   mesh.triangles = triangles;
   mesh.uv = uv;
   
   mesh.RecalculateNormals();
   mesh.RecalculateBounds();
   mesh.Optimize();
   
   // Keep properties
   
   prevSize = size;
   prevColor = color;
   prevTextureOffsetX = textureOffsetX;
   prevTextureOffsetY = textureOffsetY;
 }
 
 static public Vector2[] CreateUv(int originX, int originY, Vector2 squareSize, int texWidth, int texHeight, int padding, int border, int scale, float bottomRate) {
   
   float minX = (float)(originX + padding) / texWidth;
   float minY = (float)(originY + padding) / texHeight;
   float maxY = (float)(originY + padding + (border * 2 + squareSize.y) * scale) / (float)texHeight;
   
   float sqMinX = (float)(originX + padding + border * scale) / (float)texWidth;
   float sqMidX = (float)(originX + padding + (border + squareSize.x * bottomRate) * scale) / (float)texWidth;
   float sqMaxX = (float)(originX + padding + (border + squareSize.x - 1) * scale) / (float)texWidth;
   float sqMinY = (float)(originY + padding + border * scale) / (float)texHeight;
   float sqMaxY = (float)(originY + padding + (border + squareSize.y) * scale) / (float)texHeight;
   
   Vector2[] uv = new Vector2[] {
     new Vector2(sqMinX, sqMinY),
     new Vector2(sqMinX, sqMaxY),
     new Vector2(sqMaxX, sqMaxY),
     new Vector2(minX, sqMinY),
     new Vector2(minX, sqMaxY),
     new Vector2(sqMinX, maxY),
     new Vector2(sqMaxX, maxY),
     new Vector2(sqMidX, sqMinY),
     new Vector2(sqMinX, minY),
     new Vector2(sqMidX, minY),
   };
   
   return uv;
 }
}

また、既に作成している「TimeCircleController.cs」を以下の様に修正してください。

using UnityEngine;
using System;
using System.Collections;
public class TimeCircleController : MonoBehaviour {
 public AngleAnimation secCircleAngleAnimation;
 public float circleAnimDuration = 0.2f;
 
 // 追加ここから ->
 public AngleAnimation secNeedleAngleAnimation;
 public float needleAnimDuration = 0.1f;
 // <- 追加ここまで
 
 public void SetTime(TimeSpan ts, bool animate = false)
 {
   float secAngle = (float)((ts.TotalMinutes - Math.Truncate(ts.TotalMinutes)) * 360.0);
   
   if (secCircleAngleAnimation) {
     secCircleAngleAnimation.SetAngle(secAngle, animate, circleAnimDuration);
   }
   
   // 追加ここから ->
   if (secNeedleAngleAnimation) {
     secNeedleAngleAnimation.SetAngle(-secAngle, animate, needleAnimDuration);
   }
   // <- 追加ここまで
 }
}

オブジェクトの追加と設定

「SecTimeCircle」の子に「SecTimeCircle / NeedleHandle / Needle」という感じでオブジェクトを作成して配置してください。「NeedleHandle」のPositionは「X=0 / Y=0 / Z=0」にしてください。また「NeedleHandle」オブジェクトに前回使ったスクリプト「AngleAnimation.cs」を追加してください。

unitysw_6_1.png

「Needle」オブジェクトに「Needle.cs」を追加してPositionを「X=0 / Y=664 / Z=0」にしてください。また、Projectウィンドウの「Create / Empty Mesh」で新たにメッシュを作成して「Needle」オブジェクトのMeshFilterにアサインしてください。名前は「SecNeedleMesh」しておきます。「Material」も同じく「Needle」オブジェクトに追加してください。

unitysw_6_2.png

「Circles」オブジェクトの「TimeCircleController」のインスペクタに「Sec Needle Angle Animation」という項目が追加されていますので、「NeedleHandle」オブジェクトをアサインします。

unitysw_6_3.png

これでサークル上に針が表示される様になっていると思います。

解説

針は、本当は外部の画像編集ソフトで作って単純な四角形のメッシュに表示するのが単純ではあるのですが、このチュートリアルの最初に外部アセットは使わないと宣言したので、ちょっと工夫してUnityだけできれいな三角形になるように作ってみました。UVやメッシュは以下のような感じで作っています。

unitysw_6_4.png

テクスチャには長方形を描画しておいて、オレンジの線の部分を中心に三角形の左側と右側を左右対象にして組み合わせています。さらに、その周囲の水色の部分のエッジの外側にマージンをつけて補間を効かせているという感じです。

「Needle.cs」でやっている事はテクスチャへの描画とメッシュの作成なので「TimeCircle」とそんなに違わないと思います。まだ説明はしていませんでしたが「Texture Offset X」と「Texture Offset Y」というパラメータが「TimeCircle.cs」と「Needle.cs」にあります。これはテクスチャに描画するときの左下の原点の位置です。「Needle」では「Texture Offset Y」の初期値を30にしているので最初から「TimeCircle」の描画領域と被らないようになっています。普通、テクスチャアトラスを作るソフトなどでは、組み込む画像を指定すると自動的に描画する座標を良い感じで決めてくれたりしますが、ここでそれをやるのはめんどくさいので、もしサイズを変更してかぶってしまう場合は手動で位置を調整してください。

「TimeCircleController」に追加した部分は針の回転位置をAngleAnimationに渡す処理を追加しています。サークル上の計測秒数の位置に動かしていますが、逆方向にサークルが回っているので常に針は固定した位置にいるという感じになっています。

今回のWebPlayerビルド

これでようやく秒のサークルが出来上がりました。1サークル分の素材が全部そろったので、次回はこれをそのまま活用して「分」のサークルを作ります。

前へ | 次へ

コメントを残す

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