【Unity】初心者がまず最初にすることを初心者が教える
私はプログラムに関しては全くの初心者&業務でも使ったことがないのですが1ヶ月ぐらいで簡単なゲームを作ってGooglePlayで公開することが出来ました。レベルはさておき・・・
今回はどうやって私がUnityでゲームを作ったのかを書いてみたいと思います、簡単な記事です。
私が使い方をレクチャーするわけではないです・・・
Unityの最低限の知識を身につける
本当の最初は右も左も・・・というかなにから始めて良いのかわからないと思います。
Unityに関する記事は結構な数をみつけることは出来るのですが、どれも断片的でなかなか1からUnityを使えるレベルまで一貫して教えてくれるものはないのかなと思います。
そこで私がガチガチの初心者のとき最初に見たサイトを紹介したいと思います。
私はUmedyという無料の講座を使ってある程度使えるレベルまで学ぶことが出来ました。ちなみに無料です。
この講座はDLから画面の説明・ゲーム制作まであるのでかなりおすすめです。
網羅的に一連の使い方が学べると思います、無料なのがおかしい!
ただ私は他のサイトなどを使っていないので比較はできませんが、一つ一つやることなすことに説明があるのできちんと理解できるためPCサイトを見てブロック崩しみたいなのをつくるのよりも良いと思います。
自己学習できるようにする
おそらくこの講座を受けると簡単なゲームなら作れるようになっていると思います。
その後なのですが船のこぎ方を覚えたから海にでてください的な感じになっちゃいますよね。一気にモチベがなくなります。そういうときは近い島に上陸しましょう、比喩です。
私が行ったのはすごーく簡単なゲームを1から自力で制作したことです。
自力っていうのが重要で、要はネットに落ちているスクリプトをなにも考えずにつかわないってことです。
ちゃんと自分で読み解いてつかうことが重要です。
また1から作っているといろいろ小さな壁にぶつかるのですが、基本がわかっているとこのころからネットの記事が役に立ち始めます。
たとえば当たった時にオブジェクト消したいとか、時間制御したいとかいろいろですが、Unity 当たる 消す みたいに検索するとかなりの確率で記事が見つかり解決すると思います。
そうやっていくといつの間にかゲームは完成して知識もついていくというものです。
やはり実践のみで力は付いていくので簡単でもゲームを作り続けることが重要だと思います。
たぶん知識がついてくると作るゲームもいつのまにかレベルアップしていると思います。なのでUnityで始めてゲームつくって見た的なかんじですごい高度なことはしなくてもいいと思います。
おまけ
ちなみに私は転職するためにUnityを始めてから1ヶ月ぐらいしてテックアカデミーのUnity講座の8週間を受講しました。
このことはいつか記事にしたいと思いますが、正直メンターのシステムが必要ないのであれば必要ないです。今回紹介した講座で十分だと思います。
ただ割高感はいなめませんがメンターと話せるのはすごいはよかったです。
あと私が最初に作ったゲームと最近作ったクリッカーゲームのURLをのっけときます。
クオリティは気にしないでください。
Unityと相対性理論(思考実験風)
最近連続して物理的な観点でUnityに関するブログを書いてきました。
そこで気になったのがUnityのゲーム上では光の速度を越えられるのかというものです。
Unityでの最高速度
ものはためしで座標の1を1mと想定して光の速度c=3*10^8の速度以上をスクリプトで与えてみるとどうなるのかなと思ったんですが普通に出来ました。
しかしよくよく動かしているオブジェクトの座標を見てみると想定の挙動をしているかどうかという点では全然動いていませんでした。時間と距離はこんな感じです。
おおよそ5000m/sぐらいですかね。
要するにUnity上での最高速度は5000/sって言うことになりますね!(一応座標上でなので単位は/sとしています)
光の速度を越えることは出来ませんでした残念・・・
ちなみに3000/sで設定させてみると
こんな感じです、きちんと1秒で3000ぐらい動いていることができます。
やはり最高速度は5000/sってことになりそうですね。
これってもしかして個人のPCスペックによるとかありますかね?
すごいありそうな予感がします。
思考実験
ここから思考実験的なやつなのですが、
今回は座標の1を1mとしましたがたとえば1000kmだったとすると私のPC上のUnityでも光の速度を越えることができるということがいえますよね。なんだかせこいですが
それに伴ってすべてのスケールを調整してそのサイズで世界を作ると光の速度以上の動きが出来る世界が完成しますよね。
そしてその世界に物理法則を再現するとその世界はどうなってしまうのでしょうか?
AIが発達してゲーム上でも人間みたいな生物が生きるようになるとどんな世界になってしまうのでしょうか?
というものです。
これが気になって私は寝れそうにありません。
【Unity】Unityで万有引力
前回の記事でUnityにおいての重力について書かせていただきました。
現実の重力は地球の万有引力によるものなので、もっと掘り下げてみて万有引力を再現できないかなと思い今回万有引力を模したスクリプトを作成してみました。
ちなみに今回も2Dです。
はじめは月と地球を模して2点間の万有引力ということでがりがり進めていたのですが、値のスケールがとんでもないことになったり、動く速度が遅すぎたり(月の公転周期は1日なので・・・)と困りました。なので適当に値をいじりながら行いました。
結果としては月と地球、太陽と地球、宇宙ってすげぇ絶妙なバランスでできているんだなというものになりました。
万有引力について
まず万有引力についてなのですが、ざっくりいうと質量をもつすべて物体は他の物体をひきつける力をもつというものです。なので私達自身も万有引力で物体をひきつける力をもっています。でもそれは微力で現実に確認することは難しいでしょう。ただ地球というとんでもない質量をもつ万有引力は確認できます、地球の万有引力は重力という名で呼ばれている力です。
万有引力は以下の式で求めることができます。
F:力
M:物体1の質量
m:物体2の質量
r:物体1・物体2の距離
G:万有引力定数(6.67408*10^-11)
万有引力の方向は引き合う力なのでそれぞれの物体の方向になります。
また万有引力は多質点を求めるものではなく、2点間の力を求めるものなので多質点系で計算したい場合は2点の組み合わせをすべて計算する必要があります。
今回は2点質点でシーンを動かしています。
宇宙には無数の星があるのでこの時点で宇宙のバランスすごいってなりますよね。
ただ物理は自然現象を数字で証明するためのものなので物理の上で考えると難しいだけで実際は単純なことが起きているのかもしれないと妄想してしまいます。
万有引力を再現する下準備
①シーン上にオブジェクトを2つ用意
②2つのオブジェクトにRigidBodyコンポーネントを追加
(③2つのオブジェクトになにかしらColiderコンポーネントを追加)
④Edit→ProjectSettingsのPhysics2Dのgravityを0に変更
以上です。
上記の公式で力を求めることが出来るということは、
運動方程式より
質量で割れば加速度を求めることが出来ます。
そしてそこで求めた加速度をオブジェクトにrgidbody.addForceしてあげると完成です。
以下がスクリプトになります。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class UniversalGravitationController : MonoBehaviour { private float force; private float gravity; private Vector2 gravityVec; private Vector2 universalGravity; private float playerMass; [SerializeField] private GameObject obj; private float objMass; private float distance; private float G; private Rigidbody2D rb; // Start is called before the first frame update void Start() { //万有引力定数(今回はわかりやすいように変更しています) G = 6.6743f/ 100000; playerMass = GetComponent<Rigidbody2D>().mass; objMass = obj.GetComponent<Rigidbody2D>().mass; rb = GetComponent<Rigidbody2D>(); } // Update is called once per frame void Update() { //万有引力の加速度を計算 //2点の距離計算 distance = Vector2.Distance(transform.position, obj.transform.position); //万有引力計算 force = G * playerMass * objMass / distance / distance; //求めた万有引力から加速度を計算 gravity = force / playerMass; //万有引力の方向を計算 gravityVec = (obj.transform.position - transform.position).normalized; //万有引力のベクトルを求める universalGravity = new Vector2(gravityVec.x * force, gravityVec.y * force); //動かす rb.AddForce(universalGravity); } }
これを作成した2つの物体にそれぞれ追加し、インスペクタのobjに相手側のオブジェクトを入れてください。
初速を与えて公転を再現
このままでも万有引力は再現できると思います。
RigidBodyのMassの数値を弄ると挙動が変わってなかなか面白いですよね。
ここでは初速を与えることで公転っぽいことを再現させてみたいと思います。
以下のスクリプトを片側の物体に追加してください。
初速なのでstartのメソッドに書きます。
また速度はpublicにしているのでインスペクタで適当に数字を入れて挙動を試してみてください。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class MoonVelocityController : MonoBehaviour { private Rigidbody2D rb; public float velocity; // Start is called before the first frame update void Start() { rb = GetComponent<Rigidbody2D>(); rb.velocity = new Vector2(0, velocity); } // Update is called once per frame
void Update() { } }
ちなみに私の実験結果では以下のような挙動をしています。
緒言は
M=1000000
Mの座標(0,0)
m=0.1
mの座標(5,0)
mの初速v=7
(ピンクが軌跡)
計算すれば同じ軌跡を永遠にぐるぐるし続けるものが作れるのでしょうけど計算するのがめんどくさかったので、いろいろ数値をいじって物体が接触しないようにしました。
でも公転っぽいものがつくれたので個人的には満足しています。
今回作成したものの公転周期は一定ではないので、月と地球って絶妙なバランスの位置に存在しているんだなぁと思いました。(きれいに1日だから。)
またこの画像を見るとわかると思いますが、なんかぶっとんでいる時があるのがわかりますよね?でも月ってぶっ飛んでいかないちょうどいいところに存在しているんですよね、すげぇ!
また今回は2点でしたが、現実には星が無数にあるのでそれらがすべてかみ合って今が存在していると思うと感動しちゃいますね。
結論宇宙すげぇ
【Unity】Unityと重力(指定した場所に重力をスクリプトで発生させる)
今回はUnityで重力を変更する方法を紹介したいと思います。
いきなりですがUnityってすごいですよね、たとえばリンゴのオブジェクトを空中に作ってRigidbodyコンポーネントを付けるだけで現実のように落下していきますもんね。
でもGameSceneは所詮はプログラムで動く世界、現実のように万有引力があり、地球の重力でリンゴが落ちているわけではないのですよね。
そうUnity上では下に向かって現実の重力と同じ力を常に発生させているだけです。(なぜか私がこのことが非常に面白いと思ってしまいました。)
Unityで重力の確認と変更
現実の重力はg=9.8m/s^2で表現されているわけですが、Unityではどうなっているかというと・・・
タブのEdit→ProjectSettingsを押下してみてください。
ここでは2DですがPhysics2DのGravityの部分でY:-9.81と表記されていることがわかると思います。(3Dの場合はPhysicsで確認できます)
Unity上では常にY軸に-9.81(マイナスなので下方向に)の加速度を発生させて擬似的に重力を再現しているのです。
(急に加速度って何って人もいるかと思いますが、重力は加速度のことです重力加速度とも聞いたことあると思います、単位はm/s^2です。)
つまりこのGravityの数値をいじると重力を変化させることができます。
ちなみに月の重力は1.62m/s^2なので-1.62に変更すると月を再現でき、すべて0にするとゼログラビティ・・・そう無重力を再現できるということです。
(私はこれを知ったときワクワクしました。)
また無重力に関してはオブジェクトにつけているRigidBodyのuse gravityのチェックをはずすことで各オブジェクト毎に重力を発生させるかどうかを決めることも出来ます。
上記の方法ではゲームシーン全体の重力を変更させることになるので、全体で重力を制御したいのか、個別に制御したいのかで使い分けることが出来ます。
重力をスクリプトで制御する
スクリプトで変更する方法は非常に簡単で以下でできます。
Physics2D.gravity = new Vector2(X,Y); //変数XとYに設定したい重力の数値を設定(2D) Physics.gravity = new Vector3(X,Y,Z); //変数XとYとZに設定したい重力の数値を設定(3D)
おまけ(任意の方向・力で重力を発生させる)
これだけだとあまり面白くないので指定した方向と力で重力を発生させるスクリプトを作ったので紹介したいと思います。まるでアニメのキャラのように・・・ちなみに2Dでのスクリプトになります。
スクリプトの詳細としてはシーン上のプレイヤーに対してマウスクリックした場所に向かって重力を発生させるというものです。
必要となるのは重力の方向と力の大きさです。今回のスクリプトでは
方向:プレイヤーの座標からマウスクリックした座標
力 :9.8(地球と同じ)
となります。
ソースコードは以下になります。
void Update() { //左クリックしたとき if (Input.GetMouseButton(0)) { //マウスクリックしたスクリーン座標を取得 Vector2 mousePos = Input.mousePosition; //マウスクリックしたスクリーン座標をワールド座標に変換 Vector2 mouseWorldPos = Camera.main.ScreenToWorldPoint(mousePos); //プレイヤーオブジェクトを取得 Player = GameObject.Find("Player"); //プレイヤーオブジェクトのワールド座標を取得 Vector2 playerWorldPos = Player.transform.position; //プレイヤーから見たクリック場所の方向を計算 Vector2 gravityvector = mouseWorldPos - playerWorldPos; //プレイヤーから見たクリック場所の方向の正規化 Vector2 normalizedGravityVector = gravityvector.normalized; //求めた方向のXに9.8の力を加える float GravityVector2X = normalizedGravityVector.x * 9.8f; //求めた方向のYに9.8の力を加える float GravityVector2Y = normalizedGravityVector.y * 9.8f; //計算した重力を設定する Physics2D.gravity = new Vector2(GravityVector2X,GravityVector2Y); } else { //クリックしないときは無重力にする Physics2D.gravity = new Vector2(0, 0); } }
みそとなるのは2点ありまして
①マウスクリックした場所はInput.mousePositionで取得することができるのですが、スクリーン座標で取得するということです。
つまりプレイヤーはワールド座標なので方向を求めたいときは基準とする対象2つの座標をスクリーン座標かワールド座標のどちらかにそろえる必要があります。
今回はワールド座標にそろえています。
②任意の力を発生させるために求めた方向を一度正規化し、その後任意の力を掛けるということです。
難しい書き方になるのですが、①で求めたものは、プレイヤーとクリック場所をつなぐベクトル(簡単に言うと矢印を思い浮かべるといいと思います)で取得されます。
ベクトルは方向と大きさを持つものです。(矢印で言うと方向はそのまま方向、大きさは矢印の長さ)
①の方法だと方向は問題ないのですが、ベクトルの大きさは大きくなったり小さくなったりします。(プレイヤーとクリックした場所によって距離が変わるのでベクトルの大きさが変わります)なので大きさを1に一回変換して任意の力を掛けます。なんで大きさを1にする必要があるかすごく簡単にいうと、1にするとベクトルは方向のみを持ちます。(単位ベクトルといいます、単位ベクトルに変換することを正規化するといいます)
つまり大きさを1に変更(正規化し単位ベクトルに変更)し、任意の力を掛ける必要があります。
以上のことを留意するとシーン上のプレイヤーに対してマウスクリックした場所に向かって重力を発生させることができます。
重力を操るってやっぱりなんかワクワクしますよね。
応用ではありませんが、ちょっと進んでUnityで万有引力とかやって見ましたので暇ならみてやってください。個人的にはおもしろかったのですが他にもやってる方が多かった・・・w
【Unity】Unityで現実の時間を取得する方法(+現実での経過時間を計算)
現在Unityでクリッカーゲームを作っています。
そこでゲーム終了してから次の起動までの現実での経過時間をつかって何か要素を追加したいと思いいろいろ調べていました。
しかし検索してもいろんな記事を読み漁ることになってなかなかに時間がかかったのでUnityの現実時間系についてまとめようと思います。
まずそもそもなのですが、現実時間の取得はUnityの機能では取り出すことができません。(言い回しがあっているかはわかりません)
C#の機能を用いて取得を行います。
要はC#の機能を使うためにスクリプトの最初にusing Systemを宣言する必要があるということです。それだけです。
そして注意点は型(intとかstringとか)がいろいろとごちゃごちゃするので自分が今どの型で取得しているのかは留意して扱う必要があります。
では実際に現実時間の取得方法をまとめていきます。
①現在の時間の取得
DateTime awakeDateTime = DateTime.Now;
これで現在の日時が
awakeDateTime = (年,月,日,時,分,秒,1/100秒)
の形で取得できます。これはDateTime型で取得でき、調べるとすぐに見つかることではないかなと思います。
なのですがおそらくUnityで現実の時間を取得したい人はおそらくその取得した時間を用いていろいろ数字の計算をしたいのではないのではなかろうかということです。
現状であるとDateTime型なのでint型で数字として取得して計算が出来ません。
そこで
int year = awakeDateTime.Year;
とすると現在の西暦がint型で取得が出来ます。
その要領でMonth,Day,Hour,Minute・・・もそれぞれint型で取得ができます。
参考にしたブログがありますので記載します。
②経過時間の計算
現実の経過時間はTimeSpanを用いると簡単に取得ができます。
これの何がいいかというとDateTime型で取得した2つの日時をそのまま足し引きできる点にあります。
DateTime = awakeDateTime //今回のアプリ起動時間
DateTime = preDateTime //前回のアプリ起動時間
とすると
TimeSpan span = awakeDateTime - preDateTime
のように打ち込むと差分を求めることが出来ます。ただこれもまたDateTime型なので注意が必要です。
今回も
int year = span.Year;
とするとint型で取得することができます
ただこれにはもう一点重要なことがあります。
たとえば
preDateTimeが
2000年1月1日0時0分0.000秒で
awakeDateTimeが
2000年1月1日1時1分0.000秒だった場合(経過時間は1時間1分)
int minutes = span.Minute;は
minutes = 1で出力されます。
経過時間が1時間1分なのに出力は61分ではなく1分となります。
つまりそれぞれのDateTime型の同じ単位の差し引きしかしてくれないということです。
これもこれで使える場面はあると思いますが、経過時間を一つの単位(時間・分など)で取得したいですよね。
この解決策もあります。
double totalMimutes = span.TotalMinutes;
これで一発解決です。
tatalMinutes = 61で取得できます。TotalMinutesの部分をTotalHoursにすると時間が取得できます。
ただこのTotal~を使う場合にも型の注意点があります。
少数の出てくる数値になるためdouble型で取得することになるということです。
したがってint型の変数にしたいのであれば型の変換をする必要があります。
③時間の保存(PlayerPrefs)
アプリを起動した時間や終了した時間を保存する場合についてまとめます。
保存はPlayerPrefsを多くの人が使っていると思うのですが、PlayerPrefsはString型・int型・float型でしか保存できません。
つまりDateTime型のままだと保存はできないということです。
そのため、DateTime型をString型に変換して保存を行います。
PlayerPrefs.SetString("SAVEDATETIME", awakeDateTime.ToBinary().ToString());
これでString型に変換して保存できます。
取り出すときもString型であるため注意してください。
取り出すときは以下のようにするとDateTime型で再び取得取り出すことが出来ます。
string datetimeString = PlayerPrefs.GetString("SAVEDATETIME",awakeDateTime.ToBinary().ToString());
DateTime preawakedatetime = DateTime.FromBinary(Convert.ToInt64(datetimeString));
以上で終わりなのですが、実際に自分が作ったスクリプトを貼り付けておきます。
変数はフィーリングで汲み取ってもらえればと思います。
using System.Collections; using System.Collections.Generic; using UnityEngine; using System; public class RealTimeController : MonoBehaviour { // Start is called before the first frame update void Start() { //アプリ起動時の現在時刻の取得 DateTime awakeDateTime = DateTime.Now; //前回アプリ起動時の時刻をString型で取得(保存していたものを呼び出す) string datetimeString = PlayerPrefs.GetString("SAVEDATETIME", awakeDateTime.ToBinary().ToString()); //TimeSpanで計算するためにDateTime型に変換 DateTime preawakedatetime = DateTime.FromBinary(Convert.ToInt64(datetimeString)); //現在と前回の時刻の差分を分単位で計算 TimeSpan timespan = awakeDateTime - preawakedatetime; double timespan0 = timespan.TotalMinutes; //整数部を抽出 double timespan1 = Math.Truncate(timespan0); //int型に変換 int timespan2 = (int)timespan1; //分単位の差分をコインに追加 GameObject.Find("CoinController").GetComponent<CoinController>().AddCoin(timespan2); //現在時刻を前回アプリ起動時の時刻として保存 PlayerPrefs.SetString("SAVEDATETIME", awakeDateTime.ToBinary().ToString()); }
【Unity】UnityとObjectPoolについて~Instantiateを乱用するなかれ~
前つくったゾンビゲーのときにゾンビをゲーム中大量に生成していたときにメンターに言われたことがあります。
UnityではInstantiateの処理は重いから処理を意識した場合は避けたほうがいい
Instantiateで生成する場合はいろいろ初期化しながら行うからというのが理由らしいです。
つまり同じキャラ(GameObject)を何度もInstantiateで生成して、倒したらDestroyで削除を繰り返すのはよくないということです。
しかし生成は今までInstantiateで行っていたのにどうやって処理を軽くしながら生成をすればいいのってなりますよね、私はそうでした。
そこで教わったのがObjectPoolという考え方です。
これはゲームスタート時に大量にオブジェクトをInstantiateで生成しておき、非アクティブ化で格納しておく。生成したいときにアクティブ化させる、消したいときは非アクティブ化させるという手法です。
この手法では重いとされるInstantiateはゲームスタート時のみ行うのであいまあいまでInstanitiateの処理をはさまなくても良くなるわけです。
実際のスクリプトなどの作り方は他の方のブログ等を参考にするといいと思います。
私が参考にしたのは
ここらへんを参考にして導入しました。
ちなみにこれで出来ます。(たぶん)
public GameObject enemypref;
private int numOfPool = 100;
public List<GameObject> enemyPoolObjects = new List<GameObject>();
void Start()
{
GameObject obj;
for (int i = 0; i < numOfPool; i++)
{
obj = GameObject.Instantiate(enemypref);
obj.SetActive(false);
enemyPoolObjects.Add(obj);
}
}
私はObjectPoolを敵キャラの生成・死亡で使ったのですが、死亡で非アクティブにしたときにアニメーションとかいろいろ初期状態に戻すのを忘れないようにしましょう、死んだアニメーションの遷移先を作らなかったため、次生成されたとき倒れたままアクティブ化されるという笑っちゃうような現象にみまわれます。
あとなんかスクリプトをきれいに貼り付ける方法ないですかね;;
【Unity】ゾンビゲーム完成
ブログをサボっている間にunityをがんがん進めており、ゾンビゲーが完成したためアプリを公開しました。
最初に思い描いていたような機能は大体実装できたんですけどゲームってむずかしいですね、実際にやるとあんまり面白くないというwwww
ゲームバランスを考えることが仕事になることが良くわかってきました・・・
公開したゲームのURLを載せます。
もしコメントが付くようなら個別にここをこうしたなどと初心者の初心者による初心者のための回答を行っていこうと思います。
私はとあるオンラインの講座を受けたのですがそこで教わってきたことをコラム的な感じで今後書いていこうと思います。
あくまでも備忘録ということなので・・・