牧师与魔鬼Priests and Devils--unity小游戏

一、游戏介绍:

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!

牧师与魔鬼
牧师与魔鬼是一款益智游戏,你将帮助牧师和魔鬼在限定时间内过河。河的一边有三个牧师和三个魔鬼。他们都想去河的对岸,但只有一条船,这条船每次只能载两个人。而且必须有一个人把船从一边开到另一边。在flash游戏中,你可以点击它们来移动它们,点击go按钮将船移动到另一个方向。如果在河的两边,牧师的人数比魔鬼多,他们就会被杀死,游戏也就结束了。你可以用很多方法来尝试。让所有的牧师活下去!好运!

二、效果演示

牧师与魔鬼(Priests and Devils)--unity小游戏

三、具体实现过程

1. MVC模式介绍

2. UML图

为了辅助表达游戏对象的设计,在这里通过UML图画出主要的对象以及它们之间的关系:

在这里给出上述个对象的简介:

IUserAction:作为动作接口,定义了四个函数,分别是移动船只(MoveBoat)、移动角色(MoveRole)、检查游戏是否结束(Check)、重新开始游戏(Restart)。

ISceneController:作为场景接口,定义了两个函数,分别是加载资源(LoadResource)、销毁资源(destroyResource)。

FirstController:作为整个游戏的主要控制器,实现了动作接口IUserAction和场景接口ISceneController,同时含有一个Awake()函数用于游戏的初始化、一个Update()函数对剩余时间的实时更新(用作倒计时功能)。

UserGUI:作为用户交互界面,用于显示游戏文字信息并创建按钮等部件与玩家交互。同时,UserGUI将当前的场景传递给IUserAction用于执行控制动作。

SSDirector:作为整个游戏的“导演”,用于把握全局、控制场景。

GameModels:与游戏有关的各对象以及它们的控制器,用于定义、创建对象以及执行对它们的控制。由于对象太多,不在此一一列出。

3. 游戏中提及的事物(Objects)

游戏中提及的事物有:

牧师Priests:游戏可操纵对象,需要在玩家的帮助下过河,并且不被魔鬼杀死

魔鬼Devils:游戏可操纵对象,干扰牧师过河

船Boat:游戏可操纵对象,负责搭载牧师或者魔鬼过河

河流River:不可操作对象,用作环境背景的构建

河岸Shore:不可操作对象,用作环境背景的构建

背景backGround:不可操作对象,用作环境背景的构建

以上游戏对象都已做成预制,并且在游戏中通过代码动态生成:

4. 玩家动作表(规则表) 

玩家动作表
玩家动作动作效果动作限制
点击牧师或魔鬼(统称为角色)点击岸上的角色将上船;点击船上的角色将上岸船上满员后角色将无法再上船
点击船只或“移动船只”按钮船只移动到河岸的另一侧船上至多搭载两名角色;船上没角色时无法移动
点击“重新开始”按钮游戏将重新开始,角色和船只复位

5. 文件组织形式

Assets文件夹内,有三个子文件夹,分别是Resources(用于存储游戏资源的预制)、Scenes(存储游戏场景)、Scripts(用于存储C#脚本)。

对于Resources文件夹,其中的三个子文件夹分别存储模型的材质(Materials)、模型的预制(Prefabs)、图像纹理(Textures)。在这里不做展开。

对于Scenes文件夹,用于存储游戏的场景:

其中,整个游戏只有一个主摄像机(Main Camera)、灯光(Directional Light)和一个空对象(Empty),符合项目要求。

对于Script文件夹,则采用了MVC模式对文件进行划分:

其中Models文件夹存储游戏对象及空间关系,对应于MVC模式中的Model;Views文件夹存储玩家交互界面,即GUI,对应于MVC模式中的View;Controllers文件夹存储控制对象,用于执行游戏对象以及整个游戏的控制事件,对应于MVC模式中的Controller。

6. 代码介绍

(1)模型(Model)部分

正如上文所言,该游戏一共有六个对象,因为单独为它们创建一个类文件。其中“牧师”和“魔鬼”可以被归并成一类,即角色Role类。同时,模型部分还含有一个Position类用于存储各个对象处在游戏场景中的位置。

                                                                  背景BackGround

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

public class BackGround
{
    public GameObject bg;
    public BackGround(Vector3 position) {
        bg = GameObject.Instantiate(Resources.Load("Prefabs/backGround", typeof(GameObject))) as GameObject;
        bg.transform.localScale = new Vector3(6,1,1.5f);
        bg.transform.position = position;
        bg.name = "backGround";
    }
}

                                                                  河流River

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

public class River
{
    public GameObject river;
    public River(Vector3 position) {
        river = GameObject.Instantiate(Resources.Load("Prefabs/River", typeof(GameObject))) as GameObject;
        river.transform.localScale = new Vector3(8,2.5f,2);
        river.transform.rotation = Quaternion.Euler(0, 0, 180);
        river.transform.position = position;
        river.name = "River";
    }
}

由于背景和河流都是不可操纵对象,因此只有一个初始化函数。

                                                                  河岸Shore

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

public class Shore 
{
    public GameObject shore;
    public int priestCount, devilCount;  // 牧师和魔鬼的数量
    public Shore (Vector3 position){
        shore = GameObject.Instantiate(Resources.Load("Prefabs/Shore", typeof(GameObject))) as GameObject;
        shore.transform.localScale = new Vector3(8,4.8f,2); 
        shore.transform.position = position;
        priestCount = devilCount = 0;           // 初始牧师和魔鬼数量为0
    }

}

河岸具有两个变量priestCount, devilCount用于存储牧师和魔鬼的数量,在后续控制部分有用。

                                                                  船Boat

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

public class Boat
{
    public GameObject boat;//船对象
    public Role[] roles;//船上的角色
    public bool isRight; //船是否在右边
    public int priestCount, devilCount; //船上的牧师和恶魔数量

    public Boat(Vector3 position) {
        boat = GameObject.Instantiate(Resources.Load("Prefabs/Boat", typeof(GameObject))) as GameObject;
        boat.name = "Boat";
        boat.transform.position = position;
        boat.transform.localScale = new Vector3(2.8f,0.4f,2);   

        roles = new Role[2];        // 船上最多两个角色
        isRight = false;         // 初始在左岸
        priestCount = devilCount = 0;      // 初始牧师和魔鬼数量为0

        boat.AddComponent<BoxCollider>();              // 添加碰撞组件
        boat.AddComponent<Click>();                    // 添加点击组件 
    }
}

除了能够记录牧师与魔鬼的数量,船还需要添加碰撞组件和点击组件。

                                                                  角色Role

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

public class Role 
{
    public GameObject role;//model对应的游戏对象
    public bool isPriest;  // 是否是牧师
    public bool inBoat;  // 是否在船上
    public bool onRight;  // 是否在右岸
    public int id;  // 角色编号
    
    public Role (Vector3 position, bool isPriest, int id) {
        this.isPriest = isPriest;
        this.id = id;            // 角色编号
        onRight = false;         // 初始在左岸
        inBoat = false;          // 初始不在船上
        role = GameObject.Instantiate(Resources.Load("Prefabs/" + (isPriest ? "Priests" : "Devils"), typeof(GameObject))) as GameObject;
        role.transform.rotation = Quaternion.Euler(0, -90, 0);        // 设置角色朝向
        // role.transform.localScale = new Vector3(1,1.2f,1);            // 设置角色大小
        role.transform.position = position;
        role.name = "role" + id;
        role.AddComponent<Click>();                         // 添加点击组件
        role.AddComponent<BoxCollider>();                   // 添加碰撞组件
    }
}

角色也有碰撞组件和点击组件。

                                                                  位置Position

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

public class Position  //存储所有对象的位置
{
    //固定位置(世界坐标)
    public static Vector3 left_shore = new Vector3(-8,-3,0);   // 左岸的位置
    public static Vector3 right_shore = new Vector3(8,-3,0);   // 右岸的位置
    public static Vector3 river = new Vector3(0,-3,0);         // 河流的位置
    public static Vector3 background = new Vector3(0,1,3);        // 背景的位置
    public static Vector3 left_boat = new Vector3(-2.3f,-1.75f,-0.4f);  // 左船的位置
    public static Vector3 right_boat = new Vector3(2.4f, -1.75f, -0.4f); // 右船的位置

    //角色相对于岸边的位置(相对坐标),每一边有6个位置,因此用数组存储
    public static Vector3[] role_shore = new Vector3[] {new Vector3(0.35f,0.77f,0), new Vector3(0.21f,0.77f,0), new Vector3(0.07f,0.77f,0), new Vector3(-0.07f,0.77f,0), new Vector3(-0.21f,0.77f,0), new Vector3(-0.35f,0.77f,0)};
    
    //角色相对于船的位置(相对坐标),船上有2个位置,因此用数组存储
    public static Vector3[] role_boat = new Vector3[] {new Vector3(0.2f,3,0), new Vector3(-0.2f,3,0)};


}

记录各个对象的位置,其中船有两个位置、河岸的左边和右边各有六个位置,因此都使用数组来存储,并且使用相对位置。

(2)视图(View)部分

        视图部分只含有一个文件,即UserGUI。定义了文字显示的样式,同时创建了两个按钮“重新开始”、“移动船只”用于与玩家交互。

                                                                UserGUI

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

// 具体类型:UserGUI
public class UserGUI : MonoBehaviour  // 用户交互界面
{
    private IUserAction userAction;    // 通过接口与游戏逻辑界面交互
    public string gameMessage ;          // 传入想要显示的游戏信息,例如:游戏结束
    public int time;                 // 游戏结束倒计时
    GUIStyle style1, style2;
    void Start()
    {
        time = 60;
        userAction = SSDirector.GetInstance().CurrentSceneController as IUserAction;

        style1 = new GUIStyle();       // 设置信息显示的样式
        style1.normal.textColor = Color.yellow;
        style1.fontSize = 30;

        style2 = new GUIStyle();   // 设置标题的样式
        style2.normal.textColor = Color.black;
        style2.fontSize = 50;
    }

    void OnGUI() {   
        GUI.Label(new Rect(Screen.width / 2 -150, 0, 50, 200), "牧 师 与 魔 鬼", style2);  // 显示游戏标题
        GUI.Label(new Rect(250, 100, 50, 200), gameMessage, style1);        // 显示游戏是否结束
        GUI.Label(new Rect(0,0,100,50), "倒计时: " + time, style1);
        if(GUI.Button(new Rect(0, 50, 100, 50), "重新开始")) {   // 重新开始游戏
            time = 60;
            gameMessage = "";
            userAction.Restart();
        }
        if(GUI.Button(new Rect(0,100,100,50),"移动船只")){      // 点击GO按钮,船开始移动
            userAction.MoveBoat();
        }
        userAction.Check();
    }
}

(3)控制器(Controller)部分

                                          点击组件Click 和 点击动作接口ClickAction

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

public class Click : MonoBehaviour
{
    ClickAction clickAction;
    public void setClickAction(ClickAction clickAction) {      // 设置点击事件
        this.clickAction = clickAction;
    }
    void OnMouseDown() {          // 当鼠标按下时
        clickAction.DealClick();  // 处理点击事件
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface ClickAction
{
    void DealClick();    // 处理点击事件
}

创建了一个点击组件Click用于处理点击事件。同时创建了一个点击动作接口ClickAction来处理点击事件,对于船和角色,只需要实现ClickAction接口的DealClick函数就可以完成点击事件。

                                          移动动作MoveAction 和 移动控制器MoveCtrl

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

public class MoveAction : MonoBehaviour
{
    public bool isMoving = false;        // 是否正在移动
    public float speed = 5;         // 移动速度
    public Vector3 destination;   // 目的地的位置(坐标)
    public Vector3 mid_destination;   // 中间的位置
    void Update()      // 一直执行移动操作,直到到达目的地
    {
        if (transform.localPosition == destination) {     // 如果已经到达目的地
            isMoving = false;     // 停止移动
            return;
        }
        isMoving = true;     // 标记为正在移动
        if (transform.localPosition.x != destination.x  
        && transform.localPosition.y != destination.y) {        // 如果x坐标和y坐标与目的地的x、y坐标都不相等,就先向中间位置移动
            transform.localPosition = Vector3.MoveTowards(transform.localPosition, mid_destination, speed * Time.deltaTime);
        }
        else {          // 如果x坐标或者y坐标与目的地的x、y坐标相等,就直接向目的地直线移动
            transform.localPosition = Vector3.MoveTowards(transform.localPosition, destination, speed * Time.deltaTime);
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MoveCtrl 
{
    GameObject moveObject;      // 需要移动的对象
    public bool GetIsMoving() {    // 判断是否正在移动
        return (moveObject != null && moveObject.GetComponent<MoveAction>().isMoving == true);   // 如果正在移动,返回true
    }

    public void SetMove(Vector3 destination, GameObject moveObject) {   // 设置移动的目的地和移动的对象
        MoveAction test;
        this.moveObject = moveObject;
        if (!moveObject.TryGetComponent<MoveAction>(out test)) {     // 如果对象没有Move组件,就添加Move组件
            moveObject.AddComponent<MoveAction>();    // 添加Move组件
        }

        this.moveObject.GetComponent<MoveAction>().destination = destination;     // 设置目的地
        if (this.moveObject.transform.localPosition.y > destination.y) {    // 如果目的地的y坐标小于当前位置的y坐标,说明从岸上到船上,就先水平移动到中间位置,然后再竖直移动到目的地
            this.moveObject.GetComponent<MoveAction>().mid_destination = new Vector3(destination.x, this.moveObject.transform.localPosition.y, destination.z);
        }
        else {   // 如果目的地的y坐标大于当前位置的y坐标,说明是从船上到岸上,那么就先竖直向上移动到中间位置,然后再水平移动到目的地
            this.moveObject.GetComponent<MoveAction>().mid_destination = new Vector3(this.moveObject.transform.localPosition.x, destination.y, destination.z);
        }
    }
}

移动动作MoveAction内update函数重复执行移动的操作,直到目标到达指定的位置,由于河岸和船有高度差,为了防止碰撞,因此采用折线移动的方式。而移动控制器MoveCtrl则设置了移动的中间位置和目的地。

                                                               船控制器BoatCtrl

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

public class BoatCtrl : ClickAction   // 船可以被点击,所以实现ClickAction接口 
{
    Boat boatModel;     // 船的对象
    IUserAction userAction;
    public BoatCtrl() {
        userAction = SSDirector.GetInstance().CurrentSceneController as IUserAction;     // 获取当前场景的场记
    }
    public void CreateBoat(Vector3 position) {   // 创建船,传入船的位置
        if (boatModel != null) {
            Object.DestroyImmediate(boatModel.boat);       // 如果已经有船了,就销毁
        }
        boatModel = new Boat(position);    
        boatModel.boat.GetComponent<Click>().setClickAction(this);      // 设置船的点击事件
    }
    public Boat GetBoatModel() {     // 获取船
        return boatModel;
    }
    //点击岸上的角色,将角色从岸上移动到船上,返回接下来角色应该到达的位置
    public Vector3 AddRole(Role roleModel) {
        int index = -1;   // 船上的位置索引
        if (boatModel.roles[0] == null) index = 0;      // 如果船上的第一个位置为空,就将角色放在第一个位置
        else if (boatModel.roles[1] == null) index = 1; // 如果船上的第二个位置为空,就将角色放在第二个位置
        else return roleModel.role.transform.localPosition;  // 如果船上的两个位置都不为空,角色无法上船,就返回角色当前的位置

        boatModel.roles[index] = roleModel;   // 将角色放在船上的第index个位置
        roleModel.inBoat = true;          // 将该角色标记为在船上
        roleModel.role.transform.parent = boatModel.boat.transform;  // 将该角色的相对坐标设置为船的坐标
        if (roleModel.isPriest) boatModel.priestCount++;   // 如果该角色是牧师,船上的牧师数量加一
        else boatModel.devilCount++;               // 如果该角色是魔鬼,船上的魔鬼数量加一
        return Position.role_boat[index];   // 返回角色在船上的相对坐标
    }

    //点击船上的角色,将角色从船上移动到岸上
    public void RemoveRole(Role roleModel) {
        for (int i = 0; i < 2; ++i){
            if (boatModel.roles[i] == roleModel) {  // 遍历船上的角色,找到该角色
                boatModel.roles[i] = null;   // 船上的该位置的角色置空
                if (roleModel.isPriest) boatModel.priestCount--;  // 如果该角色是牧师,船上的牧师数量减一
                else boatModel.devilCount--;  // 如果该角色是魔鬼,船上的魔鬼数量减一
                break;
            }
        }
    }

    public bool isEmpty(){   // 判断船是否没载客
        return (boatModel.roles[0] == null && boatModel.roles[1] == null) ? true : false;
    }

    public void DealClick() {       // 对于ClickAction的具体实现,点击船
        if (!isEmpty()) {  // 只有船上有角色时才能点击船,才能移动
            userAction.MoveBoat();             // 移动船
        }
    }
}

船控制器实现了创建船对象、获取船、添加角色(上船)和移出角色(下船)的函数; 同时实现了ClickAction接口, 用于处理点击船只的事件并执行IUserAction的移动船只函数。详细编写见上代码及注释。

                                                          角色控制器RoleCtrl

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

public class RoleCtrl : ClickAction       // 角色可以被点击,所以实现ClickAction接口 
{
    Role roleModel;
    IUserAction userAction;

    public RoleCtrl() {
        userAction = SSDirector.GetInstance().CurrentSceneController as IUserAction;   // 获取当前场景的场记
    }

    public void CreateRole(Vector3 position, bool isPriest, int id) {     // 创建角色,传入角色的位置,是否是牧师,角色的id
        if (roleModel != null) {
            Object.DestroyImmediate(roleModel.role);    // 如果已经有角色了,就销毁
        }
        roleModel = new Role(position, isPriest, id);     // 创建角色,传入角色的位置,是否是牧师,角色的id
        roleModel.role.GetComponent<Click>().setClickAction(this);      // 设置角色的点击事件
    }

    public Role GetRoleModel() {      // 获取角色
        return roleModel;
    }

    public void DealClick() {          // 对于ClickAction的具体实现,点击角色
        userAction.MoveRole(roleModel);
    }
}

角色控制器实现了创建角色,获取角色的操作,同时实现了ClickAction接口, 用于处理点击角色的事件并执行IUserAction的移动角色函数。详细代码见上。

                                                          河岸控制器ShoreCtrl

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

public class ShoreCtrl
{
    Shore shoreModel;
    public void CreateShore(Vector3 position) {  // 创建河岸
        if (shoreModel == null) {
            shoreModel = new Shore(position);
        }
    }
    public Shore GetShore() {  // 获取河岸
        return shoreModel;
    }

    //将角色添加到岸上,返回角色在岸上的相对坐标
    public Vector3 AddRole(Role roleModel) {
        roleModel.role.transform.parent = shoreModel.shore.transform;    // 将当前角色的相对坐标设置为岸的坐标
        roleModel.inBoat = false;
        if (roleModel.isPriest) shoreModel.priestCount++;  // 当前角色是牧师, 牧师数量加一
        else shoreModel.devilCount++;                      // 当前角色是魔鬼, 魔鬼数量加一
        return Position.role_shore[roleModel.id];          // 返回当前角色在岸上的相对坐标
    }
    //将角色从岸上移除
    public void RemoveRole(Role roleModel) {
        if (roleModel.isPriest) shoreModel.priestCount--;     // 当前角色是牧师
        else shoreModel.devilCount--;       // 当前角色是魔鬼
    }
}

河岸控制器实现了创建河岸、获取河岸、添加角色(上岸)和移出角色(离开河岸)的函数;

                    导演SSDirector,场景接口ISceneController,动作接口IUserAction

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

// 导演把握全局,控制场景和游戏流程
public class SSDirector : System.Object  // 它继承至 C# 根对象,所以不会受 Unity 引擎管理,也不要加载
{
    private static SSDirector _instance;  
    public ISceneController CurrentSceneController {get; set;}
    public static SSDirector GetInstance() {  
        if (_instance == null) {
            _instance = new SSDirector();
        }
        return _instance;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 场记
// 抽象类型(角色):ISceneController
public interface ISceneController
{
    void LoadResources();   // 加载资源
    void destroyResourse(); // 销毁资源
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 吃瓜群众
// 抽象类型(角色):IUserAction
// 游戏逻辑与用户界面之间交互的门面
public interface IUserAction {          //用户动作接口
    void MoveBoat();              //移动船
    void MoveRole(Role roleModel);    //移动角色
    void Check();                 //检查游戏是否结束
    void Restart();               //重新开始游戏
}

实现的内容在上文UML图部分已经给出。

                                                       主要控制器FirstController    

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


// 场记
// 具体类型:FirstController
public class FirstController : MonoBehaviour, ISceneController, IUserAction {  // 对于ISceneController和IUserAction接口的具体实现
    ShoreCtrl leftShoreController, rightShoreController;     // 左岸和右岸的控制器
    River river;     // 河流对象
    BackGround backGround;      // 背景对象
    BoatCtrl boatController;    // 船的控制器
    RoleCtrl[] roleControllers;   // 人物的控制器,每个人物分别对应一个控制器
    MoveCtrl moveController;   // 移动的控制器
    bool isRunning;   // 游戏是否在运行
    float time;   // 游戏倒计时
    SSDirector director;   // 导演实例

    public void LoadResources() {     // 对于ISceneController的具体实现,加载资源,初始化游戏
        //角色部分role
        roleControllers = new RoleCtrl[6];      // 数组存储6个角色/控制器
        for (int i = 0; i < 6; ++i) {
            roleControllers[i] = new RoleCtrl();
            roleControllers[i].CreateRole(Position.role_shore[i], i < 3 ? true : false, i);   // 创建角色,传入角色在左岸的位置,依次填写角色的id,前三个是牧师,后三个是魔鬼
        }

        //河岸部分Shore
        leftShoreController = new ShoreCtrl();
        leftShoreController.CreateShore(Position.left_shore);
        leftShoreController.GetShore().shore.name = "left_shore";
        rightShoreController = new ShoreCtrl();
        rightShoreController.CreateShore(Position.right_shore);
        rightShoreController.GetShore().shore.name = "right_shore";

        //将人物添加并定位至左岸  
        foreach (RoleCtrl roleController in roleControllers)      // 遍历角色控制器
        {
            roleController.GetRoleModel().role.transform.localPosition = leftShoreController.AddRole(roleController.GetRoleModel());  // 设置每个角色在左岸的位置
        }

        //船部分boat
        boatController = new BoatCtrl();
        boatController.CreateBoat(Position.left_boat);

        //河流对象river
        river = new River(Position.river);

        //背景对象backGround
        backGround = new BackGround(Position.background);

        //移动控制器
        moveController = new MoveCtrl();

        isRunning = true;    // 游戏开始运行
        time = 60;        // 游戏倒计时60s
    }

    public void destroyResourse(){
        Object.DestroyImmediate(leftShoreController.GetShore().shore);
        Object.DestroyImmediate(rightShoreController.GetShore().shore);
        Object.DestroyImmediate(boatController.GetBoatModel().boat);
        Object.DestroyImmediate(river.river);
        Object.DestroyImmediate(backGround.bg);
        foreach (RoleCtrl roleController in roleControllers) {
            Object.DestroyImmediate(roleController.GetRoleModel().role);
        }
    }

    public void MoveBoat() {    // 对于IUserAction的具体实现,移动船
        if (isRunning == false || moveController.GetIsMoving() || boatController.isEmpty()) return;     // 如果游戏结束或者船正在移动,那么无法再移动船
        if (boatController.GetBoatModel().isRight) {        // 如果船在右岸
            moveController.SetMove(Position.left_boat, boatController.GetBoatModel().boat);   // 设置船的目的地为左岸
        }
        else {       // 如果船在左岸
            moveController.SetMove(Position.right_boat, boatController.GetBoatModel().boat);  // 设置船的目的地为右岸
        }
        boatController.GetBoatModel().isRight = !boatController.GetBoatModel().isRight;       // 每次移动之后,方向取反
    }

    public void MoveRole(Role roleModel) {  // 对于IUserAction的具体实现,移动角色
        if (isRunning == false || moveController.GetIsMoving()) return;      // 如果游戏结束或者船正在移动,那么无法再移动角色
        if (roleModel.inBoat) {            // 如果角色在船上
            if (boatController.GetBoatModel().isRight) {   // 如果船在右岸
                moveController.SetMove(rightShoreController.AddRole(roleModel), roleModel.role);  // 设置角色的目的地为右岸
            }
            else {    // 如果船在左岸
                moveController.SetMove(leftShoreController.AddRole(roleModel), roleModel.role);  // 设置角色的目的地为左岸
            }
            roleModel.onRight = boatController.GetBoatModel().isRight;  // 每次移动之后,角色的方向取决于船的方向
            boatController.RemoveRole(roleModel);   // 将角色从船上移动到岸上
        }
        else {     // 如果角色在岸上
            if (boatController.GetBoatModel().isRight == roleModel.onRight) {  // 如果角色和船在同一边
                if (roleModel.onRight) {  // 如果角色在右岸
                    rightShoreController.RemoveRole(roleModel);  // 将角色从右岸移动到船上
                }
                else {  // 如果角色在左岸
                    leftShoreController.RemoveRole(roleModel);   // 将角色从左岸移动到船上
                }
                moveController.SetMove(boatController.AddRole(roleModel), roleModel.role);  // 设置角色的目的地为船上
            }
        }
    }

    public void Check() {            // 对于IUserAction的具体实现,检查游戏是否结束
        if (isRunning == false) return;     // 如果游戏结束,那么无需再检查
        this.gameObject.GetComponent<UserGUI>().gameMessage = "";    // 游戏信息置空
        if (rightShoreController.GetShore().priestCount == 3) {  // 如果右岸的牧师数量为3,游戏胜利
            this.gameObject.GetComponent<UserGUI>().gameMessage = "牧师成功过岸,游戏胜利!";  // 显示游戏胜利信息
            isRunning = false;   // 游戏结束
        }
        else {
            int leftPriestCount, rightPriestCount, leftDevilCount, rightDevilCount;  // 两岸和船上的牧师和魔鬼数量
            leftPriestCount = leftShoreController.GetShore().priestCount + (boatController.GetBoatModel().isRight ? 0 : boatController.GetBoatModel().priestCount);
            rightPriestCount = rightShoreController.GetShore().priestCount + (boatController.GetBoatModel().isRight ? boatController.GetBoatModel().priestCount : 0);
            leftDevilCount = leftShoreController.GetShore().devilCount + (boatController.GetBoatModel().isRight ? 0 : boatController.GetBoatModel().devilCount);
            rightDevilCount = rightShoreController.GetShore().devilCount + (boatController.GetBoatModel().isRight ? boatController.GetBoatModel().devilCount : 0);
            if ((leftPriestCount != 0 && leftPriestCount < leftDevilCount) || (rightPriestCount != 0 && rightPriestCount < rightDevilCount)) {  // 如果任意一边的牧师数量不为0且小于魔鬼数量,游戏失败
                this.gameObject.GetComponent<UserGUI>().gameMessage = "牧师被魔鬼杀死,游戏结束!";
                isRunning = false;
            }
        }
    }

    public void Restart(){       // 重新开始游戏
        director.CurrentSceneController.destroyResourse();
        director.CurrentSceneController.LoadResources();
    }

    void Awake() {    // 唤醒函数,在游戏开始前调用一次
        director = SSDirector.GetInstance();   // 获取导演实例
        director.CurrentSceneController = this;   // 设置当前场景控制器为本对象
        director.CurrentSceneController.LoadResources();   // 调用上面的LoadResources()函数,加载资源,初始化游戏
        this.gameObject.AddComponent<UserGUI>();   // 添加UserGUI脚本,显示用户交互界面
    }

    void Update() {      // 每帧调用一次
        if (isRunning) {
            time -= Time.deltaTime;     // 游戏剩余时间减少
            this.gameObject.GetComponent<UserGUI>().time = (int)time;   // 设置游戏剩余时间
            if (time <= 0) {
                this.gameObject.GetComponent<UserGUI>().time = 0;   // 游戏剩余时间为0
                this.gameObject.GetComponent<UserGUI>().gameMessage = "时间到! 游戏失败!"; // 显示游戏结束信息
                isRunning = false;  // 游戏结束
            }
        }
    }
}

实现了场景接口ISceneController的加载/删除资源函数,其中在LoadResource中动态加载游戏对象,destroyResource用于销毁游戏对象,以便实现重新开始游戏的功能。

实现了动作接口IUserAction的移动船只、移动角色、检查游戏是否结束、重新开始的函数,其中限制了玩家在船只和角色在移动的过程中无法对其进行操作;检查游戏是否结束只需要检查在某一侧的魔鬼数量是否超过玩家数量即可;重新开始的函数只需要调用destroyResource函数销毁游戏对象,再用LoadResource重新加载即可。

在Awake()函数内定义了导演对象并对其进行赋值;在Update()函数内实现游戏倒计时和游戏信息播报的操作。具体实现见代码及注释。

7. 游戏视频

牧师与魔鬼(Priests and Devils)--unity小游戏_单机游戏热门视频

8. 代码链接

ZhouXSh/unity-3D (gitee.com)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值