今更感あるけど、 今年の学祭(去年になっちゃったね)で展示したものの紹介。
サークルの備品でほこりかぶってた Kinect をどうにか活用できないかなと思って、手を付けたが、初代 Kinect(v1)であったため情報が少なく、レポート等で忙しく(言い訳)、結局 2 週間程度で無理やり形に持って行った。完成度は高いとはあまり言えないのであしからず。
最初ゲームを作ろうと思ったが、なぜか当たり判定が死んで、半日程度格闘したが原因がわからなかったので別のものを作ろうと考えた。サークルの出し物がデザイン寄りなのも考えて、ジェネラティブアート的なのを作ろうと思った。
「Unity ジェネラティブアート」でググると一番上にこのサイトが出てくる。
https://ics.media/entry/19479/
これを Kinect で制御したらまあまあ面白いものができるのではと思った。 描写に関しては上のサイトとほとんど同じ…だとつまらないので、いくつかモードを追加した。
Kinect の前に人がいないとき、キャンパスに説明などを描写する待機モード。 上のサイトをもとに作った、描写モード 1。 自作した、花火みたいなエフェクトを表示させる描写モード 2。
簡単に説明すると、Kinect が人を認識していないとき待機モードになる。
人を認識すると描写モードに遷移する。現在時刻が 0-19 分の時、描写モード 1 を表示。現在時刻が 20-59 分の時、描写モード 2 を表示する。 例えば 17:09 の時、描写モード 1 を表示。10:34 の時、描写モード 2 を表示する。
描写モード 1 では、左腕の肘より左手の位置が高い時、時計回りに玉が回転する。逆に左腕の肘より左手の位置が低い時、逆時計回りに玉が回転する。
描写モード 2 では、両手を左右に動かすと玉の生成位置が移動する。X 軸(横方向)しか取得していないため上下は反応しない。
動画が残っていたため以下に添付する。 (描写モード 1 の動画はどこかに無くした…)
補足説明として、 待機モードから描写モードに移るときに来場者人数をカウントする。その際、カウントした値は Unity 内で保管されるわけではなく、localhost の PHP サーバー上に保管される。相方が実装したのでよく分からないが、インターネット環境下で外部に PHP サーバーを置けば、外部から今来場者が何人来たか確認できるらしい。サーバー上に保管することによって、一旦アプリケーションを閉じて、再度実行しても来場者数はリセットされずにカウントを続けることができる。
描写モード 1 では、ほとんどスクリプトを書いていないので、描写モード 2 について書こうと思うが、長くなりそうなので物体生成のスクリプトだけ説明する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 using System.Collections;using System.Collections.Generic;using UnityEngine;public class FlowerCircle : MonoBehaviour { public float WaitTime = 0.5f ; public float AllWaitTime = 1.0f ; public float WaitobjectTime = 0.05f ; public GameObject[] objects; public GameObject[] childobjects; public int JointNumber = 0 ; public float ObjDeleteTime; public float childObjDeleteTime; public int circleObjMax = 12 ; public GameObject BaseObj; private GameObject CloneObj; private Rigidbody[] rb; void Start ( ) { objects = new GameObject[50 ]; rb = new Rigidbody[50 ]; childobjects = new GameObject[50 ]; ObjDeleteTime = WaitTime * 11f ; childObjDeleteTime = WaitTime * 10.05f ; StartCoroutine("OnCreateCircle" ); } IEnumerator OnCreateCircle ( ) { int CircleDouble = 2 ; while (true ) { if (DataCenter.IsDetected[JointNumber] && DataCenter.GameMode == 1 ) { Vector3 basePos = BaseObj.gameObject.transform.position; for (int circleObjIdx = 0 ; circleObjIdx < circleObjMax; circleObjIdx++) { CloneObj = (GameObject)Resources.Load("MovingCreate" ); Vector3 objVec = new Vector3( CircleX(circleObjIdx, circleObjMax, CircleDouble), CircleY(circleObjIdx, circleObjMax, CircleDouble), 0 ); Vector3 objPos = basePos + objVec; objects[circleObjIdx] = Instantiate(CloneObj, objPos, Quaternion.identity); Rigidbody rb = objects[circleObjIdx].GetComponent<Rigidbody>(); rb.AddForce(objVec * 100 ); Destroy(objects[circleObjIdx], ObjDeleteTime); StartCoroutine("FadeOut" , objects[circleObjIdx]); } yield return new WaitForSeconds (WaitobjectTime ) ; for (int circleObjIdx = 0 ; circleObjIdx < circleObjMax; circleObjIdx++) { for (int circleChildObjIdx = 0 ; circleChildObjIdx < circleObjMax; circleChildObjIdx++) { Vector3 objChildVec = new Vector3( CirclechildX(circleChildObjIdx, circleObjMax, CircleDouble), CirclechildY(circleChildObjIdx, circleObjMax, CircleDouble), 0 ); Vector3 objChildPos = objects[circleObjIdx].gameObject.transform.position + objChildVec; childobjects[circleChildObjIdx] = Instantiate(CloneObj, objChildPos, Quaternion.identity); Rigidbody rbc = childobjects[circleChildObjIdx].GetComponent<Rigidbody>(); rbc.AddForce(objChildVec * 100 ); Destroy(childobjects[circleChildObjIdx], childObjDeleteTime); StartCoroutine("FadeOut" , childobjects[circleChildObjIdx]); yield return new WaitForSeconds (WaitobjectTime / circleObjMax ) ; } } } yield return new WaitForSeconds (AllWaitTime ) ; } } IEnumerator FadeOut (GameObject obj ) { Vector3 objVecSub = new Vector3(0.2f , 0.2f , 0 ); for (int i = 0 ; i < 5 ; i++) { obj.transform.localScale = obj.transform.localScale - objVecSub; yield return new WaitForSeconds (1 ) ; } } float CircleX (int circleObjNum, int circleObjMax, int Double ) { float angle = circleObjNum * 360 / circleObjMax; float x = Mathf.Sin(angle * (Mathf.PI / 180 )); return x * Double; } float CircleY (int circleObjNum, int circleObjMax, int Double ) { float angle = circleObjNum * 360 / circleObjMax; float y = Mathf.Cos(angle * (Mathf.PI / 180 )); return y * Double; } float CirclechildX (int circleObjNum, int circleObjMax, int Double ) { float angle = circleObjNum * 360 / circleObjMax + 360 / circleObjMax / 2 ; float x = Mathf.Sin(angle * (Mathf.PI / 180 )); return x * Double; } float CirclechildY (int circleObjNum, int circleObjMax, int Double ) { float angle = circleObjNum * 360 / circleObjMax + 360 / circleObjMax / 2 ; float y = Mathf.Cos(angle * (Mathf.PI / 180 )); return y * Double; } }
玉が一斉に表示されてグチャグチャにならないように、コルーチンを使って玉が生成される時間に差をつけることでアニメーションを作った。(コルーチンの変数名が適当なのは申し訳ない…)
プレイヤーの座標取得
↓
プレイヤーの取得座標を中心に円状に玉を配置して放射状に力を加える
↓
WaitobjectTime秒待つ
↓
最初に生成された玉1つを中心に玉を円状に玉を配置して放射状に力を加える。
(玉1つ生成毎にWaitobjectTime / circleObjMax秒待つ)
↓
再度、最初に生成された玉1つを中心に玉を円状に玉を配置して放射状に力を加える。
(玉1つ生成毎にWaitobjectTime / circleObjMax秒待つ)
(最初に生成された玉の数(circleObjMax)回繰り返す。)
↓
AllWaitTime秒待つ
↓
最初に戻る
文章だとわかりにくいので図で簡潔に説明してみる。
circleObjMax=3 のとき
最初、プレイヤーの取得座標を原点と仮定すると下図のようになる。
次に最初に生成された玉を中心に再度下図のように描写。
みたいな感じ… circleObjMax(玉の数)を変えても均等に配置される。
今回初めて github を使って共同制作した。複数人で 1 つの作品を制作-公開するにはすごく便利なツールだなと思った。(小並感)
他の説明は相方に任せます。良かったら見てください。詳しい説明乗ってます。Unity と Kinect で作るジェネレーティブアート:Part2 スクリプトは相方の github 上で公開してます。github:H37kouya/kinect-unity