Unity3D-魔鬼与牧师游戏开发

本文详细描述了一款基于Unity的游戏设计,包括游戏规则、对象预制、MVC架构的应用,以及角色控制和用户界面的设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、魔鬼与牧师的游戏介绍

     二、游戏的初步分析与设计

(一)游戏中提及的事物(Objects)

(二)玩家动作表(规则表)

(三)各个对象的预制

三、基于MVC框架确定脚本结构:实现模型——视图——控制器的分离

(一)Models

(二)Contollers

(三) View

(四)代码MVC框架结构UML图

四、游戏运行效果


一、魔鬼与牧师的游戏介绍

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!

游戏内容如下:

牧师和魔鬼是一款益智游戏,您将在其中帮助牧师和魔鬼过河。河的一侧有3个祭司和3个魔鬼。他们都想去这条河的另一边,但只有一条船,这条船每次只能载两个人。而且必须有一个人将船从一侧驾驶到另一侧。您可以单击按钮来移动它们,然后单击移动按钮将船移动到另一个方向。如果靠岸的船上和同一侧岸上的牧师被岸上的魔鬼人数所淹没,他们就会被杀死,游戏就结束了。您可以通过多种方式尝试它。让所有的祭司活着!最后所有牧师和魔鬼都成功过河,则表示游戏胜利。

二、游戏的初步分析与设计

(一)游戏中提及的事物(Objects)

牧师(Priest)、魔鬼(Devil)、船(boat)、两岸陆地(land)

(二)玩家动作表(规则表)

动作(玩家事件)响应条件角色效果(结果)
点击牧师/魔鬼游戏未结束;角色在船上角色上岸
点击牧师/魔鬼游戏未结束;角色在岸上角色上船
点击船游戏未结束;船没有在移动;船上至少存在一个角色,至多有两个角色船开到对岸
点击“Restart”重新开始

(三)各个对象的预制

通过创建GameObject并使用Metariel设置好对应的颜色和形状作为预制,最后完成的预制对象如下图:

 

三、基于MVC框架确定脚本结构:实现模型——视图——控制器的分离

(一)Models

包含两个部分:

  • 包含各个对象的模块,每个模块中定义了对象的类
  • 包含自定义的部件,用于处理事件

具体的模型有:船、岸、角色 ,抽象的模型有:点击、移动、位置 

1、船

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

public class Boat
{
    public GameObject boat;//船对象

    public Boat(Vector3 initPos){
        boat = GameObject.Instantiate(Resources.Load("Prefabs/boat", typeof(GameObject))) as GameObject;
        boat.transform.position = initPos;
        boat.AddComponent<Click>();
    }

}

 2、角色

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

public class Role
{
    public GameObject role;

   public Role(int roleType, Vector3 initPos){
        string path = "Prefabs/" + ((roleType == FirstController.PRIEST) ? "priest" : "devil");
        role = GameObject.Instantiate(Resources.Load(path, typeof(GameObject))) as GameObject;
        role.transform.position = initPos;
        role.AddComponent<Click>();
   }
}

 3、陆地

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

public class Land
{
    public GameObject land;

    public Land(Vector3 initPos){
        land = GameObject.Instantiate(Resources.Load("Prefabs/land", typeof(GameObject))) as GameObject;
        land.transform.position = initPos;
    }
}

4、点击

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

public class Click : MonoBehaviour
{
    IObjectController clickAction;
    public void setClickAction(IObjectController clickAction) {
        this.clickAction = clickAction;
    }
    void OnMouseDown() {
        clickAction.DealClick();
    }
}

 5、移动

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

public class Move : MonoBehaviour
{
    public bool isMoving;
    public float speed = 5;
    public Vector3 destination;
    
    void Update()
    {
        if (transform.localPosition == destination) {
            isMoving = false;
            return;
        }
        isMoving = true;
        transform.localPosition = Vector3.MoveTowards(transform.localPosition, destination, speed * Time.deltaTime);
    }
}

6、位置

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

public class Move : MonoBehaviour
{
    public bool isMoving;
    public float speed = 5;
    public Vector3 destination;
    
    void Update()
    {
        if (transform.localPosition == destination) {
            isMoving = false;
            return;
        }
        isMoving = true;
        transform.localPosition = Vector3.MoveTowards(transform.localPosition, destination, speed * Time.deltaTime);
    }
}

(二)Contollers

每一个模型的副本都会有一个对应的控制器(位置模型除外),控制模型的行为。

另外还有一类很重要的控制器就是场景控制器和导演。导演贯穿了所有场景,导演控制器必须被写成单例模式,这就确保了各控制器属于同一场“话剧”。

场景控制器(FirstController)管理所有该场景内的模型控制器,并且实现他们的综合行为。FirstController会生成并且管理:一个BoatController,两个LandController,六个RoleController,一个MoveController。所有该场景内的综合行为都会在这个场景控制器内实现。

最后还有用于规范同一类型的控制器应当具有的行为的接口设计:IObjectContoller应当被所有模型的控制器继承、IScenceController应当被所有场景的控制器继承、IUserAction负责与视图方面沟通的接口。

代码示例:

1、BoatController

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

public class BoatController : IObjectController
{
    
    public bool onLeftside;
    IUserAction userAction;
    public int[] seat;
    public Boat boatModel;

    public BoatController(){
        userAction = SSDirector.GetInstance().CurrentSceneController as IUserAction;
        seat = new int[3];
        Reset();
    }

    public void Reset(){
        onLeftside = true;
        for(int i = 0; i < 3; i++){
            seat[i] = -1;
        }
    }

    public int embark(int roleID){
        for(int i = 0; i < 3; i++){
            if(seat[i] == -1){
                seat[i] = roleID;
                return i;
            }
        }
        return -1;
    }

    public int getEmptySeat(){
        for(int i = 0; i < 3; i++){
            if(seat[i] == -1){
                return i;
            }
        }
        return -1;
    }


    public void disembark(int roleID){
        for(int i = 0; i < 3; i++){
            if(seat[i] == roleID){
                seat[i] = -1;
                return;
            }
        }
    }

    public void CreateModel(){
        boatModel = new Boat(Position.boatLeftPos);
        boatModel.boat.GetComponent<Click>().setClickAction(this);
    }

    public void DealClick(){
        userAction.MoveBoat();
    }

    public GameObject GetModelGameObject(){
        return boatModel.boat;
    }

}

2、 MoveController

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

public class MoveController
{
    GameObject moveObject;

    public bool IsMoving(){
        return(this.moveObject != null && this.moveObject.GetComponent<Move>().isMoving == true);
    }


    public void SetMove(GameObject moveObject, Vector3 destination) {
        // 设置一个新的移动
        Move test;
        this.moveObject = moveObject;
        if (!moveObject.TryGetComponent<Move>(out test)) {
            moveObject.AddComponent<Move>();
        }
        this.moveObject.GetComponent<Move>().destination = destination;
    }
}

3、 SSDirector

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

public class SSDirector : System.Object
{
    static SSDirector _instance;
    public ISceneController CurrentSceneController {get; set;}
    public static SSDirector GetInstance() {
        if (_instance == null) {
            _instance = new SSDirector();
        }
        return _instance;
    }
}

 4、FirstController

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

public class FirstController : MonoBehaviour, ISceneController, IUserAction
{
    public static int LEFTLAND = 0;
    public static int RIGHTLAND = 1;
    public static int BOAT = 2;

    public static int PRIEST = 0;
    public static int DEVIL = 1;

    public static int PLAYING = 0;
    public static int WIN = 1;
    public static int FAILED = 2;

    BoatController BoatCtrl;
    RoleController[] RoleCtrl = new RoleController[6];
    LandController[] LandCtrl = new LandController[2];
    MoveController MoveCtrl;

    int[] rolesID = new int[6]{0,1,2,3,4,5};
    int gameState;

    void Awake(){
        SSDirector director = SSDirector.GetInstance();
        director.CurrentSceneController = this;
        director.CurrentSceneController.Initialize();
    }

    public void Initialize(){
        //如果有,则释放原有的GameObject
        for(int i = 0; i < 6; i++){
            if(RoleCtrl[i] != null){
                Destroy(RoleCtrl[i].GetModelGameObject());
            }
        }
        for(int i = 0; i < 2; i++){
            if(LandCtrl[i] != null){
                Destroy(LandCtrl[i].GetModelGameObject());
            }
        }
        if(BoatCtrl != null){
            Destroy(BoatCtrl.GetModelGameObject());
        }
        // 加载控制器和模型
        BoatCtrl = new BoatController();
        BoatCtrl.CreateModel();

        for(int i = 0; i < 6; i++){
            int roleType = (i < 3) ? PRIEST : DEVIL;
            RoleCtrl[i] = new RoleController(roleType, rolesID[i]);
            RoleCtrl[i].CreateModel();
        }
        LandCtrl[0] = new LandController(LEFTLAND, rolesID);
        LandCtrl[1] = new LandController(RIGHTLAND, rolesID);
        LandCtrl[0].CreateModel();
        LandCtrl[1].CreateModel();
        MoveCtrl = new MoveController();
        //开始游戏
        gameState = PLAYING;
    }

    //将角色的ID转换成数组的下标
    int IDToNumber(int ID){
        for(int i = 0; i < 6; i++){
            if(rolesID[i] == ID){
                return i;
            }
        }
        return -1;
    }

    //点击船时执行
    public void MoveBoat(){
        if(gameState != PLAYING || MoveCtrl.IsMoving()) return;
        CheckAndSetGameState();
        if(BoatCtrl.onLeftside){
            MoveCtrl.SetMove(BoatCtrl.GetModelGameObject(), Position.boatRightPos);
            for(int i = 0; i < 3; i++){
                if(BoatCtrl.seat[i] != -1){
                    RoleController r = RoleCtrl[IDToNumber(BoatCtrl.seat[i])];
                    MoveCtrl.SetMove(r.GetModelGameObject(), Position.seatRightPos[i]);
                }
            }
        }
        else{
            MoveCtrl.SetMove(BoatCtrl.GetModelGameObject(), Position.boatLeftPos);
            for(int i = 0; i < 3; i++){
                if(BoatCtrl.seat[i] != -1){
                    RoleController r = RoleCtrl[IDToNumber(BoatCtrl.seat[i])];
                    MoveCtrl.SetMove(r.GetModelGameObject(), Position.seatLeftPos[i]);
                }
            } 
        }
        BoatCtrl.onLeftside = !BoatCtrl.onLeftside;
    }

    //点击角色时执行
    public void MoveRole(int id){
        int num = IDToNumber(id);
        if(gameState != PLAYING || MoveCtrl.IsMoving()) return;
        int seat;
        switch(RoleCtrl[num].roleState){
            case 0: // LEFTLAND
                if(!BoatCtrl.onLeftside) return;
                LandCtrl[0].LeaveLand(id);
                seat = BoatCtrl.embark(id);
                RoleCtrl[num].GoTo(BOAT);
                if(seat == -1) return;
                MoveCtrl.SetMove(RoleCtrl[num].GetModelGameObject(), Position.seatLeftPos[seat]);
                break;
            case 1: // RIGHTLAND
                if(BoatCtrl.onLeftside) return;
                LandCtrl[1].LeaveLand(id);
                seat = BoatCtrl.embark(id);
                RoleCtrl[num].GoTo(BOAT);
                if(seat == -1) return;
                MoveCtrl.SetMove(RoleCtrl[num].GetModelGameObject(), Position.seatRightPos[seat]);
                break;
            case 2: //BOAT
                if(BoatCtrl.onLeftside){
                    seat = LandCtrl[0].getEmptySeat();
                    BoatCtrl.disembark(id);
                    LandCtrl[0].GoOnLand(id);
                    RoleCtrl[num].GoTo(LEFTLAND);
                    MoveCtrl.SetMove(RoleCtrl[num].GetModelGameObject(), Position.roleLeftPos[seat]);
                }
                else{

                    seat = LandCtrl[1].getEmptySeat();
                    BoatCtrl.disembark(id);
                    LandCtrl[1].GoOnLand(id);
                    RoleCtrl[num].GoTo(RIGHTLAND);
                    MoveCtrl.SetMove(RoleCtrl[num].GetModelGameObject(), Position.roleRightPos[seat]);
                }
                break;
            default: break;
        }
    }

    //判断游戏状态
    public void CheckAndSetGameState(){
        if(gameState != PLAYING) return;
        //判断是否失败
        int[,] rolePos = new int[2, 3]{{0, 0, 0}, {0, 0, 0}};
        foreach(RoleController r in RoleCtrl){
            rolePos[r.roleType, r.roleState]++;
        }
        if((rolePos[0,0]>0 && rolePos[0,0]<rolePos[1,0]) || 
           (rolePos[0,1]>0 && rolePos[0,1]<rolePos[1,1]) || 
           (rolePos[0,2]>0 && rolePos[0,2] < rolePos[1,2])){
            gameState = FAILED;
            return;
        }  
        //判断是否成功
        foreach(RoleController r in RoleCtrl){
            if(r.roleType == 0 && r.roleState != RIGHTLAND){
                return;
            }
        }
        gameState = WIN;
        return;
    }

    //Reset按钮执行的功能
    public void Restart(){
        Initialize();
        gameState = PLAYING;
    }

    //获取游戏当前状态
    public int GetGameState(){
        return gameState;
    }
}

5、IUserAction

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

public interface IUserAction {
    void MoveBoat();
    void MoveRole(int id);
    int GetGameState();
    void Restart();
}

 

(三) View

视图:UserGUI,视图控制了所有与3D游戏实体不相关的,GUI上的组件。比如说:标题、重新开始的按钮、游戏成功或者失败时的提示。

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

public class UserGUI : MonoBehaviour
{
    IUserAction userAction;
    GUIStyle msgStyle, titleStyle;
    void Start()
    {
        userAction = SSDirector.GetInstance().CurrentSceneController as IUserAction;

        msgStyle = new GUIStyle();
        msgStyle.normal.textColor = Color.white;
        msgStyle.alignment = TextAnchor.MiddleCenter;
        msgStyle.fontSize = 30;

        titleStyle = new GUIStyle();
        titleStyle.normal.textColor = Color.white;
        titleStyle.alignment = TextAnchor.MiddleCenter;
        titleStyle.fontSize = 60;
    }

    // Update is called once per frame
    void OnGUI() {
        // 重新开始的按钮
        if(GUI.Button(new Rect(Screen.width*0.4f, Screen.height*0.65f, Screen.width*0.2f, Screen.height*0.1f), "Restart")){
            userAction.Restart();
        }
        // 检查是否正确
        GUI.Label(new Rect(0, 0, Screen.width, Screen.height*0.2f), "Preists and Devils", titleStyle);
        if(userAction.GetGameState() == FirstController.WIN){
            GUI.Label(new Rect(0, Screen.height*0.8f, Screen.width, Screen.height*0.2f), "You Win.", msgStyle);
        }     
        else if(userAction.GetGameState() == FirstController.FAILED){
            GUI.Label(new Rect(0, Screen.height*0.8f, Screen.width, Screen.height*0.2f), "You failed.", msgStyle);
        }
    }
}

(四)代码MVC框架结构UML图

 

四、游戏运行效果展示

将Assert/Scripts/UserGUI拖动到Main Camera上,创建一个新的空GameObject,将FirstController拖动到GameObject上,点击运行即可。

视频展示:Devil and Priest_哔哩哔哩_bilibili

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值