プログラム経験はそこそこあるものの、ゲーム開発経験は薄い私が、Unity の初心者向けチュートリアル はじめてのUnity を今日一日で、えいやっ!と試してみたときのメモです。
私の悪い癖で、変に細かいところばかり気になり、横道にばかり反れますが… まあ、そんな面倒な初心者もいるんだな、などと、生温く見守っていただければ幸いです。ほんの少しでも、新しい興味のきっかけや、何かの参考になることがあれば嬉しいです。
インストール
Unity の ダウンロードサイト から Free 版をインストールします。Windows 用の Unity 2017.4.0f1 Personal (64bit)。最初の起動で Unity アカウントの新規登録もできました。
プロジェクトの作成
最初のページ プロジェクトを作成する から順に実施していきます。
「シーンを保存する」でファイルが作成されたようなので、一応、確認してみます。
拡張子が .unity なファイルの中身は YAML 形式のようですね。念のためバックアップをとっておきますw
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!29 &1
OcclusionCullingSettings:
m_ObjectHideFlags: 0
serializedVersion: 2
m_OcclusionBakeSettings:
smallestOccluder: 5
smallestHole: 0.25
backfaceThreshold: 100
m_SceneGUID: 00000000000000000000000000000000
m_OcclusionCullingData: {fileID: 0}
# (以下略)
ステージを作成
2ページ目の ステージを作成 に進み、「3D Obejct」の「Plane」を作成します。
初の「GameObject」の作成!ですね。記念してさきほどの stage01.unity ファイルを見て、追加されたものを見てみましょう。かなりの情報が増えています…
--- !u!1 &52521118
GameObject:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
serializedVersion: 5
m_Component:
- component: {fileID: 52521122}
- component: {fileID: 52521121}
- component: {fileID: 52521120}
- component: {fileID: 52521119}
m_Layer: 0
m_Name: Plane
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!23 &52521119
MeshRenderer:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 52521118}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_Materials:
- {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_PreserveUVs: 1
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 0
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
--- !u!64 &52521120
MeshCollider:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 52521118}
m_Material: {fileID: 0}
m_IsTrigger: 0
m_Enabled: 1
serializedVersion: 3
m_Convex: 0
m_CookingOptions: 14
m_SkinWidth: 0.01
m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0}
--- !u!33 &52521121
MeshFilter:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 52521118}
m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0}
--- !u!4 &52521122
Transform:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 52521118}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
細かな値はわかりませんが、ざっと眺めると以下のことに気がつきます。
- 今回追加した GameObject の ID は 52521118 らしい
- MeshRenderer/MeshCollider/MeshFilter/Transform という4オブジェクト(?)も同時に作成されたらしい
- これら4オブジェクトは m_GameObject に元の GameObject の ID を保持しているらしい
- 逆に元の GameObject は m_Component にこれら4オブジェクトの ID を保持しているらしい
そしてアプリ右側の Inspector をみると、作成した GameObject に対して、その下にこれら4つに対応した設定欄があることに気がつきます。なるほど、って感じですね。
さて、「四枚のPanelを設定」まで作業を進めましょう。Panel はマウスでおおまかに移動した後、右側の Inspector で Position の数値で整列・微調整するほうが楽ですね。
Panel が3枚追加されたことにより、stage01.unity ファイルの記述はだいぶ増えています。まあ同じ Panel なので大差はないでしょうから、最初に追加した Panel の項目の変化だけ見ておきましょう。
--- !u!1 &52521118
GameObject:
m_Name: Ground1
--- !u!4 &52521122
Transform:
m_LocalPosition: {x: 5, y: 0, z: -5}
最初に追加した Panel は名前を「Ground1」に変え、位置を移動しました。その変化が stage01.unity ファイルにダイレクトに反映されていますね。わかりやすくて好感がもてます。
床の色を塗る
さて「床の色を塗る」に進みましょう。Materialファイルを作成すると、mat ファイルというのが新しく作成されました。
さきほど 3D Object の Panel を追加するときには「HierarchyブラウザでCreate」で、これは Scene のファイルに追記されました。それに対して今回の Material は「ProjectブラウザでCreate」で、こちらは別ファイルが作成されました。この動作の違いは要注意、な気がします。
作成した mat ファイルは、以下のように stage01.unity ファイルから参照されているようです。ファイル名でなく ID で管理されているようですね。
--- !u!23 &52521119
MeshRenderer:
m_Materials:
- {fileID: 2100000, guid: 37412096bf5a48d4f98e58da292888ab, type: 2}
なお床のテクスチャの選択の際に Default-Checker を選択しますが
これは Unity ツールのインストール先にある、以下のリソースの中にあるようです。
壁の作成
さて、壁の作成はこれまでの延長ですから、サクサク進めます。こんなに簡単にステージが作成できるなんて、嬉しいですねぇ。
オブジェクトの整理
引き続き、「オブジェクトの整理」にはいります。
Projectブラウザでフォルダを作って整理すると、実際のファイル階層もそれに連動します。わかりやすくていいですよね。
それに対してHierarchyビューで「Stage」オブジェクトを作成して配置すると、例によって、Scene ファイル(stage01.unity) の記述が変更されます。
以下が実際に追加された記述ですが、empty オブジェクトだけあって、GameObject と Transform しか作成されないようです。そして Transform の m_Children には下に配置したオブジェクトのIDが並んでいますね。
--- !u!1 &34882342
GameObject:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
serializedVersion: 5
m_Component:
- component: {fileID: 34882343}
m_Layer: 0
m_Name: Stage
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &34882343
Transform:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 34882342}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0.03723812, y: -7.349492, z: 1.7287483}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 52521122}
- {fileID: 1947452124}
- {fileID: 2126360140}
- {fileID: 1472673936}
- {fileID: 637992096}
- {fileID: 895024129}
- {fileID: 1933621100}
- {fileID: 870633282}
m_Father: {fileID: 0}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
また Stage の下に移動したオブジェクトの記述も以下の部分が変更されています。親の指定が 0 だったのが上記の stage の Transform の ID になり、また(親の下での)並び順が変化しています。
--- !u!4 &52521122
Transform:
m_Father: {fileID: 34882343}
m_RootOrder: 0
※ Parent ではなく Father なんだ… ちょっと珍しいw
Transform は画面上の配置だけでなく、Hierarchyビューにおける配置場所も指定している、のが面白いところです。
このページの最後に「Stageを静的なオブジェクトとして配置する」を実施します。Static 化を実施すると ファイルのほうでは、0 だった以下のフィールドに値が設定されるようです。
--- !u!1 &52521118
GameObject:
m_StaticEditorFlags: 4294967295
同時に設定した Stage および配下の GameObject の m_StaticEditorFlags に 4294967295 という数値が設定されますが、この数は16進数だと32ビットの FFFF FFFF になるので、まあフラグ値なのでしょう。
プレイヤーの移動
3ページ目の プレイヤーの移動 に進み、「3D Obejct」の「Sphere」を作成します。
物理モデルを追加することで再生すると落下するようになりますね。この物理モデルですが、例によってシーンファイルには以下のように追記されました。
--- !u!54 &1736258364
Rigidbody:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 1736258359}
serializedVersion: 2
m_Mass: 1
m_Drag: 0
m_AngularDrag: 0.05
m_UseGravity: 1
m_IsKinematic: 0
m_Interpolate: 0
m_Constraints: 0
m_CollisionDetection: 0
設定画面とセーブファイル、対応がわかりやすくていいですね。ちょっとの修正であれば、テキストエディタで実施できちゃうかも?
プレイヤーを任意の方向へ転がす機能の追加
キタ!スクリプトの追加です。今回のチュートリアルでは C# で記述していますが、私は JavaScript のほうが得意なので、あえて UnityScript で記述してみます。
あれ、C# しか選べないぞ…
…ググってみると、Unity 2017.2あたりからサポート外になった模様。なんてこった、Unityを選んだ最大の理由が消えちまったぜ!
まあ C# も少し経験あるので、古の記憶を呼び覚ましつつ、まずはコピペで進めましょう(ぉぃ)
最初に表示されたコードは以下ですが、説明に従い、さくっと空にします。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
}
そしてコードを追加していくわけですが、
と Public 変数を追加しただけで、
Inspector 上に自動で設定欄が表示されて連動するだなんて、便利な時代になりましたなー (ジジィ的感想)
とまあこれで再生すると、カーソルキーやゲームパッドで Player の球を動かすことができるようになりました。いい感じで進んでいます。
カメラを動かす
4ページ目の カメラを動かす に進みましょう。
このページはコードをそのまま利用しただけなのですが、以下の Target 欄に Player オブジェクトを設定するところで、(Transform) と表示されているのが興味深かったです。
Payer は GameObject だと単純に思っていたのですが、ここで渡されているのは、それに含まれる Transform のほうなのですね。FollowPlayer の C# コード内でクラスの指定があるので納得の動作なのですが、変数定義をみてちゃんと値を渡しているのが地味に凄いな、と。いやほんと、便利な時代に… (以下略)
ちなみに Target 欄に関しては当然ですが、Scene ファイル(stage01.unity) に追記されます。
--- !u!114 &606333442
MonoBehaviour:
target: {fileID: 1736258363}
ここで ID に指定されている 1736258363 は、Player に関連付けられた Transform の ID です。
アイテム回収の追加
5ページ目の アイテム回収の追加 に進みましょう。
prefab 機能について
ここで新しく出てきたのが「HierarchyビューのItemを選択し、ProjectブラウザのPrefabフォルダへドラッグ&ドロップ」する操作です。
結果として Item.prefab ファイルが生成され、その中身は以下になります。これまでオブジェクトを作成するごとに Scene ファイル (stage01.unity) に追加してきた定義と同じようにみえます。
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1001 &100100000
Prefab:
m_ObjectHideFlags: 1
serializedVersion: 2
m_Modification:
m_TransformParent: {fileID: 0}
m_Modifications: []
m_RemovedComponents: []
m_ParentPrefab: {fileID: 0}
m_RootGameObject: {fileID: 1309955381917574}
m_IsPrefabParent: 1
--- !u!1 &1309955381917574
GameObject:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
serializedVersion: 5
m_Component:
- component: {fileID: 4023925443566846}
- component: {fileID: 33926934516982554}
- component: {fileID: 136780775377576056}
- component: {fileID: 23651006627612608}
m_Layer: 0
m_Name: Item
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &4023925443566846
Transform:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
m_GameObject: {fileID: 1309955381917574}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 1, z: 0}
m_LocalScale: {x: 0.5, y: 0.5, z: 0.5}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!23 &23651006627612608
MeshRenderer:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
m_GameObject: {fileID: 1309955381917574}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_Materials:
- {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_PreserveUVs: 1
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 0
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
--- !u!33 &33926934516982554
MeshFilter:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
m_GameObject: {fileID: 1309955381917574}
m_Mesh: {fileID: 10208, guid: 0000000000000000e000000000000000, type: 0}
--- !u!136 &136780775377576056
CapsuleCollider:
m_ObjectHideFlags: 1
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 100100000}
m_GameObject: {fileID: 1309955381917574}
m_Material: {fileID: 0}
m_IsTrigger: 0
m_Enabled: 1
m_Radius: 0.5
m_Height: 2
m_Direction: 1
m_Center: {x: 0, y: 0, z: 0}
HierarchyビューにもItemが残りますが、若干、色が変わったような? Scene ファイル(stage01.unity) に追加された情報も確認してみましょう。
--- !u!1001 &1779316819
Prefab:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: 4023925443566846, guid: eaaffba31a72f66469e48da1ff776780, type: 2}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4023925443566846, guid: eaaffba31a72f66469e48da1ff776780, type: 2}
propertyPath: m_LocalPosition.y
value: 1
objectReference: {fileID: 0}
- target: {fileID: 4023925443566846, guid: eaaffba31a72f66469e48da1ff776780, type: 2}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4023925443566846, guid: eaaffba31a72f66469e48da1ff776780, type: 2}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4023925443566846, guid: eaaffba31a72f66469e48da1ff776780, type: 2}
propertyPath: m_LocalRotation.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4023925443566846, guid: eaaffba31a72f66469e48da1ff776780, type: 2}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4023925443566846, guid: eaaffba31a72f66469e48da1ff776780, type: 2}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 4023925443566846, guid: eaaffba31a72f66469e48da1ff776780, type: 2}
propertyPath: m_RootOrder
value: 4
objectReference: {fileID: 0}
m_RemovedComponents: []
m_ParentPrefab: {fileID: 100100000, guid: eaaffba31a72f66469e48da1ff776780, type: 2}
m_IsPrefabParent: 0
Scene ファイル(stage01.unity) に残されたほうの Item は、Item.prefab に記載された内容への参照で、だいぶ情報の量が少ないようにみえます。
更に Item を増やしてみると、その目的は明確になります。2つ目の Item も上記とほぼ同じ参照情報で、名称など変更点が追記されています。
私の理解した Item.prefab ファイルの役割とは
- Scene に追加される Object の情報が独立したファイルとして書き出される
- Scene には prefab への参照情報だけが残り、差分情報だけが記録される
Item.prefab ファイルは、Scene に配置した Item たちのスーパークラスで、全ての情報をもっている。Scene で使用される各 Item はこれとの差分情報だけをもつため、少ない情報量で多くの Item オブジェクトを利用できる。という感じでしょうか。
データと比較してみて気がついたのですが、Inspector で太字で表示されている項目が、その Item 用に差分として管理されている情報のようです。
prefab はなかなか面白い仕組みで、これがドラッグ一発で設定できるのは楽で良いですね。
ゲームのUIを追加する
6ページ目の ゲームのUIを追加する に進みましょう。
ここでの注目は、スクリプト中で用いられた MonoBehaviour.Update() 関数でしょうか。「Update は MonoBehaviour が有効の場合に、毎フレーム呼び出されます」だそうで、ゲームでは便利に使えそうですね。今回のサンプルのように、画面には表示されない Controller オブジェクトにまとめて実装するのが管理しやすそうです。
ゲームのクリアを追加する
7ページ目の ゲームのクリアを追加する に進みましょう。
このページは今までの技術の応用で、特に注目すべき点は無いような気もしますが。チェックボックスを外すことで最初は非表示にしておいたオブジェクトを
C# コードのほうで条件をみて表示させるあたりは
if (count == 0) {
// オブジェクトをアクティブにする
winnerLabelObject.SetActive(true);
}
いろいろ応用がききそうですね。
障害物とリスタート
いよいよ最後のページ、障害物とリスタート に進みましょう。
ここで障害物を追加して、やっとゲームとして完成です。今までの技術の応用がほとんどですが、Material で Emission を設定するだけで、簡単に障害物を光らせることができるのは、さすがですね。
また Player のボールが障害物の壁に当たった時に
// 接触したオブジェクトのタグが"Player"の場合
if (hit.gameObject.CompareTag("Player")) {
// 現在のシーン番号を取得
int sceneIndex = SceneManager.GetActiveScene().buildIndex;
// 現在のシーンを再読込する
SceneManager.LoadScene(sceneIndex);
}
と、シーンの再読み込みですぐにゲーム再開できるのは面白いところです。SceneManager をうまく利用して、タイトルやゲームオーバー用の Scene を追加し、そちらに遷移させることで、よりゲームらしいサンプルに拡張できそうですね。
また最後にある注釈に従って、ライティング設定の最後にある Auto のチェックを外さないと、画面が暗くなる症状が発生しました。
補足情報 (2018/0328)
全部が終わったあとに、あらためて公式サイトのチュートリアルを眺めているのですが インターフェースと基本 は必見!ですね。先に観ておけば良かった… と少し後悔してみたり。
また今回のチュートリアルとほぼ同じゲームを作成するビデオ 玉転がし(9) があるんですが、今回のと比較しながら観てみると面白いです!同じことやるのに、そういう設定の仕方もあるのか!そう操作すると早いのね!などと。理解がかなり深まります。
これらビデオ系のチュートリアルなんですが、けっこう日本語字幕が付いています。付いていなくとも自動翻訳の機能があり日本語を指定できるので、英語が苦手な人でもあまり問題なく理解できるとおもいます。たまに訳されすぎるので、おや?と思ったところは英語字幕に戻して見直してみると更に良し!
おわりに
以上、Unity のチュートリアル 「はじめてのUnity」に従って簡単なサンプルゲームを作成してみました。簡単なコードを書くだけで、これだけちゃんと動作するゲームが作成できるのは素晴らしいですね。
みなさんも是非、Unity で遊んでみてください。