Unity学習記録 - 空間と動き
序文
この記事は、中山大学ソフトウェア工学部の 2020 年 3D ゲーム プログラミングとデザインの課題 3 です。
プログラミングに関する質問
1. 太陽系をシミュレーションする
最初に、探しているテクスチャ マテリアルを Unity のアセットに追加します太陽テクスチャ | 太陽系スコープ
シーン内に 8 つの惑星、月、太陽を表す 10 個の球体を作成します。
10 個の SPhere を作成したら、対応する惑星テクスチャを惑星上にドラッグします。このとき、Assets に Materials フォルダーが生成され、その中に 10 個の惑星のマテリアルが含まれます。
簡単なマッピングと位置のソートが完了すると、各惑星の配置は次のようになります。
模擬太陽系のさらなる最適化は次のとおりです
元のデフォルトの光源を削除し、次のように太陽に点光源を作成します
次に、太陽のマテリアルを変更し、発光を追加して光らせます。
このとき、8 つの惑星と月の自転と公転を実行するために使用される MoveBehavior という名前の C# コードをアセット領域に作成します。
コードは次のとおりです。
public class MoveBehaviourScript : MonoBehaviour
{
// 公转中心
public Transform center;
// 公转速度
public float around_speed;
// 自转速度
public float self_speed;
// 旋转所绕的轴
public float x = 0, y, z;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
Vector3 axis = new Vector3(x, y, z);
// 自转
this.transform.Rotate(Vector3.up * self_speed * Time.deltaTime);
// 公转
this.transform.RotateAround(center.position, axis, around_speed * Time.deltaTime);
}
}
各コードを自転・公転を行う惑星にドラッグし、回転中心や速度などの値を設定します。
惑星の動きの効果をより明確にするために、各惑星オブジェクトのトレーリング レンダラーを設定します。
次に、カメラの背景色を黒に設定します
ランニング効果は以下の通りです。
gif は大きすぎて csdn にアップロードできません。gitee で表示できます。リンクは次のとおりです:
hw3/img/P9.gif · XiaoChen04_3/3D_Computer_Game_Programming< /span>
2. 司祭と悪魔
- ゲームのルールは次のとおりです。
Priests and Devils は、制限時間内に司祭と悪魔が川を渡るのを助けるパズル ゲームです。川の片側には3人の司祭と3人の悪魔がいます。彼らは皆、この川の向こう岸に行きたいと思っていますが、船は 1 隻しかなく、この船は毎回 2 人しか乗せることができません。そして、ボートを一方の側から反対側まで操縦する人は 1 人でなければなりません。 Flash ゲームでは、それらをクリックしてボートを移動し、移動ボタンをクリックしてボートを別の方向に移動できます。川の両側で僧侶の数が悪魔よりも多ければ、彼らは殺され、ゲームは非常に複雑です。 さまざまな方法でプレイできます。すべての司祭を生かしてください!幸運を!
-
ゲーム内で言及されるオブジェクトには、司祭、悪魔、ボート、川、土地が含まれます。
-
プレイヤーアクションテーブルは以下の通りです
アクション | 状態 | 結果 |
---|---|---|
キャラクター(僧侶または悪魔)をクリックします | キャラクターは船と同じ岸にいます; キャラクターは船の上にいます | キャラクターたちが上陸 |
キャラクター(僧侶または悪魔)をクリックします | キャラクターは船とともに海岸にいます; キャラクターは海岸にいます | キャラクターが船に乗り込む |
ボートをクリックしてください | 船には1人か2人のキャラクターがいます | 船は向こう岸へ航行します |
-
MCV アーキテクチャとその概要
- M はモデルです: ゲーム内のすべてのゲーム オブジェクトを表し、それぞれのコントローラーによって制御されます。モデルとはゲーム内に存在する実体です。
- V は View: ゲームによって提供されるインターフェイスを表します。これには主に GUI と Click の 2 つの部分が含まれており、GUI はゲーム結果の表示を担当し、Click はユーザーのクリック イベントの処理を担当します。
- C はコントローラー: ゲーム内のコントローラーを表し、FistController (シーン コントローラー) と SSDirector (ディレクター) に分けられます。このゲームにはシーンが 1 つしかないため、ディレクターは終了と一時停止のみを担当し、シーン コントローラーはシーン内のすべてのオブジェクトを制御する必要があります。
-
コードの各部分とその概要
1. モデル
コードのこの部分は、モデルの初期読み込みに使用されます。プロジェクトのモデルには、Role (司祭、悪魔)、Boat (ボート)、Water (川)、Land (土地) が含まれます。ここのコードは Model.cs に統合されています。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace ModelApplication
{
public class Water
{
GameObject obj_water;
public Water()
{
obj_water = Object.Instantiate(Resources.Load<GameObject>("Prefabs/Water"),
Vector3.zero, Quaternion.identity) as GameObject;
obj_water.name = "water";
}
}
public class Land
{
public GameObject obj_land;
public int landSign;
public Vector3[] positions;
public Land(string str)
{
positions = new Vector3[] {
new Vector3(6.35F,2F,0), new Vector3(7.35f,2F,0), new Vector3(8.35f,2F,0),
new Vector3(9.35f,2F,0), new Vector3(10.35f,2F,0), new Vector3(11.35f,2F,0)
};
if (str == "start") landSign = 1;
else landSign = -1;
obj_land = Object.Instantiate(Resources.Load<GameObject>("Prefabs/Land"),
new Vector3(9 * landSign, 1, 0), Quaternion.identity) as GameObject;
obj_land.name = str + "Land";
}
}
public class Boat
{
public GameObject obj_boat;
public int boatSign;
public Vector3[] startPos, endPos;
public Boat()
{
obj_boat = Object.Instantiate(Resources.Load<GameObject>("Prefabs/Boat"),
new Vector3(5, 0.5f, 0), Quaternion.identity) as GameObject;
boatSign = 1;
startPos = new Vector3[] { new Vector3(5.5f, 1, 0), new Vector3(4.5f, 1, 0) };
endPos = new Vector3[] { new Vector3(-4.5f, 1, 0), new Vector3(-5.5f, 1, 0) };
obj_boat.name = "boat";
}
}
public class Role
{
public GameObject obj_role;
public int roleSign;
public int onBoat;
public int landSign;
public Role(string name, Vector3 pos)
{
if (name == "priest")
{
obj_role = Object.Instantiate(Resources.Load<GameObject>("Prefabs/Priest"),
pos, Quaternion.Euler(0, -90, 0)) as GameObject;
roleSign = 0;
}
else
{
obj_role = Object.Instantiate(Resources.Load<GameObject>("Prefabs/Devil"),
pos, Quaternion.Euler(0, -90, 0)) as GameObject;
roleSign = 1;
}
landSign = 1;
}
}
}
2. コントローラ
SSディレクター
SSDirector: シングルトン モードを使用して作成され、 System.Object
から継承されて存在し続け、Unity メモリ管理の対象になりません。ディレクター クラスは、現在のシーンの実行、切り替え、ゲームの一時停止、再開と終了、構成の設定などを制御する責任があります。
public class SSDirector : System.Object
{
private static SSDirector _instance;
public ISceneController CurrentSceneController { get; set; }
public static SSDirector GetInstance()
{
if (_instance == null)
{
_instance = new SSDirector();
}
return _instance;
}
}
モデルコントローラー
ModelController: モデルに対するさらなる操作のための関数。次のものが含まれます。
LandModel: 土地モデルを制御します。 LandModel を使用して、この土地上の役割 (つまり、土地の内外の役割) を追加または削除し、土地上の役割の数を決定できます。ここでは、バイナリ int 配列を使用して、この土地上の役割の数を記録します。土地、つまり【僧侶の数、悪魔の数】でゲームの進行を判断します。
public class LandModel
{
Land land;
List<RoleModel> roles = new List<RoleModel>(){null,null,null,null,null,null};
public LandModel(string str)
{
land = new Land(str);
}
public int getLandSign()
{
return land.landSign;
}
public int getEmptyNumber()
{
for (int i = 0; i < 6; i++)
{
if (roles[i] == null)
return i;
}
return -1;
}
public Vector3 getEmptyPos()
{
Vector3 res = land.positions[getEmptyNumber()];
res.x = res.x * land.landSign;
return res;
}
public int[] getRoleNum()
{
int[] res = { 0, 0 };
for (int i = 0; i < 6; i++)
{
if (roles[i] != null)
{
if (roles[i].getSign() == 0) res[0]++;
else res[1]++;
}
}
return res;
}
public void addRole(RoleModel role)
{
roles[getEmptyNumber()] = role;
}
public RoleModel deleteRole(string name)
{
for (int i = 0; i < 6; i++)
{
if (roles[i] != null && roles[i].getName() == name)
{
RoleModel res = roles[i];
roles[i] = null;
return res;
}
}
return null;
}
}
BoatModel: ボートのモデルを制御します。 BoatModel にはボートの移動のための関数が含まれています。ボートは 2 つの役割を担うことができるため、 getEmptyNumber()
関数はボートが満員かどうかを判断するために使用されます。同時に、上記の LandModel と同様の機能を使用して乗降操作を実行します。同時に、2 つの固定位置を船の移動の開始点と終了点として使用して船を移動します。
public class BoatModel
{
Boat boat;
List<RoleModel> roles = new List<RoleModel>(){null,null,null,null,null,null};
Move move;
Click click;
public BoatModel()
{
boat = new Boat();
move = boat.obj_boat.AddComponent(typeof(Move)) as Move;
click = boat.obj_boat.AddComponent(typeof(Click)) as Click;
click.setBoat(this);
}
public int getBoatSign()
{
return boat.boatSign;
}
public int getEmptyNumber()
{
for (int i = 0; i < 2; i++)
{
if (roles[i] == null)
return i;
}
return -1;
}
public Vector3 getEmptyPos()
{
Vector3 res;
if (boat.boatSign == 1) res = boat.startPos[getEmptyNumber()];
else res = boat.endPos[getEmptyNumber()];
return res;
}
public int[] getRoleNum()
{
int[] res = { 0, 0 };
for (int i = 0; i < 2; i++)
{
if (roles[i] != null)
{
if (roles[i].getSign() == 0) res[0]++;
else res[1]++;
}
}
return res;
}
public void addRole(RoleModel role)
{
roles[getEmptyNumber()] = role;
}
public RoleModel deleteRole(string name)
{
for (int i = 0; i < 2; i++)
{
if (roles[i] != null && roles[i].getName() == name)
{
RoleModel res = roles[i];
roles[i] = null;
return res;
}
}
return null;
}
public void boatMove()
{
if (boat.boatSign == -1) move.MovePosition(new Vector3(5, 0.5f, 0));
else move.MovePosition(new Vector3(-5, 0.5f, 0));
boat.boatSign *= -1;
}
public GameObject getBoat()
{
return boat.obj_boat;
}
public bool empty()
{
for (int i = 0; i < 2; i++)
{
if (roles[i] != null) return false;
}
return true;
}
}
RoleModel: ロールモデルの移動と判断のためのコードのほとんどは上記の LandModel と BoatModel に実装されているため、ここでの RoleModel 内のほとんどの関数は必要な戻り値を提供し、上記の移動に必要なデータの一部を再処理しますそして判断を立てる
public class RoleModel
{
Role role;
Move move;
Click click;
public RoleModel(string name, Vector3 pos)
{
role = new Role(name, pos);
move = role.obj_role.AddComponent(typeof(Move)) as Move;
click = role.obj_role.AddComponent(typeof(Click)) as Click;
click.setRole(this);
}
public string getName()
{
return role.obj_role.name;
}
public int getLandSign()
{
return role.landSign;
}
public void setLandSign(int land)
{
role.landSign = land;
}
public int getSign()
{
return role.roleSign;
}
public int getOnBoat()
{
return role.onBoat;
}
public void setOnBoat(int a)
{
role.onBoat = a;
}
public void setName(string name)
{
role.obj_role.name = name;
}
public GameObject getRole()
{
return role.obj_role;
}
public void roleMove(Vector3 pos)
{
move.MovePosition(pos);
}
}
ファーストコントローラー
FirstController は、ゲームの最初のシーン、つまり Start()
をロードする役割を果たし、その中で LoadResoureces()
を呼び出してシーンを動的に生成します。このプロジェクト。 MoveRole()
と MoveBoat()
も FirstController で記述され、ゲーム プロジェクト内の 2 つの操作のみを実装します。同時にゲームの成否判定やリスタートボタンの実装もFirstControllerに実装されています。
using UnityEngine.SceneManagement;
using UnityEngine;
using ModelApplication;
using ControllerApplication;
using InterfaceApplication;
using System.Collections.Generic;
public class FirstController : MonoBehaviour, ISceneController, IUserAction
{
public LandModel startLand;
public LandModel endLand;
public Water water;
public BoatModel boat;
public List<RoleModel> roles;
public UserGUI GUI;
// 资源加载
public void LoadResoureces()
{
water = new Water();
startLand = new LandModel("start");
endLand = new LandModel("end");
boat = new BoatModel();
roles = new List<RoleModel>();
for (int i = 0; i < 3; i++)
{
RoleModel role = new RoleModel("priest", startLand.getEmptyPos());
role.setName("priest" + i);
startLand.addRole(role);
roles.Add(role);
}
for (int i = 0; i < 3; i++)
{
RoleModel role = new RoleModel("devil", startLand.getEmptyPos());
role.setName("devil" + i);
startLand.addRole(role);
roles.Add(role);
}
}
// 移动船
public void moveBoat()
{
if (boat.empty() || GUI.sign != 0) return;
boat.boatMove();
GUI.sign = check();
}
// 移动角色
public void moveRole(RoleModel role)
{
if (GUI.sign != 0) return;
if (role.getOnBoat() == 1)
{
boat.deleteRole(role.getName());
role.setOnBoat(0);
role.getRole().transform.parent = null;
if (boat.getBoatSign() == 1)
{
role.roleMove(startLand.getEmptyPos());
role.setLandSign(1);
startLand.addRole(role);
}
else
{
role.roleMove(endLand.getEmptyPos());
role.setLandSign(-1);
endLand.addRole(role);
}
}
else
{
if (role.getLandSign() == 1)
{
if (boat.getEmptyNumber() == -1 || role.getLandSign() != boat.getBoatSign()) return;
startLand.deleteRole(role.getName());
role.roleMove(boat.getEmptyPos());
role.getRole().transform.parent = boat.getBoat().transform;
role.setOnBoat(1);
boat.addRole(role);
}
else
{
if (boat.getEmptyNumber() == -1 || role.getLandSign() != boat.getBoatSign()) return;
endLand.deleteRole(role.getName());
role.roleMove(boat.getEmptyPos());
role.getRole().transform.parent = boat.getBoat().transform;
role.setOnBoat(1);
boat.addRole(role);
}
}
GUI.sign = check();
}
// 重新开始
public void reStart()
{
SceneManager.LoadScene(0);
}
// 游戏进度判断
public int check()
{
int[] boatRole = boat.getRoleNum();
Debug.Log(string.Format("{0},{1}",boatRole[0],boatRole[1]));
int[] startRole = startLand.getRoleNum();
Debug.Log(string.Format("{0},{1}",startRole[0],startRole[1]));
int[] endRole = endLand.getRoleNum();
Debug.Log(string.Format("{0},{1}",endRole[0],endRole[1]));
if (endRole[0] + endRole[1] == 6) return 1;
if (boat.getBoatSign() == 1)
{
startRole[0] += boatRole[0];
startRole[1] += boatRole[1];
}
else
{
endRole[0] += boatRole[0];
endRole[1] += boatRole[1];
}
if ((endRole[0] > 0 && endRole[1] > endRole[0]) || (startRole[0] > 0 && startRole[1] > startRole[0]))
return -1;
return 0;
}
// 游戏开始
void Start()
{
SSDirector director = SSDirector.GetInstance();
director.CurrentSceneController = this;
GUI = gameObject.AddComponent<UserGUI>() as UserGUI;
LoadResoureces();
}
}
クリックコントローラー
Click クラスは、船とキャラクターがクリックされたかどうかを検出する役割を果たし、ModelController 内の関連関数を呼び出してユーザーのクリックに応答してゲームをプレイします。
public class Click : MonoBehaviour
{
IUserAction action;
RoleModel role = null;
BoatModel boat = null;
public void setRole(RoleModel role)
{
this.role = role;
}
public void setBoat(BoatModel boat)
{
this.boat = boat;
}
void Start()
{
action = SSDirector.GetInstance().CurrentSceneController as IUserAction;
}
void OnMouseDown()
{
if (boat == null && role == null) return;
if (boat != null) action.moveBoat();
else if (role != null) action.moveRole(role);
}
}
インターフェース
他のクラスにインターフェイスを提供する 2 つのクラスが含まれています。このうち ISceneController はシーン生成を実現するシーンインターフェースを提供し、IUserAction はボートの移動、キャラクターの移動、再起動、ゲームステータスの確認などの機能を含むユーザーインタラクションインターフェースを提供します。
using ControllerApplication;
namespace InterfaceApplication
{
public interface ISceneController
{
void LoadResoureces();
}
public interface IUserAction
{
void moveBoat();
void moveRole(RoleModel role);
void reStart();
int check();
}
}
3. 表示
WhereGui
ビューはゲームの UI を構築し、ユーザーと対話するための UI (主に再起動ボタンとゲームの成功と失敗のプロンプト) を設定します。
using UnityEngine;
using InterfaceApplication;
public class UserGUI : MonoBehaviour
{
public IUserAction action;
public int sign = 0;
void Start()
{
action = SSDirector.GetInstance().CurrentSceneController as IUserAction;
}
void OnGUI()
{
GUIStyle text_style;
GUIStyle button_style;
GUIStyle titleStyle;
text_style = new GUIStyle()
{
fontSize = 30,
alignment = TextAnchor.MiddleCenter,
};
button_style = new GUIStyle("button")
{
fontSize = 15,
alignment = TextAnchor.MiddleCenter,
};
titleStyle = new GUIStyle()
{
fontSize = 40,
alignment = TextAnchor.MiddleCenter,
};
titleStyle.normal.textColor = Color.black;
if (sign == -1)
{
GUI.Label(new Rect(0, 0, Screen.width, Screen.height * 0.5f), "Gameover!", text_style);
}
else if (sign == 1)
{
GUI.Label(new Rect(0, 0, Screen.width, Screen.height * 0.5f), "You Win!", text_style);
}
if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 200, 100, 50), "Restart", button_style))
{
action.reStart();
sign = 0;
}
GUI.Label(new Rect(0, 0, Screen.width, Screen.height * 1.6f), "牧师与恶魔", titleStyle);
}
}
実行結果
ランニング効果は以下の通りです。
(司祭と悪魔は単純な抽象的なルートを選択しました。白い立方体が司祭、赤いボールが悪魔、茶色の長方形が土地、青い長方形が水、黄色の長方形がボートです)
コードの場所
コードとドキュメントはhw3 · XiaoChen04_3/3D_Computer_Game_Programming - gitee にアップロードされました。
Asset には PriestsAndDevils と SolarSystem という 2 つのフォルダーが含まれており、これらは今回の 2 つのプログラミングの質問に対応するコードです。
コードエフェクトを表示するには、Assets フォルダーをローカル Unity プロジェクトにコピーし、対応するシーン ファイルを開くだけです。