インドネシアのどこか
みなさんこんにちは🌚
最近日本酒が臭く感じなくなってぐびぐび飲めるようになってきてしまったブルーノです。別に一人で飲む機会が多いわけではないので友達と飲むときしか影響はないのですが自分の体が恐ろしくなってきました笑
さて今回は、ゲーム内の時間の概念とその扱い方についてやっていきたいと思います。
ゲームの時間管理
ゲームの中の時間は我々の世界とは違い、フレーム単位で進んでいきます。じゃあそれは何秒なの?と言われるとこれまた設定次第なので一概に何秒とは言えませんが、とにかく我々の1秒と同じではありません。そのフレームの設定から逆算すれば1秒をゲーム内で測れますが、毎回時間を扱う際にそのゲームと我々の時間の比率を計算するのはめんどくさいですよね。そこでUnityEngine.Timeクラスのtimeプロパティを使うことで、起動後から呼び出されるまでの時間を我々の時間で測ってくれます!
UnityEngine.Timeクラス
public sealed class Time
timeプロパティ
public static float time { get; }
逆にフレームを測りたい場合は同じくTimeクラスのframeCountプロパティを使用します
frameCountプロパティ
public static int frameCount { get; }
ちょっとみてみましょう
7行目でまず座標を作り、22行目でTimeクラスからそれぞれframeCountとtimeプロパティを使っています。結果は以下の通りです
フレームと我々の時間の進みはやはり全然違うようですね。
ゲーム内の時間はフレーム単位で進むことはわかりましたが、では我々の時間基準でオブジェクトを動かしたい場合や進行させたいときは時間をただ刻み続けているtimeプロパティじゃあまり意味がありませんね。そこで使われるのがdeltatimeプロパティです。このdeltatimeプロパティは呼び出される直前のフレームから現在のフレームまでにかかった時間を我々の時間単位に直してfloat型で返してくれます。つまりめっちゃ早いです。なんでそれで時間が測れるんだよって思いますがつまり、1秒が60フレームのゲームなら1/60の値を返すので、1秒に動いてほしい値にかけることでその1/60の値になります。それを1秒間に60回かけるのでうまく1秒にその値が収まるという理屈だそうです。厳密には違うそうです笑
では例を見ていきましょう
これをCubeにつけます
およそ1秒で一周していますね。室伏みたいですね。このdeltatimeプロパティは今後ゲームにしろアプリにしろ必須になってきそうですね。
フレームレートの設定
UnityはUpdateメソッドが毎回呼び出され更新することでゲームが動いてるように見えます。このUpdateメソッドが1秒間に呼び出される回数のことをフレームレートと言います。この回数はモニタのスペックに影響され、その画面が1秒に描画できる回数分がデフォルトでは設定されます。この画面の1秒に描画できる回数を垂直同期周波数(カッコイイ)というのですが、この設定を変更することもできます。エディタ画面上部の[Edit]から[Project Setting]の[Quality]を開きます
インスペクターにQuality Settingが表示されます。
このメニューの下側「Other」の欄のVSync Countから変更できます。
デフォルトの「Every V Blank」は垂直同期周波数のことを表します。「Don't Sync」は垂直同期しないことを表し、「Every Second Sync」はEvery V Blankの半分の回数になります。
基本的に垂直同期しないという設定は使いませんがEvery Second Syncのように更新回数を減らせばモバイル向けアプリの動作を軽くしたりできます。
処理の分割 - コルーチン
RPGゲーなどの場合、次のマップの読み込みやエフェクトの描画、入力の受付など、様々な処理が同時に行われてますが、もし1フレームに次のマップの読み込み全てを行わせていたらとてもじゃないですがPS4のオープンワールドなんかは時間がかかってしまいます。マップの読み込みだけでなく、派手なエフェクトの描画なども処理が遅れれば処理落ちし、それがひどいとストレスがすごいですよね。そこでUnityでは、C#がもともと持っているコルーチンという機能を使ってその作業を何フレームにも分割させられるそうです。コルーチンは関数の途中で処理を中断し、その関数から抜け、再度呼び出された時に中断した部分から処理を行える機能です。
コルーチンはC#の「IEnumeratorインターフェイス」で表されます。インターフェイス懐かしいけど難しかった😓これとyield return文を使うことで書くことができます。
そしてIEnumeratorインターフェイスを呼び出すにはStartCoroutine()メソッドを使います。
StartCoroutine()メソッド
public Coroutine StartCoroutine(IEnumerator routine);
routineの場所にIEnumeratorインターフェイスを記述します。
手順としては
IEnumeratorインターフェイスを記述
↓
その中の中断するポイントでyield return文を書く
↓
StartCoroutine()メソッドで呼び出す
となります。実際にちょっと書いてみます
13行目〜30行目にIEnumeratorインターフェイスのRotateRoutineを記述し、その中にyield return null文を入れています。nullは「空っぽ」のことで、特に何もアクションをせずにここで処理を中断するのですが他にも色々なアクションをここで起こすことができます。例えばここに
StartCoroutine() →別のコルーチンに入る
WaitForSeconds() →一定時間待機する。
といったメソッドを入れることができます。とても便利!
さてこのスクリプトではfor文の中で「Vector3変数の r 文だけ回転するという処理を90回繰り返し」for文を抜け、向きを90度変えてcount変数に+1するというのを繰り返すものになっており、count変数が4になった場合初めて文が終わる仕組みになっています。コルーチンは関数の状態を保持してくれるのでこの中でcount変数を宣言しても再び呼び出したときにリセットされないし、rの中身もしっかり保持されていますね。
結果は以下のようになります
4回向きが変わったところで止まりました。Startメソッドの中で書いたものがずっと動き続けたり中身がずっと保持されてるなんて現実世界の考え方にすごく近くなっていますね。これだけわかればますます直感的にゲームをみんなが作れるようになるんじゃないか....?
ほんとはコルーチンはそれ単体で記事を書く必要がありそうなくらい便利で奥深そうですが今回はここまでにしておきます。
今年2018年のUnityカンファレンスイベントの早割チケットを実は購入しました!ちっともスキルも伴ってない実戦経験皆無の素人ですがたくさん刺激をもらっておこうと思います😄それまでは勉強だ😤