3D游戏设计与分析-牧师与魔鬼
目标
使用unity3D做出一个牧师与魔鬼的游戏,程序需要满足以下要求:
- play the game ( http://www.flash-game.net/game/2535/priests-and-devils.html )
- 列出游戏中提及的事物(Objects)
- 用表格列出玩家动作表(规则表),注意,动作越少越好
- 请将游戏中对象做成预制
- 在 GenGameObjects 中创建 长方形、正方形、球 及其色彩代表游戏中的对象。
- 使用 C# 集合类型 有效组织对象
- 整个游戏仅 主摄像机 和 一个 Empty 对象, 其他对象必须代码动态生成!!! 。 整个游戏不许出现 Find 游戏对象, SendMessage 这类突破程序结构的 通讯耦合 语句。 违背本条准则,不给分
- 请使用课件架构图编程,不接受非 MVC 结构程序
- 注意细节,例如:船未靠岸,牧师与魔鬼上下船运动中,均不能接受用户事件!
列出游戏中提及的事物
名称 | 实现方式 |
---|---|
牧师 | 正方体 |
恶魔 | 球体 |
河岸 | 长方体 |
船 | 长方体 |
表格列出玩家动作表
动作 | 条件 |
---|---|
开船 | 船上必须有一个人 |
牧师在左岸上船 | 左岸必须有牧师,船上有空位,船在左岸 |
牧师在右岸上船 | 右岸必须有牧师,船上有空位,船在右岸 |
牧师在左岸下船 | 船上有牧师,船在左岸 |
牧师在右岸下船 | 船上有牧师,船在有岸 |
恶魔在左岸上船 | 左岸必须有恶魔,船上有空位,船在左岸 |
恶魔在右岸上船 | 右岸必须有恶魔,船上有空位,船在右岸 |
恶魔在左岸下船 | 船上有恶魔,船在左岸 |
恶魔在右岸下船 | 船上有恶魔,船在右岸 |
具体实现
在Assets中创建Resources文件夹,在Resources中创建Prefabs文件夹,将做好的对象拖入Prefabs中成为预制。在Assets文件下创建Scripts文件用以放置C#文件。
Director
Director类利用了单例模式,第一次调用getInstance()时,因为instance为null,所以instance会保存一个新的Director对象。往后的每次getInstance(),都会获得相同的值。也就是说所有的C#文件都将获得相同的Director对象,从而可以获得一个相同的controller,实现不同类之间的通信。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//Director.cs
public class Director : System.Object
{
private static Director instance;
public Controller controller;
public static Director getInstance()
{
if (instance == null)
{
instance = new Director();
}
return instance;
}
}
Controller
Controller类实现了IUserActions接口的方法,并通过一个GenGameObject类型的model变量对游戏对象进行操作。Awake()将Controller注入到单实例中。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//IUserActions.cs
public interface IUserActions
{
void priestOn();
void priestOnEnd();
void devilOn();
void devilOnEnd();
void changeState();
int Check();
void priestOff();
void devilOff();
void Restart();
}
//Controller.cs
public class Controller : MonoBehaviour, IUserActions
{
private GenGameObject model;
private void Awake()
{
Director director = Director.getInstance();
director.controller = this;
}
public void setModel(GenGameObject Model)
{
model = Model;
}
public void priestOn()
{
model.priestOn();
}
public void devilOn()
{
model.devilOn();
}
public void changeState()
{
model.change_state();
}
public void priestOnEnd()
{
model.priestOnEnd();
}
public void devilOnEnd()
{
model.devilOnEnd();
}
public int Check()
{
return model.Check();
}
public void priestOff()
{
model.priestOff();
}
public void devilOff()
{
model.devilOff();
}
public void Restart()
{
model.Restart();
}
}
GenGameObject
GenGameObject类定义了一系列变量。其中priests_start保存左岸牧师的游戏对象,priests_end保存右岸牧师的游戏对象,恶魔同理。onBoat用于存储当前船只上物体的数量。boat_state记录船只的状态,boat_state为1时,船在左岸;值为0时,船在右岸。通过Check(),可以判断游戏是否结束,当Check()返回1时,游戏胜利并结束;返回2时,游戏失败并结束;返回3时游戏进行中。具体代码如下。
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading;
using UnityEngine;
public class GenGameObject : MonoBehaviour
{
private Director instance;
int onBoat;
public int speed;
int boat_state = 1;
GameObject[] objectOnBoat = new GameObject[2];
readonly Vector3 shore_begin = new Vector3(10, 0, 0);
readonly Vector3 shore_end = new Vector3(-10, 0, 0);
readonly Vector3 boat_begin = new Vector3(-3, 0, 0);
readonly Vector3 boat_end = new Vector3(3, 0, 0);
readonly Vector3 priests_begin = new Vector3(-9f, 2, 0);
readonly Vector3 priests_last = new Vector3(9f, 2, 0);
readonly Vector3 devils_begin = new Vector3(-15f, 2, 0);
readonly Vector3 devils_last = new Vector3(15f, 2, 0);
readonly Vector3 gap = new Vector3(-1.5f, 0, 0);
Stack<GameObject> priests_start = new Stack<GameObject>();
Stack<GameObject> devils_start = new Stack<GameObject>();
Stack<GameObject> priests_end = new Stack<GameObject>();
Stack<GameObject> devils_end = new Stack<GameObject>();
GameObject boat;
private void Start()
{
speed = 10;
onBoat = 0;
boat_state = 1;
instance = Director.getInstance();
instance.controller.setModel(this);
Load();
}
private void Load()
{
Instantiate(Resources.Load("Prefabs/Shore"), shore_begin, Quaternion.identity);
Instantiate(Resources.Load("Prefabs/Shore"), shore_end, Quaternion.identity);
boat = Instantiate(Resources.Load("Prefabs/Boat"), boat_begin, Quaternion.identity) as GameObject;
boat.name = "boat";
for (int i = 0; i < 3; i++)
{
GameObject priest = Instantiate(Resources.Load("Prefabs/Priest"), (priests_begin - gap * i), Quaternion.identity) as GameObject;
priest.name = "Priest";
priests_start.Push(priest);
GameObject devil = Instantiate(Resources.Load("Prefabs/Devil"), (devils_begin - gap * i), Quaternion.identity) as GameObject;
devil.name = "Devil";
devils_start.Push(devil);
}
}
public int Check()
{
int devilleft, devilright, preleft, preright;
devilleft = devilright = preleft = preright = 0;
devilleft += devils_start.Count;
devilright += devils_end.Count;
preleft += priests_start.Count;
preright += priests_end.Count;
if(boat_state==1 && onBoat>0)
{
for(int i=0;i<2;i++)
{
if (objectOnBoat[i] != null && objectOnBoat[i].name == "Devil")
devilleft += 1;
else if (objectOnBoat[i] != null && objectOnBoat[i].name == "Priest")
preleft += 1;
}
}
else if(boat_state==0 && onBoat>0)
{
for (int i = 0; i < 2; i++)
{
if (objectOnBoat[i]!=null && objectOnBoat[i].name == "Devil")
devilright += 1;
else if (objectOnBoat[i] != null && objectOnBoat[i].name == "Priest")
preright += 1;
}
}
if (devils_end.Count == 3 && priests_end.Count == 3)
{
return 1;
}
if (devilleft>preleft && preleft>0)
{
// UnityEngine.Debug.Log(preleft);
return 2;
}
else if(devilright >preright && preright>0)
{
// UnityEngine.Debug.Log(preright);
return 2;
}
return 3;
}
private void Update()
{
Check();
}
public void priestOn()
{
if (priests_start.Count == 0)
return;
if (onBoat < 2)
{
GameObject temp = priests_start.Pop();
temp.transform.parent = boat.transform;
if (objectOnBoat[0] == null)
{
temp.transform.localPosition = new Vector3(0.3f, 1.5f, 0);
objectOnBoat[0] = temp;
}
else
{
temp.transform.localPosition = new Vector3(-0.3f, 1.5f, 0);
objectOnBoat[1] = temp;
}
onBoat++;
}
}
public void priestOff()
{
for (int i = 0; i < 2; i++)
{
if (objectOnBoat[i] != null && objectOnBoat[i].name == "Priest")
{
if (boat.transform.position != boat_begin && boat.transform.position != boat_end)
return;
GameObject pri = objectOnBoat[i];
pri.transform.parent = null;
if (boat.transform.position == boat_end)
{
pri.transform.position = priests_last + gap * priests_end.Count;
priests_end.Push(pri);
}
else if (boat.transform.position == boat_begin)
{
pri.transform.position = priests_begin - gap * priests_start.Count;
priests_start.Push(pri);
}
objectOnBoat[i] = null;
onBoat--;
return;
}
}
}
public void devilOn()
{
if (devils_start.Count == 0)
return;
if (onBoat < 2)
{
GameObject temp = devils_start.Pop();
temp.transform.parent = boat.transform;
if (objectOnBoat[0]==null)
{
temp.transform.localPosition = new Vector3(0.3f, 1.5f, 0);
objectOnBoat[0] = temp;
}
else
{
temp.transform.localPosition = new Vector3(-0.3f, 1.5f, 0);
objectOnBoat[1] = temp;
}
onBoat++;
// UnityEngine.Debug.Log(onBoat);
}
}
public void devilOff()
{
for (int i = 0; i < 2; i++)
{
if (objectOnBoat[i] != null && objectOnBoat[i].name == "Devil")
{
if (boat.transform.position != boat_begin && boat.transform.position != boat_end)
return;
GameObject pri = objectOnBoat[i];
pri.transform.parent = null;
if (boat_state==0)
{
pri.transform.position = devils_last + gap * devils_end.Count;
devils_end.Push(pri);
// UnityEngine.Debug.Log(devils_end.Count);
}
else if (boat_state == 1)
{
pri.transform.position = devils_begin - gap * devils_start.Count;
devils_start.Push(pri);
}
objectOnBoat[i] = null;
onBoat--;
return;
}
}
}
public void Restart()
{
int num = priests_end.Count;
for (int i = 0; i < num; i++)
{
GameObject temp = priests_end.Pop();
temp.transform.position = priests_begin - gap * priests_start.Count;
priests_start.Push(temp);
}
num = devils_end.Count;
for (int i = 0; i < num; i++)
{
GameObject temp = devils_end.Pop();
temp.transform.position = devils_begin - gap * devils_start.Count;
devils_start.Push(temp);
}
if(onBoat>0)
for (int i = 0; i < 2; i++)
{
if (objectOnBoat[i] == null)
{
continue;
}
GameObject temp = objectOnBoat[i];
temp.transform.parent = null;
if (temp.name == "Priest")
{
temp.transform.position = priests_begin - gap * priests_start.Count;
priests_start.Push(temp);
}
else
{
temp.transform.position = devils_begin - gap * devils_start.Count;
priests_start.Push(temp);
}
objectOnBoat[i] = null;
}
onBoat = 0;
boat.transform.position = boat_begin;
boat_state = 1;
}
public void priestOnEnd()
{
if (priests_end.Count == 0)
return;
if (onBoat < 2)
{
GameObject temp = priests_end.Pop();
temp.transform.parent = boat.transform;
if (objectOnBoat[0] == null)
{
temp.transform.localPosition = new Vector3(0.3f, 1.5f, 0);
objectOnBoat[0] = temp;
}
else
{
temp.transform.localPosition = new Vector3(-0.3f, 1.5f, 0);
objectOnBoat[1] = temp;
}
onBoat++;
}
}
public void devilOnEnd()
{
if (devils_end.Count == 0)
return;
if (onBoat < 2)
{
GameObject temp = devils_end.Pop();
temp.transform.parent = boat.transform;
if (objectOnBoat[0] == null)
{
temp.transform.localPosition = new Vector3(0.3f, 1.5f, 0);
objectOnBoat[0] = temp;
}
else
{
temp.transform.localPosition = new Vector3(-0.3f, 1.5f, 0);
objectOnBoat[1] = temp;
}
onBoat++;
}
}
public void boatMove()
{
if (onBoat != 0)
{
if (boat_state == 0)
boat.transform.position = boat_end;
else
boat.transform.position = boat_begin;
}
}
public void change_state()
{
if (boat_state == 0)
boat_state = 1;
else
boat_state = 0;
boatMove();
}
}
UI
为界面添加按钮,并调用按钮对应的接口函数
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UserInterface : MonoBehaviour
{
Director instance;
IUserActions action;
void Start()
{
instance = Director.getInstance();
action = instance.controller as IUserActions;
}
private void OnGUI()
{
GUIStyle fontStyle = new GUIStyle();
fontStyle.fontSize = 40;
fontStyle.normal.textColor = new Color(255, 255, 255);
if (action.Check() == 1)
{
GUI.Label(new Rect(440, 200, 100, 100), "Win", fontStyle);
if (GUI.Button(new Rect(435, 360, 80, 50), "Restart"))
{
action.Restart();
}
}
if (action.Check() == 2)
{
GUI.Label(new Rect(390, 200, 100, 100), "GameOver", fontStyle);
if (GUI.Button(new Rect(435, 360, 80, 50), "Restart"))
{
action.Restart();
}
}
if (GUI.Button(new Rect(130, 0, 50, 50), "On"))
{
action.devilOn();
}
if (GUI.Button(new Rect(270, 0, 50, 50), "On"))
{
action.priestOn();
}
if (GUI.Button(new Rect(600, 0, 50, 50), "On"))
{
action.priestOnEnd();
}
if (GUI.Button(new Rect(750, 0, 50, 50), "On"))
{
action.devilOnEnd();
}
if (GUI.Button(new Rect(450, 0, 50, 50), "Move"))
{
action.changeState();
}
if (GUI.Button(new Rect(450, 50, 50, 50), "POff"))
{
action.priestOff();
}
if (GUI.Button(new Rect(450, 100, 50, 50), "DOff"))
{
action.devilOff();
}
}
}