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!

        总结一下就是过河问题,要帮助三个牧师过河,船上最多2人,最少1人,如果河的任意一边魔鬼数量比牧师多,那么游戏失败。游戏成功的条件是让三个牧师都到达对岸。

        上面是游戏的主要内容。游戏的操作就是点击,点击角色让他们上船或下船,点击船让船在两岸移动。下面的项目参考学长学姐的博客实现,感谢学长学姐的优秀博客!

        游戏的UML图如下:

代码部分

        项目的总体结构如下:

        游戏依然遵循MVC模式,即 Model-View-Controller(模型-视图-控制器),下面主要按这三个部分来讲解代码。

Model

        模型部分包含游戏对象数据,本游戏中主要有船、两岸、河、角色。为了方便后面的代码编写,在这一部分中还加入了自定义的click类来处理鼠标的点击事件,同时加入了position类来统一管理游戏中对象的坐标。

        其中Boat.cs代码如下,由于要求游戏场景中只有一个camera、light和gameobject,所以采用了预制,创建对象的方法就是读取预先制作好的预制加载到指定位置。

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

public class Boat
{
    public GameObject boat; //船对象
    public Role[] roles;    //船上的角色
    public bool isRight;    //用于判断船的位置
    public int pastorCount, 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;        // 初始在右边
        pastorCount = devilCount = 0;

        boat.AddComponent<BoxCollider>();
        boat.AddComponent<Click>();
    }
}

View

        视图(View)代表模型包含的数据的可视化。这里只有一个UserGUI类来加载文字等。

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

public class UserGUI : MonoBehaviour
{
    UserAction userAction;
    public string gameMessage ;
    public int time;
    GUIStyle style, bigstyle, dstyle, pstyle;
    // Start is called before the first frame update
    void Start()
    {
        time = 60;
        userAction = Director.GetInstance().CurrentSceneController as UserAction;

        style = new GUIStyle();
        style.normal.textColor = Color.white;
        style.fontSize = 30;
        // 把牧师和魔鬼的颜色设置不一样
        bigstyle = new GUIStyle();
        bigstyle.normal.textColor = Color.white;
        bigstyle.fontSize = 50;
        dstyle = new GUIStyle();
        dstyle.normal.textColor = Color.red;
        dstyle.fontSize = 50;
        pstyle = new GUIStyle();
        pstyle.normal.textColor = Color.yellow;
        pstyle.fontSize = 50;
    }

    // Update is called once per frame
    void OnGUI() {
        userAction.Check();
        GUI.Label(new Rect(250, Screen.height * 0.05f, 50, 200), "Pastor", pstyle);
        GUI.Label(new Rect(415, Screen.height * 0.05f, 50, 200), "and", bigstyle);
        GUI.Label(new Rect(520, Screen.height * 0.05f, 50, 200), "Devil", dstyle);
        GUI.Label(new Rect(350, 100, 50, 200), gameMessage, style);
        GUI.Label(new Rect(0, 0, 100, 50), "Time: " + time, style);
        userAction.RestartGame();
    }
}

Controller

        Controller(控制器)作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开。是游戏的控制逻辑实现的地方,可以说是游戏的主体部分。这个游戏中要控制的东西较多,不能放在一个类里,所以对controller进行分层管理。

Director

        导演类director是游戏中最高层的控制器,像剧组中的导演一样统筹整个项目。这里采用单实例,即只有一个director实例。

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

public class Director : System.Object   // 项目的导演类
{
    // 单实例
    static Director _instance;
    public SceneController CurrentSceneController {get; set;}
    public static Director GetInstance() {
        if (_instance == null) {
            _instance = new Director();
        }
        return _instance;
    }
}

SceneController&UserAction

        场景控制和用户动作是两个接口,用于统筹整个游戏资源的控制和用户的动作。本游戏中控制和动作很少:场景控制只有加载资源,动作只有移动船、移动角色、检查游戏进程(是否解释、是否获胜)和重启游戏。(这里划分正确性存疑,感觉后两个动作也可以放到SceneController中。)

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

public interface SceneController
{
    void LoadResources();
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface UserAction {
    void MoveBoat();
    void MoveRole(Role roleModel);
    void Check();
    void RestartGame();
}

具体的控制类

        下面是对游戏的一些具体实现,首先是最核心的FirstController,他实现了SceneController和UserAction的接口,加载游戏资源、响应用户动作、检查游戏进程等;同时也使用和控制了更具体的控制类,比如船的控制类BoatCtl。

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

public class FirstController : MonoBehaviour, SceneController, UserAction {
    ShoreCtrl leftShoreController, rightShoreController;
    River river;
    BoatCtrl boatController;
    RoleCtrl[] roleControllers;
    MoveCtrl moveController;
    bool isRunning;
    float time;

    public void LoadResources() {
        //role
        roleControllers = new RoleCtrl[6];
        for (int i = 0; i < 6; ++i) {
            roleControllers[i] = new RoleCtrl();
            roleControllers[i].CreateRole(Position.role_shore[i], i < 3 ? true : false, i);
        }

        // 加载此岸和彼岸
        leftShoreController = new ShoreCtrl();
        leftShoreController.CreateShore(Position.left_shore);
        leftShoreController.GetShore().shore.name = "this_shore";
        rightShoreController = new ShoreCtrl();
        rightShoreController.CreateShore(Position.right_shore);
        rightShoreController.GetShore().shore.name = "other_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);

        //move
        moveController = new MoveCtrl();

        isRunning = true;
        time = 60;
    }

    public void MoveBoat() {
        if (isRunning == false || moveController.GetIsMoving()) 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) {
        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() {
        if (isRunning == false)
            return;
        this.gameObject.GetComponent<UserGUI>().gameMessage = "";
        if (rightShoreController.GetShore().pastorCount == 3) {
            this.gameObject.GetComponent<UserGUI>().gameMessage = "You Win!";
            isRunning = false;
        }
        else {
            int leftPastorCount, rightPastorCount, leftDevilCount, rightDevilCount;
            leftPastorCount = leftShoreController.GetShore().pastorCount + (boatController.GetBoatModel().isRight ? 0 : boatController.GetBoatModel().pastorCount);
            rightPastorCount = rightShoreController.GetShore().pastorCount + (boatController.GetBoatModel().isRight ? boatController.GetBoatModel().pastorCount : 0);
            leftDevilCount = leftShoreController.GetShore().devilCount + (boatController.GetBoatModel().isRight ? 0 : boatController.GetBoatModel().devilCount);
            rightDevilCount = rightShoreController.GetShore().devilCount + (boatController.GetBoatModel().isRight ? boatController.GetBoatModel().devilCount : 0);
            if (leftPastorCount != 0 && leftPastorCount < leftDevilCount || rightPastorCount != 0 && rightPastorCount < rightDevilCount) {
                this.gameObject.GetComponent<UserGUI>().gameMessage = "Game Over!";
                isRunning = false;
            }
        }
    }

    public void RestartGame(){
        if (GUI.Button(new Rect(0, 35, 100, 30), "Restart")){
            // 重新加载游戏场景,只有一个场景,那编号就是0
            SceneManager.LoadScene(0);
        }
    }

    void Awake() {
        Director.GetInstance().CurrentSceneController = this;
        LoadResources();
        this.gameObject.AddComponent<UserGUI>();
    }

    // Update is called once per frame
    void Update() {
        if (isRunning) {
            time -= Time.deltaTime;
            this.gameObject.GetComponent<UserGUI>().time = (int)time;
            if (time <= 0) {
                this.gameObject.GetComponent<UserGUI>().time = 0;
                this.gameObject.GetComponent<UserGUI>().gameMessage = "Game Over!";
                isRunning = false;
            }
        }
    }
}

        可以看到FirstController中使用了更具体的控制器,下面以船控制器BoatCtrl为例简单介绍一下。船控制器主要做几件事情:1.创建船;2.将角色从岸上移到船上;3.将角色从船上移到岸上;4.实现船的DealClick()函数。

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

public class BoatCtrl : ClickAction
{
    Boat boatModel;
    UserAction userAction;

    public BoatCtrl() {
        userAction = Director.GetInstance().CurrentSceneController as UserAction;
    }
    public void CreateBoat(Vector3 position) {
        if (boatModel != null) {
            Object.Destroy(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;

        if (index == -1) return roleModel.role.transform.localPosition;

        boatModel.roles[index] = roleModel;
        roleModel.inBoat = true;
        roleModel.role.transform.parent = boatModel.boat.transform;
        if (roleModel.isPastor) boatModel.pastorCount++;
        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.isPastor) boatModel.pastorCount--;
                else boatModel.devilCount--;
                break;
            }
        }
    }

    public void DealClick() {
        if (boatModel.roles[0] != null || boatModel.roles[1] != null) {
            userAction.MoveBoat();
        }
    }
}

         其他的类就不再说明。

        以上就是对游戏结构的全部分析。

        

“牧师与魔鬼”游戏演示

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值