这次实验让我们用Unity3D来做一个牧师与魔鬼的游戏,不过这可是3D版本哦听起来就有点小兴奋有木有。
       牧师和魔鬼游戏是一款益智类游戏,游戏的目标是将3个牧师和3个魔鬼从河的一端安全地送到河的另一端。在运送过程中,船可以搭载两个人,而且必须有一人掌船。无论何时,只要河一边的魔鬼数量多于牧师的数量,游戏就会以失败结束。想玩玩的话请走传送门: priests-and-devils
先上个成品图压压惊。
截图效果没那么好其实水是会动的。话不多说,我们来分析一下。
游戏的对象:

游戏角色:3个牧师、3个魔鬼

游戏场景:2个河岸、1艘小船,河

游戏的架构:

UserInterface 用来创建GUI对象接受玩家动作,处理GUI事件,使用 IUserActions 接口控制游戏。

GenGameObjec 作为场记用于载入资源,用来处理对象间通信和实现 IUserActions 接口和ISceneController接口

SSDirector 作为导演,含有场记对象及设置帧

首先我们先实现SSDirector

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public interface ISceneController
{void LoadResources();//void Pause ();//void Resume ();
}public class SSDirector : System.Object
{private static SSDirector _instance;public ISceneController currentScenceController { get; set; }public bool running { get; set; }public static SSDirector getInstance(){if (_instance == null){_instance = new SSDirector();}return _instance;}public int getFPS(){return Application.targetFrameRate;}public void setFPS(int fps){Application.targetFrameRate = fps;}
}
这个基本上就是老师上课的代码,我就不多解释了。接下来实现GenGameObject。
首先创建导演实例并载入资源,这里在awake()中实现。
void Awake()//创建导演实例并载入资源{SSDirector director = SSDirector.getInstance();director.setFPS(60);director.currentScenceController = this;director.currentScenceController.LoadResources();}
然后我们实现接口ISceneController,就是LoadResources()。
public void LoadResources()//载入资源{// shore  Instantiate(Resources.Load("prefabs/begin"), shoreStartPos, Quaternion.identity);Instantiate(Resources.Load("prefabs/end"), shoreEndPos, Quaternion.identity);Instantiate(Resources.Load("prefabs/water"), waterPos, Quaternion.identity);Instantiate(Resources.Load("prefabs/water"), waterPos1, Quaternion.identity);// boat  boat_obj = Instantiate(Resources.Load("prefabs/Capsule"), boatStartPos, Quaternion.identity) as GameObject;// priests & devils  for (int i = 0; i < 3; ++i){priests_start[i] = (Instantiate(Resources.Load("prefabs/Priest")) as GameObject);priests_end[i] = null;devils_start[i] = (Instantiate(Resources.Load("prefabs/Devil")) as GameObject);devils_end[i] = null;}}
begin为开始岸,end为结束岸,water为水,capsule为船,priest为牧师,devil为魔鬼。boat_obj为船的实体。priests_start[],priests_end[],devils_start[],devils_end[]分别是存储开始岸和结束岸的牧师对象,魔鬼对象。


开始岸和结束岸都是直接建立一个立方体,通过合适缩放,然后建立material,将贴图贴在material上,再应用到岸即可。water是通过Asserts->Import Package->environment导入资源,然后使用里面的素材。船是通过建立capsule然后加上material制成。至于priest和devil是我上网下载的立体素材。
这里吐槽一下,上网找素材真特么不好找,有的需要付费(上面的女巫和狼人就是),有的是假网站,点击链接跳到直播间去,有的是3Dmax格式的素材而我们需要的是fbx(无法转换,后来逼得我去下载3Dmax软件去转换),真正可用素材其实不多。这里我分享一些网站, Unity3D模型1  , Unity3D模型 ,2 圣典 , 蛮牛 .我也在网盘上分享了我现在拥有的3D素材,有需自取哈。
链接:http://pan.baidu.com/s/1slGYLw5 密码:tw8m
用表格列出玩家动作表(规则表)

在继续编写 GenGameObject 脚本之前,我们要明确游戏允许玩家的所有动作。这里,我规定的玩家动作有7个:

项目 条件
开船 船在开始岸、船在结束岸
船的左方下船 船靠岸且船左方有人
船的右方下船 船靠岸且船右方有人
开始岸的牧师上船 船在开始岸,船有空位,开始岸有牧师
开始岸的魔鬼上船 船在开始岸,船有空位,开始岸有魔鬼
结束岸的牧师上船 船在结束岸,船有空位,结束岸有牧师
结束岸的魔鬼上船 船在结束岸,船有空位,结束岸有魔鬼
基于规则表完善 GenGameObject 类

考虑到牧师和魔鬼的位置时刻要变化,因此先定义一个 setCharacterPositions 函数。该函数接受一个array[]参数,和一个Vector3坐标。我说明下这里为什么不使用栈,因为栈只能按顺序的pop和push,而我想实现的是通过鼠标点击响应,可能有乱序,所以我这里用array[].

void setCharacterPositions(GameObject[] array, Vector3 pos)//设置人物位置{for (int i = 0; i < 3; ++i){if(array[i] != null)array[i].transform.position = new Vector3(pos.x, pos.y, pos.z + gap * i);}}
然后在update里面
void Update () {setCharacterPositions(priests_start, priestStartPos);setCharacterPositions(priests_end, priestEndPos);setCharacterPositions(devils_start, devilStartPos);setCharacterPositions(devils_end, devilEndPos);

 现在,我们来考虑规则抽象的行为,分为3种: 上船 、 开船 、 下船 。

1.  上船:把一个游戏对象设为船的子对象。

定义 getOnTheBoat 函数,接受一个游戏对象为参数,只要船上有空位,就把游戏对象设置为船的子对象,这样游戏对象便能跟着船移动:

void getOnTheBoat(GameObject obj)//上船{print(obj.name);if (boatCapacity != 0){if (boat_position == 0){for (int i = 0; i < 3; ++i){if (devils_start[i] == obj){devils_start[i] = null;find = 1;}if (priests_start[i] == obj){priests_start[i] = null;find = 1;}}}else if (boat_position == 1){for (int i = 0; i < 3; ++i){if (devils_end[i] == obj){devils_end[i] = null;find = 1;}if (priests_end[i] == obj){priests_end[i] = null;find = 1;}}}if(find == 1)obj.transform.parent = boat_obj.transform;if (boat[0] == null && find == 1){boat[0] = obj;boat[0].transform.tag = obj.transform.tag;boatCapacity--;obj.transform.localPosition = new Vector3(0, 1.2f, 0.19f);}else if(boat[1] == null && find == 1){boat[1] = obj;boat[1].transform.tag = obj.transform.tag;boatCapacity--;obj.transform.localPosition = new Vector3(0, 1.2f, -0.12f);}}find = 0;}

getOnTheBoat接受一个GameObject,为鼠标点击的物体。首先判断boatCapacity是否不为0,即船上有空位。然后判断船的位置,boat_position是0(在开始岸),或是1(在结束岸)。然后检查该物体是否在所在岸上的牧师或者魔鬼数组里,若找到find为1.若是1,将该gameobject挂在船的子对象里,然后判断boat[0]或者boat[1]是否为空(boat数组用来存储在船上的对象),找到合适的位置,装进去,并且设置boat[i]的tag(用于鼠标点击)。boatCapacity减1,设置其相对位置(设置位置是最TMTMTM麻烦的,不断微调,自己去体验下吧)。
2.   开船 : 船的位置移动
void Boatmoving(GameObject obj)//船的移动{if(boat_position == 1){boat_position = 0;while (obj.transform.position != boatStartPos)obj.transform.position = Vector3.MoveTowards(obj.transform.position, boatStartPos, 1);}else if(boat_position == 0){boat_position = 1;while (obj.transform.position != boatEndPos)obj.transform.position = Vector3.MoveTowards(obj.transform.position, boatEndPos, 1);}}
判断船的位置,boat_position如果为1就设置为0,用while循环绑定(否则鼠标点击,在速度很小的情况下,他只会在点击时间内移动一个很小的距离)。
3. 下船 : 取消船和游戏对象的父子关系,将对象压入对应数组里面。
void getOffTheBoat(int side)//下船{if (boat[side] != null){boat[side].transform.parent = null;if (boat_position == 1){print(side);if (boat[side].transform.tag == "Priest"){for(int i = 0; i < 3; i++){if(priests_end[i] == null){priests_end[i] = boat[side];boatCapacity++;break;}}}else if (boat[side].transform.tag == "Devil"){for (int i = 0; i < 3; i++){if (devils_end[i] == null){devils_end[i] = boat[side];boatCapacity++;break;}}}}else if (boat_position == 0){if (boat[side].transform.tag == "Priest"){for (int i = 0; i < 3; i++){if (priests_start[i] == null){priests_start[i] = boat[side];boatCapacity++;break;}}}else if (boat[side].transform.tag == "Devil"){for (int i = 0; i < 3; i++){if (devils_start[i] == null){devils_start[i] = boat[side];boatCapacity++;break;}}}}boat[side] = null;}check();}

传入参数side,指明是在船上的哪一边。判断如果船上的那一边对象为非空,首先取消父子关系。判断船的位置和对象的tag压入数组里面的非空的位置,boat[side]置空,然后检查游戏是否结束了。
注意到,为了区分出牧师和魔鬼,我给牧师和魔鬼预设分别添加了 Tag 。Tag需要在控制面板添加。
                                   

除此以外,还需要判断游戏的输赢,定义一个 check 函数:

void check()//检查游戏是否结束{int priests_s = 0, devils_s = 0, priests_e = 0, devils_e = 0;for(int i = 0; i < 3; i++){if(priests_start[i] != null){priests_s++;}if(devils_start[i] != null){devils_s++;}if(priests_end[i] != null){priests_e++;}if(devils_end[i] != null){devils_e++;}}if(((priests_s < devils_s) && (priests_s != 0))||((priests_e < devils_e) && (priests_e != 0))){print("you lose");game = 1;}else if (priests_s == 0 && devils_s == 0){print("you win!!!");game = 2;}}
设置4个变量int priests_s = 0, devils_s = 0, priests_e = 0, devils_e = 0;
分别是开始岸的牧师数,开始岸的魔鬼数,结束岸的牧师数,结束岸的魔鬼数。
接下来考虑鼠标点击问题。
if (Input.GetMouseButtonDown(0) && game == 0){Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);RaycastHit hit;if (Physics.Raycast(ray, out hit)){if (hit.transform.tag == "Devil" || hit.transform.tag == "Priest"){if(hit.collider.gameObject == boat[0] || hit.collider.gameObject == boat[1]){if(hit.collider.gameObject == boat[0])getOffTheBoat(0);else getOffTheBoat(1);}else{print(hit.transform.tag);getOnTheBoat(hit.collider.gameObject);}}else if (hit.transform.tag == "Boat" && boatCapacity != 2){print(hit.transform.tag);Boatmoving(hit.collider.gameObject);check();}}}

Input.GetMouseButtonDown(0)是响应鼠标左键按下,game = 0表示游戏正在进行中,game = 1表示输,game = 2表示赢,game = 3表示暂停。鼠标点击是通过从鼠标位置引一条射线,如果遇到碰撞框即可获取该物体。判断hit.transform.tag == Devil 或者是 Priest 然后判断是否是在船上,在船上就下船,不在船上就下船(这里有一个小小的bug,假如你点击了对岸的devil或者是priest呢?难道跳上船?)如果hit.transform.tag == boat且船上有人就移动船,并且检查输赢。到此基本完成场景控制。
开始编写Userinterface(IUserAction),考虑下用户的功能有Restart()//游戏重新开始;Detail()获取游戏细节;Pause()暂停游戏;
public interface IUserAction
{void Restart();void ShowDetail();void Pause();
}
然后编写用户类(UserGUI)
private IUserAction action;// Use this for initializationvoid Start () {action = SSDirector.getInstance().currentScenceController as IUserAction;}
void OnGUI() {GUIStyle fontstyle1 = new GUIStyle();fontstyle1.fontSize = 50;fontstyle1.normal.textColor = new Color(255, 255, 255);if (GUI.Button(new Rect(0, 80, 80, 60), "RESTART")){action.Restart();}if (GUI.RepeatButton(new Rect(0, 0, 120, 60), "Priests and Devils")){action.ShowDetail();}if(GUI.Button(new Rect(0, 160, 80, 60), "Pause")){action.Pause();}}
设置按键响应用户操作,(RepeatButton控件,用于用户持续按下按钮能不断响应)
然后在GenGameObect()具体实现void Restart();void ShowDetail();void Pause();
public void Restart(){SceneManager.LoadScene("task2");}public void ShowDetail(){GUI.Label(new Rect(220, 20, 350, 250), "Priests and Devils is a puzzle game in which you will help the Priests and Devils to cross the river. 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 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!");}public void Pause(){if (game == 0){game = 3;}else if(game == 3){game = 0;}}
最后在GenGameObect()写个OnGUI表示游戏结束时显示结果
private void OnGUI(){GUIStyle fontstyle1 = new GUIStyle();fontstyle1.fontSize = 50;fontstyle1.normal.textColor = new Color(255, 255, 255);if (game == 1){GUI.Label(new Rect(260, 180, 100, 100), "YOU LOSE!!!", fontstyle1);}else if(game == 2){GUI.Label(new Rect(260, 180, 100, 100), "YOU WIN!!!", fontstyle1);}}

自此游戏完成,虽然已经完成,但是感觉架构还是不很清晰,等待慢慢学习,慢慢理解。感觉还是有一段很长的路要走。欢迎大家回复提建议帮我改进。

有兴趣的同学请去GitHub看,这是传送门

Unity3D学习(3)之基于鼠标点击的3D版牧师与魔鬼相关推荐

  1. 鼠标点击器至尊版使用教程指导

    鼠标点击器至尊版是目前网上功能最多一款软件,为什么这么说呢,软件以工具形式,用户只需要了解每个工具的作用,就可以像搭积木一样完成各种各样的点击任务.首先我们看下软件界面 可以看到软件提供了8个工具.下 ...

  2. Visual C C++ studio2019 自制鼠标点击器,窗口版和命令行版 210325

    一 窗口版 1.h和cpp h: DesktopMouseClick1.h #pragma once#include "resource.h" #include <threa ...

  3. Unity3d HW4-动作分离版牧师与魔鬼

    一.基本操作演练 下载 Fantasy Skybox FREE, 构建自己的游戏场景 首先上Assert Store下载Skybox,我下载的是这一个: 之后我在unity中,把下载好的天空盒拖入摄像 ...

  4. Unity3d使用鼠标点击控制人物走动无效的问题

    Unity3d使用鼠标点击控制人物走动无效的问题 最近在自学Unity3d,在学到使用鼠标点击控制人物走动时,按照API上面将代码写好,如下: void Update () {// _clickLis ...

  5. cesium获取点击内容信息_Cesium获取鼠标点击位置(PickPosition)

    cesium学习了这么长时间,有时候写鼠标点击事件时,想获取鼠标点击点位置,发现情况很多.比如以下情形: 1获取鼠标点的对应椭球面位置 2获取加载地形后对应的经纬度和高程 3获取倾斜摄影或模型点击处的 ...

  6. 【Unity3D基础】让物体动起来②--UGUI鼠标点击逐帧移动

    背景 上一篇通过鼠标移动的代码很简单,所以看的人也不多,但是还是要感谢"武装三藏"在博客园给出的评论和支持,希望他也能看到第二篇,其实可以很简单,而且是精灵自控制,关键是代码少是我 ...

  7. Unity3d鼠标点击屏幕来控制人物的走动

    今天呢,我们来一起实现一个在RPG中游戏中十分常见的功能,通过鼠标点击屏幕来控制人物的走动.首先来说一下原理,当我们点击屏幕时,我们按照一定的方法,将屏幕上的二维坐标转化为三维坐标,然后我们从摄像机位 ...

  8. 学习swing鼠标点击事件心得体会_西门子COMOS软件开发定制学习8-查询列表间的数据交互...

    ​本篇在西门子COMOS软件开发定制学习6-管理界面定制基础上定制,简单介绍两个查询列表之间的数据交互. 实现效果: 在左侧列表中选择某一设备,右侧列表自动根据所选设备,显示该设备相关的设计图纸(如P ...

  9. 基于jQuery鼠标点击弹出登陆框效果

    基于jQuery鼠标点击弹出登陆框效果.这是一款扁平样式风格的jQuery弹出层登陆框特效.效果图如下: 在线预览    源码下载 实现的代码. html代码: <input type=&quo ...

最新文章

  1. spring boot 下载
  2. python脚本实例手机端-用Python实现自动化操作Android手机
  3. python编程语言是什么-编程语言分类及python所属类型
  4. 【1024程序员节】都有什么?现场亲历者告诉你...
  5. [gic]-ARM gicv3/gicv2的总结和介绍-PPT
  6. 搭建servlet+jsp环境
  7. 图像超分工具,在线工具
  8. AcWing 143. 最大异或对
  9. Northwind 示例数据库
  10. 华光职业学院计算机专业,关于给予张庆俊等同学省高校计算机一级 考试成绩优秀奖励的通知...
  11. 软件工程阶段性总结(四)——测试和维护
  12. Java实现动态规划经典题目
  13. cannot lock ref问题的解决
  14. Fiddler跟F12
  15. Python 中File(文件) 方法?
  16. 【bat】验证是否安装某个软件
  17. 分治算法小结(附例题详解)
  18. AT89S51单片机硬件结构
  19. 小米路由器获得BSI物联网安全风筝标志认证;IDC最新数据显示浪潮分布式存储增速中国第一 | 全球TMT...
  20. java毕业设计_租房管理系统

热门文章

  1. Day16(正则表达式,枚举)
  2. html首行缩进语言,怎么将html设置页面文本首行缩进
  3. Python爬虫-某某瓜网二手车数据
  4. 干货推荐 | 掌握这几点,轻松玩转Bokeh可视化
  5. 利用python进行累积
  6. A系统跨域访问其他系统页面
  7. Collection里的方法
  8. 励志!大凉山小伙全奖直博!论文致谢看哭网友
  9. 【Blender】基础物体建模(3)
  10. 张一鸣辞职半年后,成为中国互联网首富——“我奋斗的目标不是为了赚钱”