【3D游戏编程与设计】三 空间与运动 : 牧师与魔鬼

编程实践"牧师与魔鬼"

游戏规则

Priests and Devils
Priests and Devils is a puzzle game in which you will help the Priests and Devils to cross the river within the time limit. There are 3 priests and 3 devils at one side of the river. They all want to get to the other side of this river, but there is only one boat and this boat can only carry two persons each time. And there must be one person steering the boat from one side to the other side. In the flash game, you can click on them to move them and click the go button to move the boat to the other direction. If the priests are out numbered by the devils on either side of the river, they get killed and the game is over. You can try it in many > ways. Keep all priests alive! Good luck!

要帮助三个牧师和三个魔鬼在规定的时间内过河。在游戏中过河只能靠一条船,且这条船上最多只能同时载两个人(牧师/魔鬼)。船上必须要有至少一个人(牧师/魔鬼),才能开到对岸。当任何一边岸上的魔鬼数目比牧师的数目多时,这些魔鬼就会杀掉同处一岸的牧师,游戏就会失败。需要运用智慧让三个牧师和三个魔鬼都顺利过河。

列出游戏中提及的事物(Objects)

游戏中出现的事物包括三个牧师,三个魔鬼,一条船,两边的河岸,阻挡过河的一条河流。在后面需要将这些对象制作成预制,并在游戏初始化时将这些对象生成出来。

用表格列出玩家动作表(规则表)

游戏操作之后会触发规则判断,可能会触发游戏成功或失败。

玩家动作操作条件操作结果
点击船船上至少有一个人(牧师/魔鬼)船移动到另一侧河岸
点击岸上的人(牧师/魔鬼)船已经靠岸,且与被点击的人处于同一河岸,且船上的人数少于2被点击的人移动到船上
点击船上的人(牧师/魔鬼)船已经靠岸被点击的人移动到船同侧的岸上

项目架构

软件版本

项目使用的开发软件为Unity 3D 2020.1.4f1c1。

文件组织

文件组织
项目的资源文件夹包括Assets和Packages两个子文件夹。其中,Packages子文件夹存储了系统自带的一些包,在这个项目中并没有特别使用。而Assets文件夹则存储了这次游戏项目使用的资源,场景,脚本等。其中,Assets使用了标准素材库中Standard Assets的water材料(用于构建河流对象)。而Scenes文件夹存储了游戏的场景(这个游戏中只有一个场景)。Scripts文件夹存储了游戏中使用的脚本。Resources文件夹存储了游戏中用到的材料和预制对象。

其中,项目的脚本包括如下文件:
脚本

设计模式

MVC
游戏项目要求采用MVC设计模式设计,这个设计模式在之前Web 2.0课程中也有接触和学习实践过。MVC是界面人机交互程序设计的一种经典架构模式。它把程序分为三个部分:

  • 模型(Model):数据对象及关系,包括游戏对象,空间组织关系等。
  • 控制器(Controller):接受用户事件,控制模型的变化。在游戏中,每一个游戏场景都需要一个主控制器。控制器至少要能实现与玩家交互的接口,并且一般要实现和管理运动。
  • 界面(View):显示模型,将人机交互事件交给控制器处理。其接收和处理输入事件,并进行相应GUI的渲染。

设计思路

前面已经列举出了这次游戏中包括的对象:牧师,魔鬼,河岸,河流,船。根据上面的玩家动作表,我们可以发现,玩家同河岸与河流并没有交互,河岸与河流起到了类似于背景的作用。因此,对于船只,牧师,魔鬼,我们都需要编写controllor,此外,还需要一个管理对象移动的controllor。

前面提到这次游戏中只包括一个场景,也就是要操纵牧师和魔鬼过河的场景。这个场景需要一个场景主控制器,其需要具体调用上面阐述的几个controllor。

导演类的职责包括:获取当前游戏的场景,控制场景运行、切换、入栈与出栈,暂停、恢复、退出,管理游戏全局状态,设定游戏的配置,设定游戏的全局视图等。其实际起到了最高的总体控制作用。导演类采取单体设计模式,其在整个游戏运行过程中始终只有唯一的一个实例对象。当前游戏中没有用到场景的切换,因此只是简单地设计了导演类对象,并没有在其中具体实现复杂的功能。

此外,我们还需要提供一个用户友好的GUI接口,其可以向用户展示游戏规则,而且可以及时反馈游戏胜利、失败信息,提供接口使得用户可以重新开始游戏。UserGUI类具体实现了这个功能。Click类则控制与用户点击游戏对象的交互操作。

游戏中的所有GameObject就是MVC架构中的Model,它们都分别受到对应的Controller的控制。上述的UserGUi和Click类则属于View部分,该部分展示游戏状态,并且负责用户通过点击物体或按钮的形式与游戏交互。上面描述的对应每个Model的Controller,场景的主控制器,导演类则属于MVC架构中的Controller部分。这样的设计就符合MVC架构的设计。

将游戏中对象做成预制

首先,找到一些合适的材料贴图。
贴图
之后,使用上面的贴图,以及Unity中的调色板等工具,创建游戏对象所需要用到的材料:
材料
其中,河流用到的材料使用标准素材库中的water材料。

之后,可以创建Unity中的Cube和Sphere等对象。之后设置其材料为上面对应的材料,并设置Scale的分量为合适的大小,可以得到下面的游戏对象。
在这里插入图片描述
之后,类似上一个实验的操作,将这些对象拖入到Assets/Resources/Prefabs文件夹中,使其成为预制对象。之后在游戏中删除这些对象的实例。根据实验要求,游戏中所有对象都必须由代码生成,初始不能有游戏对象出现在游戏中。

项目地址

由于整个游戏文件夹过大,这里按照实验要求仅将Assets文件夹传到了公开的仓库上。仓库的链接为https://github.com/alphabstc/PriestsAndDevils。新建一个Unity 3D项目,按照下面的指引将仓库内容导入,将脚本拖到对应的对象上,应该可以创建出一个可以正常运行的游戏。

脚本分析与设计

导演类GameDirector.cs

这个类起到了导演的功能。其是整个游戏中最高的控制器,由初始场景的主控制器调用载入。导演类采用单体设计模式,在整个游戏过程中仅有一个实例对象。当前游戏中没有用到场景的切换,因此只是简单地设计了导演类对象,并没有在其中具体实现复杂的功能。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using interfaceController;

public class GameDirector : System.Object {//导演类
	private static GameDirector _instance;
	public ISceneController CurrentScenceController { get; set; }
	public static GameDirector GetInstance() {//单体设计模式
		if (_instance == null) {//创建单体
			_instance = new GameDirector();
		}
		return _instance;
	}
}

处理用户点击Click.cs

正如前面所述,这个类属于View部分。其会通过导演类获取当前场景的主控制器,由于当前游戏只有一个场景,因此其会在Start函数执行时就获取场景的主控制器。Click类提供了Set方法给控制器设定当前选中的对象。鼠标按下时,其会执行OnMouseDown函数,根据当前选中船或角色执行主控制器的相应函数。注意其这里不需要具体实现MoveBoat和MoveRole的功能,而是通过控制器处理,这也符合MVC架构的设计要求。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using modelController;
using interfaceController;

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 = GameDirector.GetInstance().CurrentScenceController as IUserAction;//通过导演获取当前场景的主控制器
	}
	void OnMouseDown() {//鼠标按下
		if (boat == null && role == null) return;//没有选中船或角色
		if (boat != null)//选中了船
			action.MoveBoat();//移动船
		else if(role != null)//选中了角色
			action.MoveRole(role);//移动角色
	}
}

用户界面UserGUI.cs

这个类逻辑没有很复杂,其也属于View的部分。其通过用户点击按钮,实现展示与隐藏游戏规则的功能。在游戏胜利和结束时,其也会提示用户,并且提供按钮使得用户可以重新开始游戏。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using interfaceController;

public class UserGUI : MonoBehaviour {

	private IUserAction action;//场景主控制器
	public int sign = 0;//游戏状态

	bool isShow = false;//是否显示规则
	void Start() {
		action = GameDirector.GetInstance().CurrentScenceController as IUserAction;
	}
	void OnGUI()
	{
		GUIStyle text_style;
		GUIStyle button_style;
		text_style = new GUIStyle()
		{
			fontSize = 30
		};
		button_style = new GUIStyle("button")
		{
			fontSize = 15
		};
		if (GUI.Button(new Rect(10, 10, 60, 30), "规则", button_style))
		{
			isShow = !isShow;
		}
		if(isShow)
		{
			GUI.Label(new Rect(Screen.width / 2 - 114, 10, 200, 50), "需要让三个牧师和三个魔鬼都过河");
			GUI.Label(new Rect(Screen.width / 2 - 120, 30, 250, 50), "每一边恶魔数量都不能多于牧师数量");
			GUI.Label(new Rect(Screen.width / 2 - 85, 50, 250, 50), "船同时最多只能载两个人");
			GUI.Label(new Rect(Screen.width / 2 - 85, 70, 250, 50), "点击牧师、魔鬼、船操作");
		}
		if (sign == 1)
		{
			GUI.Label(new Rect(Screen.width / 2 - 75, Screen.height / 2-120, 100, 50), "你输了!", text_style);
			if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 100, 50), "重新开始", button_style))
			{
				action.Restart();
				sign = 0;
			}
		}
		else if (sign == 2)
		{
			GUI.Label(new Rect(Screen.width / 2 - 75, Screen.height / 2 - 120, 100, 50), "你赢了!", text_style);
			if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 100, 50), "重新开始", button_style))
			{
				action.Restart();
				sign = 0;
			}
		}
	}
}

接口interfaceController.cs

其将游戏中用到的所有的接口放在同一命名空间interfaceController下。这样其他脚本文件using这个命名空间就可以访问游戏中的接口。其具体定义了场景控制器的接口ISceneController以及用户行动的接口IUserAction。ISceneController接口可以找出当前场景是由哪个主控制器控制,然后向场景控制器传达要求。IUserAction接口则处理用户通过鼠标和键盘等外设对游戏发出的指令。接口在这里起到了抽象类的作用,并没有具体实现功能,其虚函数需要具体的控制器实现。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using modelController;

namespace interfaceController{
	public interface ISceneController                      
	{
		void LoadResources();
	}
	public interface IUserAction                           
	{
		void MoveBoat();                                   
		void Restart();                                    
		void MoveRole(RoleModel role);                     
		int Check();                                       
	}
}

对象控制器 modelController.cs

对象控制器文件中定义了命名空间modelController,其中又具体定义了游戏中管理与操纵各个对象的控制器,包括船,角色,河岸,移动的控制器。其具体内容如下,具体分析详见下面代码注释:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace modelController {
	public class BoatModel {
		GameObject boat;//船                                  
		Vector3[] start_empty_pos; //开始位置                                  
		Vector3[] end_empty_pos; //结束位置                             
		Move move; //移动                           
		Click click;//点击
		int boat_sign = 1;//船所在的河岸                                                     
		RoleModel[] roles = new RoleModel[2]; //船上的人                             

		public BoatModel() {//构造函数
			boat = Object.Instantiate(Resources.Load("Prefabs/Boat", typeof(GameObject)), new Vector3(25, -2.5F, 0), Quaternion.identity) as GameObject;//载入船的预制
			boat.name = "boat";//命名
			move = boat.AddComponent(typeof(Move)) as Move;//添加元素
			click = boat.AddComponent(typeof(Click)) as Click;//添加元素
			click.SetBoat(this);//设置点击对应当前的船
			start_empty_pos = new Vector3[] {new Vector3(18, 4, 0), new Vector3(32, 4, 0)};//设置船在河岸一边的位置
			end_empty_pos = new Vector3[] {new Vector3(-32, 4, 0), new Vector3(-18, 3, 0)};//设置船在河岸另一边的位置
		}

		public bool IsEmpty() {//判断船是否为空
			for (int i = 0; i < roles.Length; i++) {
				if (roles[i] != null)
					return false;
			}
			return true;
		}

		public void BoatMove() {//船移动到对岸
			if (boat_sign == -1) {
				move.MovePosition(new Vector3(25, -2.5F, 0));
				boat_sign = 1;
			} else {
				move.MovePosition(new Vector3(-25, -2.5F, 0));
				boat_sign = -1;
			}
		}

		public int GetBoatSign() {//获得船当前所在的河岸
			return boat_sign;
		}

		public RoleModel DeleteRoleByName(string role_name)	{//通过角色的名字删除船上的一个角色
			for (int i = 0; i < roles.Length; ++i) {
				if (roles[i] != null && roles[i].GetName() == role_name) {
					RoleModel role = roles[i];
					roles[i] = null;
					return role;
				}
			}
			return null;
		}

		public int GetEmptyNumber() {//获得船上一个空的位置
			for (int i = 0; i < roles.Length; i++) {
				if (roles[i] == null) {
					return i;
				}
			}
			return -1;
		}

		public Vector3 GetEmptyPosition() {//获得船上一个空位置对应的具体三维坐标
			Vector3 pos;
			if (boat_sign == -1)
				pos = end_empty_pos[GetEmptyNumber()];
			else
				pos = start_empty_pos[GetEmptyNumber()];
			return pos;
		}

		public void AddRole(RoleModel role) {//增加一个角色
			roles[GetEmptyNumber()] = role;
		}

		public GameObject GetBoat() {//返回船
			return boat; 
		}

		public int[] GetRoleNumber() {//获得当前船上的角色数目(通过一个存储2个元素的数组表示牧师/魔鬼的计数)
			int[] count = {0, 0};
			for (int i = 0; i < roles.Length; i++) {
				if (roles[i] == null)
					continue;
				if (roles[i].GetSign() == 0)
					count[0]++;
				else
					count[1]++;
			}
			return count;
		}
	}
	public class LandModel {//河岸对象
		GameObject land;   //河岸                   
		Vector3[] positions;   //位置                    
		int land_sign;  //河岸的编号标记 区分河流的两岸                                
		RoleModel[] roles = new RoleModel[6]; //每边河岸都有6个空位         
		public LandModel(string land_mark) {//构造函数
			positions = new Vector3[] {new Vector3(46F, 14.75F, -4), new Vector3(55F, 14.75F, -4), new Vector3(64F, 14.75F, -4),
				new Vector3(73F, 14.75F, -4), new Vector3(82F, 14.75F, -4), new Vector3(91F, 14.75F, -4)};
			if (land_mark == "start") {//根据为河哪一边的河岸载入到不同的位置 并设置标记
				land = Object.Instantiate(Resources.Load("Prefabs/Land", typeof(GameObject)), new Vector3(70, 1, 0), Quaternion.identity) as GameObject;
				land_sign = 1;
			} else if (land_mark == "end") {
				land = Object.Instantiate(Resources.Load("Prefabs/Land", typeof(GameObject)), new Vector3(-70, 1, 0), Quaternion.identity) as GameObject;
				land_sign = -1;
			}
		}

		public int GetEmptyNumber() {//获得岸上的一个空位编号
			for (int i = 0; i < roles.Length; i++) {
				if (roles[i] == null)
					return i;
			}
			return -1;
		}

		public int GetLandSign() {//返回当前河岸编号
			return land_sign; 
		}

		public Vector3 GetEmptyPosition() {//获得岸上一个空位对应的具体三维坐标
			Vector3 pos = positions[GetEmptyNumber()];
			pos.x = land_sign * pos.x;                  
			return pos;
		}

		public void AddRole(RoleModel role) {//岸上添加一个角色
			roles[GetEmptyNumber()] = role;
		}

		public RoleModel DeleteRoleByName(string role_name) { //根据名字从岸上删除一个角色
			for (int i = 0; i < roles.Length; i++) {
				if (roles[i] != null && roles[i].GetName() == role_name) {
					RoleModel role = roles[i];
					roles[i] = null;
					return role;
				}
			}
			return null;
		}

		public int[] GetRoleNum() {//返回岸上的角色数目(通过一个存储2个元素的数组表示牧师/魔鬼的计数) 
			int[] count = {0, 0};                    
			for (int i = 0; i < roles.Length; i++) {
				if (roles[i] != null) {
					if (roles[i].GetSign() == 0)
						count[0]++;
					else
						count[1]++;
				}
			}
			return count;
		}	
	}
	public class RoleModel { //角色对象
		GameObject role;//角色
		int role_sign;  //角色类型
		Click click;//点击
		bool on_boat;  //是否在船上的标记         
		Move move;//移动
		LandModel land_model = (GameDirector.GetInstance().CurrentScenceController as Controller).start_land;//初始为出发的河岸
		public RoleModel(string role_name) {
			if (role_name == "priest") {//构造函数 根据为牧师/魔鬼 载入不同的对象
				role = Object.Instantiate(Resources.Load("Prefabs/Priests", typeof(GameObject)), Vector3.zero, Quaternion.Euler(0, -90, 0)) as GameObject;
				role_sign = 0;
			} else {
				role = Object.Instantiate(Resources.Load("Prefabs/Devils", typeof(GameObject)), Vector3.zero, Quaternion.Euler(0, -90, 0)) as GameObject;
				role_sign = 1;
			}
			move = role.AddComponent(typeof(Move)) as Move;//添加Move元素
			click = role.AddComponent(typeof(Click)) as Click;//添加Click元素
			click.SetRole(this);//click设置角色为当前对象
		}
		public int GetSign() {//获得角色类别
			return role_sign;
		}
		public LandModel GetLandModel() {//获得角色所在的河岸
			return land_model;
		}
		public string GetName() { //获得角色的名字
			return role.name; 
		}
		public bool IsOnBoat() { //判断角色是否在船上
			return on_boat; 
		}
		public void SetName(string name) { //设置角色的名字
			role.name = name; 
		}
		public void SetPosition(Vector3 pos) {//设置角色的位置 
			role.transform.position = pos; 
		}
		public void Move(Vector3 vec) {//角色移动
			move.MovePosition(vec);
		}
		public void GoLand(LandModel land) {//角色移动到河岸上
			role.transform.parent = null;
			land_model = land;
			on_boat = false;
		}
		public void GoBoat(BoatModel boat) {//角色移动到船上
			role.transform.parent = boat.GetBoat().transform;
			land_model = null;          
			on_boat = true;
		}

	}
	public class Move : MonoBehaviour {//移动
		float move_speed = 260;  //移动速度                 
		int move_sign = 0;  //移动标志    移动过程可以分为两段 避免穿过其他物体    
		Vector3 end_pos; //结束坐标
		Vector3 middle_pos; //中点坐标

		void Update() {
			if (move_sign == 1) {
				transform.position = Vector3.MoveTowards(transform.position, middle_pos, move_speed * Time.deltaTime);//先移动到一个中间点
				if (transform.position == middle_pos)//还有一段
					move_sign = 2;
			} else if (move_sign == 2) {
				transform.position = Vector3.MoveTowards(transform.position, end_pos, move_speed * Time.deltaTime);//移动到终点
				if (transform.position == end_pos)//已经移动到了目的地
					move_sign = 0;           
			}
		}
		public void MovePosition(Vector3 position) {
			end_pos = position;
			if (position.y == transform.position.y) {  //y坐标相同 直接水平移动
				move_sign = 2;
			}
			else if (position.y < transform.position.y) {//否则会进行有垂直分量的移动  先设置一个移动到的中间点
				middle_pos = new Vector3(position.x, transform.position.y, position.z);
			}
			else {
				middle_pos = new Vector3(transform.position.x, position.y, position.z);
			}
			move_sign = 1;
		}
	}
}

场景主控制器 Controller.cs

上面的控制器代码管理了各个游戏对象的行为,操作与状态的getter、setter函数。这些控制器针对每个对象设计了对应的游戏中需要用到的状态及更新函数。而场景主控制器则从宏观上将各个对象联系到一起,完成了游戏整体的功能,包括游戏的初始载入及游戏结束的判定。其具体实现了游戏规则,也就是起到了裁判的作用。其具体内容如下,具体分析详见下面代码注释:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using modelController;
using interfaceController;
public class Controller : MonoBehaviour, ISceneController, IUserAction {//不仅继承自MonoBehaviour 而且还实现了ISceneController和IUserAction接口
	public LandModel start_land;  //开始河岸         
	public LandModel end_land;    //目的河岸
	public BoatModel boat;  // 船
	private RoleModel[] roles;  //所有角色           
	UserGUI user_gui; //UserGUI成员
	void Start () {//初始时执行
		GameDirector director = GameDirector.GetInstance();//获得导演类
		director.CurrentScenceController = this;//设置导演类的当前场景主控制器为当前主控制器
		user_gui = gameObject.AddComponent<UserGUI>() as UserGUI;//添加元素UserGUI
		LoadResources();//载入资源
	}

	public void LoadResources() {
		GameObject water = Instantiate(Resources.Load("Prefabs/Water", typeof(GameObject)), new Vector3(0,-10,-2), Quaternion.identity) as GameObject;//创建河流
		water.name = "water";       
		start_land = new LandModel("start");//创建河岸
		end_land = new LandModel("end");
		boat = new BoatModel();//创建船
		roles = new RoleModel[6];

		for (int i = 0; i < 3; i++) {//创建三个牧师
			RoleModel role = new RoleModel("priest");
			role.SetName("priest" + i);
			role.SetPosition(start_land.GetEmptyPosition());
			role.GoLand(start_land);
			start_land.AddRole(role);
			roles[i] = role;
		}

		for (int i = 3; i < 6; i++) {//创建三个魔鬼
			RoleModel role = new RoleModel("devil");
			role.SetName("devil" + i);
			role.SetPosition(start_land.GetEmptyPosition());
			role.GoLand(start_land);
			start_land.AddRole(role);
			roles[i] = role;
		}
	}

	public void MoveBoat() {//移动船
		if (boat.IsEmpty() || user_gui.sign != 0) return;//游戏没有结束的情况下才可以移动
		boat.BoatMove();
		user_gui.sign = Check();//可能会触发游戏胜利或失败
	}

	public void MoveRole(RoleModel role) {//移动角色
		if (user_gui.sign != 0) return;//游戏没有结束的情况下才可以移动
		if (role.IsOnBoat()) {//从船上移动到河岸
			LandModel land;
			if (boat.GetBoatSign() == -1)
				land = end_land;
			else
				land = start_land;
			boat.DeleteRoleByName(role.GetName());
			role.Move(land.GetEmptyPosition());
			role.GoLand(land);
			land.AddRole(role);
		}
		else { //从河岸移动到船上     
			LandModel land = role.GetLandModel();
			if (boat.GetEmptyNumber() == -1 || land.GetLandSign() != boat.GetBoatSign()) return;   
			land.DeleteRoleByName(role.GetName());
			role.Move(boat.GetEmptyPosition());
			role.GoBoat(boat);
			boat.AddRole(role);
		}
		user_gui.sign = Check();//可能会触发游戏胜利或失败
	}

	public void Restart() {//重新开始游戏
		SceneManager.LoadScene(0);//重新载入初始场景
	}

	public int Check() {//检查游戏是否结束
		int start_priest = (start_land.GetRoleNum())[0];//获得每一边河岸上的牧师和魔鬼数目
		int start_devil = (start_land.GetRoleNum())[1];
		int end_priest = (end_land.GetRoleNum())[0];
		int end_devil = (end_land.GetRoleNum())[1];

		if (end_priest + end_devil == 6) //全部都到了目的河岸上  游戏胜利
			return 2;

		int[] boat_role_num = boat.GetRoleNumber();//获得船上的角色数目
		if (boat.GetBoatSign() == 1) {//船上的角色累计到船所停靠的河岸上
			start_priest += boat_role_num[0];
			start_devil += boat_role_num[1];
		}
		else {
			end_priest += boat_role_num[0];
			end_devil += boat_role_num[1];
		}
		if (start_priest > 0 && start_priest < start_devil) { //有牧师且牧师数目比魔鬼少 游戏失败
			return 1;
		}
		if (end_priest > 0 && end_priest < end_devil) { //有牧师且牧师数目比魔鬼少 游戏失败
			return 1;
		}
		return 0;   //游戏未结束                                          
	}
}

部署脚本

只需要在游戏中创建一个除了摄像头和光源之外的空对象,然后将第一个场景主控制器的脚本Controller.cs(在这个游戏中也是唯一一个)拖动到这个空对象上,就可以完成脚本的部署。
脚本部署

调整摄像头

调整摄像头的位置和姿态,使得其可以以比较好的视角看见创建后的棋盘。

这样就完成了项目的配置。

游戏效果截图

牧师为白色的球,魔鬼为黑色的正方体。

游戏开始时:
游戏开始
查看游戏规则:
查看游戏规则
移动:
移动
游戏失败:
游戏失败
另一种移动序列:
移动
移动
移动
移动
移动
移动
移动
移动
移动
游戏胜利:
游戏胜利

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合;、下载 4使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合;、 4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.m或d论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 、1资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值