中山大学软件工程-Unity牧师与魔鬼作业

本文详细介绍了使用Unity3D开发牧师与魔鬼过河游戏的过程,包括项目配置、游戏元素、玩家动作、模块设计和实现细节。游戏采用MVC架构,通过组件实现代码复用,利用门面设计模式实现用户动作与控制器的解耦合。文章还提供了代码示例,展示了Character、Boat和Land类的实现,以及GUI组件和移动组件的使用。
摘要由CSDN通过智能技术生成

牧师与魔鬼小游戏

零、写在前面

  • 自定义的数据类不要继承Unity的MonoBehavior类,一开始在判断游戏角色对象是否为空的时候,即判断是否== null,发现判断的结果一直为true,后来发现是因为Character类继承了MonoBehavior类,此类及其子类均不允许被new出来,Unity重载了UnityEngine命名空间下的Object的==运算符,故导致了==的使用出现了问题。

  • 此次作业运用了MVC架构,体会到了模块之间低耦合度的好处,在修改代码时很方便,只需要找到对应的模块进行修改,修改结果不会对其它模块造成太大的影响。各个模块的明确分工,职责驱动使得整个项目能够方向明确地推进。

  • 利用组件的概念,当对象添加了Moveable组件,则其具有了移动的功能,当对象添加了GUIClick组件,则其具有对鼠标点击做出反应的功能,由此实现了代码的重用。

  • 通过运用facade设计模式也进一步体验到接口是如何实现解耦合的——控制器去完成UserAction接口,即UserAction实现了用户动作和控制器的解耦合。

一、 项目配置

  • 首先创建一个新项目,选择3D模板

  • 新项目的文件结构如下:

    • Assets/Resources下存放的是项目动态加载所需的图片以及预制,预制包括按要求制作成预制的牧师、魔鬼、船、河流和河岸,图片则是用于GUI装饰

    • Assets/Materials

    • Assets/Scripts中则存放的是项目代码,各个类之间遵守MVC架构

  • 由于图片是动态加载,故需要将在Inspector菜单中,将图片设置为可读可写状态,才能保证图片能被正常加载。

  • 最后将FirstController代码拖到Main Camera中,Ctrl+B即可运行。

二、游戏提及的事物

  • 牧师

  • 魔鬼

  • 河流

三、玩家动作表(规则表)

玩家动作前提条件结果
点击岸上的牧师或魔鬼船上有空位被点击的牧师或魔鬼移动到船上
点击船上的牧师或魔鬼被点击的牧师或魔鬼移动到岸上
点击GO按钮船上有乘客船移动到对岸
点击Rstart按钮游戏重置

四、 实现过程和方法

1. 总体设计思路

  • 整个项目使用MVC架构,各个代码文件划分如下

    • Model部分:Character、Boat、Land

    • View部分:GUI

    • Controller部分:Director、SceneController、FirstController

    • UserAction是一个接口,控制器去实现UserAction接口,实现用户动作和游戏逻辑实现的解耦合,也即门面设计模式的应用

    • GUIClick和Moveable则是作为两个组件,通过组合的方式作为游戏对象的组件,实现代码的复用

  • Model部分就是将游戏中出现的事物封装为各个类,管理各自的数据,然后受Controller的调用、控制,最后View部分实现将Model数据可视化通过用户界面的呈现。

2. 模块分析

Model部分

  • Character

    • 牧师和魔鬼就是通过实现Character类,其管理的数据的主要为一个GameObject对象,通过Object.Instantiate函数动态加载预制中的牧师或魔鬼模型。

    • Character类的功能主要有实现离岸、上岸、上船、下船的数据更新,以及通过moveable组件控制GameObject对象的移动。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    ​
    public class Character {
        readonly GameObject character;
        readonly int type; //0-priest 1-devil
        readonly Moveable moveableScript;// 移动脚本组件
        readonly GUIClick _GUIClick;// 响应鼠标点击组件
    ​
        private bool isOnBoat;// 是否在船上
        private bool isFinished;//是否已经过河
    ​
        // 构造函数,根据传入的字符串选择创建牧师对象或者魔鬼对象
        public Character(string sel){
            // 动态加载预制
            if(sel == "priest"){
                character = Object.Instantiate(Resources.Load("Prefabs/priest", typeof(GameObject)),Vector3.zero, Quaternion.identity, null) as GameObject;
                type = 0;
            }
            else{
                character = Object.Instantiate(Resources.Load("Prefabs/devil", typeof(GameObject)),Vector3.zero, Quaternion.identity, null) as GameObject;
                type = 1;
            }
            // 为对象添加移动和点击响应组件
            moveableScript = character.AddComponent (typeof(Moveable)) as Moveable;
            _GUIClick = character.AddComponent(typeof(GUIClick)) as GUIClick;
            _GUIClick.bindCharacter(this);
            // 初始化变量
            isOnBoat = false;
            isFinished = false;
        }
    ​
        public int getType(){
            return type;
        }
    ​
        public void setName(string name) {
            character.name = name;
        }
    ​
        public string getName() {
            return character.name;
        }
    ​
        public void getOnBoat(Boat boat){
            // 设置transform为船的子组件,这样当船移动的时候就可以随着船移动
            character.transform.parent = boat.getGameobj().transform;
            isOnBoat = true;
        }
    ​
        public void getOffBoat(){
            character.transform.parent = null;
            isOnBoat = false;
        }
    ​
        public void getOnLand(Land land){
            if(land.getType() == 0){
                isFinished = false;
            }
            else{
                isFinished = true;
            }
            isOnBoat = false;
        }
    ​
        public bool getIsOnBoat(){
            return isOnBoat;
        }
    ​
        public void setPosition(Vector3 pos){
            character.transform.position = pos;
        }
    ​
        public void moveToPosition(Vector3 pos){
            moveableScript.setDestination(pos);
        }
    ​
        public bool getIsFinished(){
            return isFinished;
        }
    ​
        // 重置对象
        public void reset(){
            moveableScript.reset();
            // 如果对象已经过河
            if(isFinished){
                // 通过导演对象获取当前场景的控制器,取得其控制的Land对象
                Land endLand = (Director.getInstance ().currentSceneController as FirstController).endLand;
                endLand.leaveLand(this);
                Land startLand = (Director.getInstance ().currentSceneController as FirstController).startLand;
                getOnLand(startLand);
                setPosition(startLand.getOnLand(this));
            }
            // 如果对象在船上
            if(isOnBoat){
                getOffBoat();
                Land startLand = (Director.getInstance ().currentSceneController as FirstController).startLand;
                Boat boat = (Director.getInstance ().currentSceneController as FirstController).boat;
                boat.removePassenger(this);
                getOnLand(startLand);
                setPosition(startLand.getOnLand(this));
            }
            character.transform.parent = null;        
        }
    ​
    }
  • Boat

    • 主要就是实现船对象,其主要的数据是一个GameObject对象,通过Object.Instantiate函数动态加载预制中的船模型。

    • 通过维护一个Character类数组,实现船上乘客的更新。

    • 主要功能包括:添加乘客、移除乘客、获得当前船上牧师和魔鬼的数量,以及通过moveable组件控制GameObject对象的移动。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    ​
    public class Boat{
        readonly GameObject boat;
        readonly Moveable moveableScript;
        readonly Vector3 startPos = new Vector3(-1.95F,0.68F,0);// 在起始岸边的位置
        readonly Vector3 endPos = new Vector3(1.95F,0.68F,0);// 在终点岸边的位置
        readonly Vector3 startFirstPos = new Vector3(-2.3F,1.15F,0);// 在起始岸边船的第一个座位位置
        readonly Vector3 startSecondPos = new Vector3(-1.54F,1.15F,0);// 在起始岸边船的第二个座位位置
        readonly Vector3 endFirstPos = new Vector3(1.54F,1.15F,0);// 在终点岸边船的第一个座位位置
        readonly Vector3 endSecondPos = new Vector3(2.37F,1.15F,0);// 在终点岸边船的第二个座位位置
    ​
        private Character[] passenger;// 乘客对象数组,存放船上的对象
        private int curPosition;//当前船在哪个岸边 0-start  1-end
        private int count;// 船上人数
    ​
        public Boat(){
            // 初始化对象数组,一开始船上无人,故置为空
            passenger = new Character[2];
            passenger[0] = null;
            passenger[1] = null;
            // 动态加载船的预制
            boat = Object.Instantiate (Resources.Load ("Prefabs/boat", typeof(GameObject)), startPos, Quaternion.identity, null) as GameObject;
            boat.name = "boat";
            // 添加moveable组件使得船可以移动
            moveableScript = boat.AddComponent (typeof(Moveable)) as Moveable;
            // 初始变量
            curPosition = 0;
            count = 0;
        }
    ​
        public void move(){
            if(curPosition == 0){
                moveableScript.setDestination(endPos);
                curPosition = 1;
            }
            else{
                moveableScript.setDestination(startPos);
                curPosition = 0;
            }
        }
    ​
        public bool isEmpty(){
            return count == 0;
        }
    ​
        public bool isFull(){
            return count == 2;
        }
    ​
        public bool addPassenger(Character ch){
            if(count == 2){
                return false;
            }
            // 找到空位置
            for(int i = 0;i < 2;++i){
                if(passenger[i] == null){
                    passenger[i] = ch;
                    break;
                }
            }
            count++;
            return true;
        }
    ​
        public bool removePassenger(Character ch){
            if(count == 0){
                return false;
            }
            // 找到要删除的乘客
            for(int i = 0;i < 2;++i){
                if(passenger[i] != null && passenger[i].getName() == ch.getName()){
                    passenger[i] = null;
                }
            }
            count--;
            return true;
        }
    ​
        public int getCurPosition(){
            return curPosition;
        }
    ​
        public Vector3 getEmptyPosition(){
            // 若船已满,则返回坐标(0,0,0)
            if(count == 2){
                return new Vector3(0,0,0);
            }
            if(curPosition == 0){
                if(passenger[0] == null){
                    return startFirstPos;
                }
                else{
                    return startSecondPos;
                }
            }
            else{
                if(passenger[0] == null){
                    return endFirstPos;
                }
                else{
                    return endSecondPos;
                }
            }
        }
    ​
        public GameObject getGameobj(){
            return boat;
        }
    ​
    ​
        public int getNumOfPriest(){
            int res = 0;
            for(int i = 0;i < 2;++i){
                if(passenger[i] != null && passenger[i].getType() == 0){
                    res++;
                }
            }
            return res;
        }
    ​
        public int getNumOfDevil(){
            int res = 0;
            for(int i = 0;i < 2;++i){
                if(passenger[i] != null && passenger[i].getType() == 1){
                    res++;
                }
            }
            return res;
        }
    ​
        public void reset(){
            count = 0;
            passenger[0] = null;
            passenger[1] = null;
            // 若船在终点,则回到起始岸
            if(curPosition == 1){
                moveableScript.setDestination(startPos);
                curPosition = 0;
            }
        }
    }
  • Land

    • 实现的是河岸对象,包含了一个GameObject对象,t对象,通过Object.Instantiate函数动态加载预制中的河岸模型。

    • Land类主要通过维护三个数组:当前河岸上的对象数组、位置数组、位置状态数组,这三者的下标一一对应。河岸上有六个位置,用于放置游戏角色,当某个位置上有角色的时候,即该位置被占有,此时位置状态数组相同下标对应的值会由变为1.

    • 因此,当有新的角色要上岸时,会先通过查找位置状态数组找到空位置,然后返回相同下标的位置数组的值,并将该对象存入相同下标的对象数组中,实际上就是一个哈希的过程。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    ​
    public class Land{
        private GameObject land;
        readonly Vector3 startPos = new Vector3(-6.6F,0,0);// 起始岸的位置
        readonly Vector3 endPos = new Vector3(6.6F,0,0);// 终点岸的位置
        
        private int type;//河岸类型 0-start 1-end
        private int count;// 岸上人数
        private Character[] curCharacter;// 当前岸上的角色
        readonly Vector3[] place;// 岸上的六个位置坐标 角色数组下标和位置数组下标保持一致
        private int[] emptyPlace;// 岸上六个位置的情况 0-empty 1-not empty
    ​
        public Land(string sel){
            place = new Vector3[6];
            if(sel == "start"){
                land = Object.Instantiate(Resources.Load("Prefabs/land", typeof(GameObject)),startPos, Quaternion.identity, null) as GameObject;
                land.name = "start";
                type = 0;
                count = 0;
                for(int i = 0;i < 6;++i){
                    place[i] = new Vector3(-3.5F-0.8F*i,1.5F,0);
                }
            }
            else{
                land = Object.Instantiate(Resources.Load("Prefabs/land", typeof(GameObject)),endPos, Quaternion.identity, null) as GameObject;
                land.name = "end";
                type = 1;
                count = 0;
                for(int i = 0;i < 6;++i){
                    place[i] = new Vector3(3.5F+0.8F*i,1.5F,0);
                }
            }
            curCharacter = new Character[6];
            emptyPlace = new int[6];
            for(int i = 0;i < 6;++i){
                curCharacter[i] = null;
            }
            for(int i = 0;i < 6;++i){
                emptyPlace[i] = 0;
            }
        }
    ​
        public int getType(){
            return type;
        }
    ​
        public int getCount(){
            return count;
        }
    ​
        public Vector3 getOnLand(Character ch){
            int index = 0;
            // 找到空位置,将角色放入对应下标的角色数组中,方便后面查找删除
            while(true){
                if(emptyPlace[index] == 0){
                    emptyPlace[index] = 1;
                    curCharacter[index] = ch;
                    count++;
                    break;
                }
                else{
                    index++;
                }
            }
            // 返回角色所在位置
            return place[index];
        }
    ​
        public void leaveLand(Character ch){
            for(int i = 0;i < 6;++i){
                if( curCharacter[i] != null && curCharacter[i].getName() == ch.getName()){
                    curCharacter[i] = null;
                    emptyPlace[i] = 0;
                    count--;
                    return;
                }
            }  
        }
    ​
        public Vector3 getEmptyPosition(){
            for(int i = 0;i < 6;++i){
                if(emptyPlace[i] == 0){
                    return place[i];
                }
            }
            return new Vector3(0,0,0);
        }
    ​
        public int getNumOfPriest(){
            int res = 0;
            for(int i = 0;i < 6;++i){
                if(curCharacter[i] == null){
                    continue;
                }
                if(curCharacter[i].getType() == 0){
                    res++;
                }
            }
            return res;
        }
    ​
        public int getNumOfDevil(){
            int res = 0;
            for(int i = 0;i < 6;++i){
                if(curCharacter[i] == null){
                    continue;
                }
                if(curCharacter[i].getType() == 1){
                    res++;
                }
            }
            return res;
        }
    ​
        public void reset(){
            if(type == 0){
                for(int i = 0;i < 6;++i){
                    emptyPlace[i] = 1;
                }
                count = 6;
            }
            else{
                for(int i = 0;i < 6;++i){
                    emptyPlace[i] = 0;
                }
                count = 0;
            }
        }
    }

View部分

  • _GUI

    • 此类通过将用户的行为传递给控制器,从而修改model中的数据,并不断呈现最新的数据

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    ​
    public class _GUI : MonoBehaviour{
        private SceneController sc;
        private UserAction ac;
        private GUIStyle style;
        private int status;
        private Texture2D priest;
        private Texture2D devil;
        private Texture2D rule;
    ​
        void Start(){
            style = new GUIStyle();
            style.fontSize = 40;
            // 获得控制器,通过控制器更新model部分的数据
            sc = Director.getInstance ().currentSceneController;
            ac = Director.getInstance().currentSceneController as UserAction;
            status = (Director.getInstance ().currentSceneController as FirstController).status;
            // 动态加载图片
            priest = Instantiate (Resources.Load ("Pictures/牧师", typeof(Texture2D))) as Texture2D;
            devil = Instantiate (Resources.Load ("Pictures/恶魔", typeof(Texture2D))) as Texture2D;
            rule = Instantiate (Resources.Load ("Pictures/规则", typeof(Texture2D))) as Texture2D;
    ​
            // Debug.Log("Screen.width"+Screen.width);
            // Debug.Log(" Screen.height"+ Screen.height);
        }
    ​
    ​
        void OnGUI(){
            // 同步游戏状态
            status = (Director.getInstance ().currentSceneController as FirstController).status;
            if(GUI.Button(new Rect(795,490, 120, 120),"GO")){
                if(status == 0){
                    ac.goButtonIsClicked();
                }
            }
            if(GUI.Button(new Rect(1095,490, 120, 120), "Restart")){
                ac.restart();
            }
            if(status == 1){
                GUI.Label(new Rect(800,100,300,250),devil);
                GUI.Label(new Rect(247, 50, 100, 50), "You lose!",style);
            }
            if(status == 2){
                GUI.Label(new Rect(800,100,300,250),priest);
                GUI.Label(new Rect(247, 50, 100, 50), "You win!",style);
            }
            GUI.Label(new Rect(0,800,1500,1500),rule);
        }
    }

Controller部分

这一部分基本就是按照课程网站上的指示做

  • Director

    • Director继承自C#根对象,因此不受unity引擎的管理,无需加载,且通过应用单例模式保证全局只有一个实例对象

    • 其主要实现:获取当前游戏的场景、管理游戏全局状态

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    ​
    public class Director : System.Object{
        private static Director _instance;
        public SceneController currentSceneController { get; set; }
    ​
        public static Director getInstance() {
            if (_instance == null) {
                _instance = new Director ();
            }
            return _instance;
        }
    }
    ​
  • SceneController

    • SceneController是一个接口,由具体的场记来实现,对于这个游戏,此接口只有两个函数分别用于实现加载游戏资源和检查游戏状态

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    ​
    public interface SceneController {
        void loadResources ();
        void checkGameStatus();
    }
  • FirstController

    • FirstController是此游戏唯一的一个场记,负责实现GUI和模型数据之间的同步。

    • 其主要就是存放各个对象,并实现SceneController和UserAction接口,实现对模型对象的操作

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    ​
    public class FirstController : MonoBehaviour, SceneController, UserAction{
        readonly Vector3 water_pos = new Vector3(0,-0.4F,0);// 河流预制的位置
        
        public Land startLand;// 起始河岸
        public Land endLand;// 终点河岸
        public Boat boat;// 船
        public int status;//游戏状态 0-gaming 1-lose 2-win 
    ​
        private Character[] characters;// 六个游戏角色
        private _GUI userGUI;// GUI组件
    ​
        
        void Awake() {
            // 将自身设置为当前场记
            Director director = Director.getInstance ();
            director.currentSceneController = this;
            // 将GUI作为一个组件
            userGUI = gameObject.AddComponent <_GUI>() as _GUI;
            characters = new Character[6];
            // 加载游戏资源
            loadResources();
        }
    ​
        // 实现SceneController接口,加载游戏资源
        public void loadResources() {
            GameObject water = Instantiate (Resources.Load ("Prefabs/river", typeof(GameObject)), water_pos, Quaternion.identity, null) as GameObject;
            water.name = "water";
            startLand = new Land ("start");
            endLand = new Land ("end");
            boat = new Boat();
            loadCharacter ();
        }
    ​
        // 实现SceneController接口,检查游戏状态 0->not finish, 1->lose, 2->win
        public void checkGameStatus() { 
            // 船在出发点
            int startNumOfPriest = startLand.getNumOfPriest();
            int startNumOfDevil = startLand.getNumOfDevil();
            int endNumOfPriest = endLand.getNumOfPriest();
            int endNumOfDevil = endLand.getNumOfDevil();
            int boatNumOfPriest = boat.getNumOfPriest();
            int boatNumOfDevil = boat.getNumOfDevil();
            if(endNumOfPriest + endNumOfDevil + boatNumOfPriest + boatNumOfDevil == 6){
                status = 2;
                return ;
            }
            if(boat.getCurPosition() == 1){
                if((startNumOfPriest  < startNumOfDevil && startNumOfPriest > 0)||(endNumOfPriest +  boatNumOfPriest < endNumOfDevil + boatNumOfDevil && endNumOfPriest +  boatNumOfPriest > 0)){
                    status = 1;
                    return ;
                }
            }
            else{
                if((startNumOfPriest + boatNumOfPriest < startNumOfDevil + boatNumOfDevil && startNumOfPriest + boatNumOfPriest > 0) || (endNumOfPriest < endNumOfDevil && endNumOfPriest > 0)){
                    status = 1;
                    return ;
                }
            }
            status = 0;
        }
    ​
        // 加载游戏对象
        private void loadCharacter() {
            for (int i = 0; i < 3; i++) {
                Character ch = new Character("priest");
                ch.setName("priest" + i);
                ch.getOnLand(startLand);
                ch.setPosition (startLand.getOnLand(ch));
                characters[i] = ch;
            }
    ​
            for (int i = 0; i < 3; i++) {
                Character ch = new Character("devil");
                ch.setName("devil" + i);
                ch.setPosition (startLand.getOnLand(ch));
                ch.getOnLand(startLand);
                characters [i+3] = ch;
            }
        }
    ​
        // 实现UserAction接口的goButtonIsClicked函数
        public void goButtonIsClicked(){
            if(!boat.isEmpty()){
                boat.move();
                checkGameStatus();
            }
        }
    ​
        // 实现UserAction接口的characterIsClicked函数
        public void characterIsClicked(Character ch){
            // 角色在船上
            if(ch.getIsOnBoat()){
                if(boat.getCurPosition() == 0){
                    ch.moveToPosition(startLand.getOnLand(ch));
                    ch.getOnLand(startLand);
                }
                else{
                    ch.moveToPosition(endLand.getOnLand(ch));
                    ch.getOnLand(endLand);
                }
                ch.getOffBoat();
                boat.removePassenger(ch);
            }
            // 角色在岸上
            else{
                if(!boat.isFull()){
                    if(ch.getIsFinished() && boat.getCurPosition() == 1){
                        ch.getOnBoat(boat);
                        endLand.leaveLand(ch);
                        ch.moveToPosition(boat.getEmptyPosition());
                        boat.addPassenger(ch);
                    }
                    if(!ch.getIsFinished() && boat.getCurPosition() == 0){
                        ch.getOnBoat(boat);
                        startLand.leaveLand(ch);
                        ch.moveToPosition(boat.getEmptyPosition());
                        boat.addPassenger(ch);
                    }
                }
    ​
            }
        }
    ​
        // 重置游戏
        public void restart(){
            status = 0;
            for(int i = 0;i < 6;++i){
                characters[i].reset();
            }
            boat.reset();
            startLand.reset();
            endLand.reset();        
        }
    }

组件部分

  • GUIClick

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
​
public class GUIClick : MonoBehaviour{
    UserAction action;
    Character bindingCharacter;// 组件当前绑定的角色对象
    SceneController sc;
    int status;// 游戏状态
​
    public void bindCharacter(Character ch){
        bindingCharacter = ch;
        status = (Director.getInstance ().currentSceneController as FirstController).status;
    }
​
    void Start(){
        action = Director.getInstance().currentSceneController as UserAction;
        sc = Director.getInstance().currentSceneController;
    }
​
    void OnMouseDown(){
        // 同步游戏状态
        status = (Director.getInstance ().currentSceneController as FirstController).status;
        // 只有在游戏中点击才有效
        if(status == 0){
            action.characterIsClicked(bindingCharacter);
        }
    }
}
  • Moveable

    此代码参考了师兄博客的实现,由于河岸和船不在同一水平线,故让游戏角色在上下船的时候以折线移动

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
​
public class Moveable : MonoBehaviour{
    readonly float speed = 10;
​
    int moving_status;  // 0->not moving, 1->moving to middle, 2->moving to dest
    Vector3 dest;
    Vector3 middle;
​
    void Update() {
        if (moving_status == 1) {
            transform.position = Vector3.MoveTowards (transform.position, middle, speed * Time.deltaTime);
            if (transform.position == middle) {
                moving_status = 2;
            }
        } 
        else if (moving_status == 2) {
            transform.position = Vector3.MoveTowards (transform.position, dest, speed * Time.deltaTime);
            if (transform.position == dest) {
                moving_status = 0;
            }
        }
    }
​
    public void setDestination(Vector3 _dest) {
        dest = _dest;
        middle = _dest;
        if (_dest.y == transform.position.y) {  // boat moving
            moving_status = 2;
        }
        else if (_dest.y < transform.position.y) {  // character from coast to boat
            middle.y = transform.position.y;
        } else {                                // character from boat to coast
            middle.x = transform.position.x;
        }
        moving_status = 1;
    }
​
    public void reset() {
        moving_status = 0;
    }
}

五、效果展示

[]: Unity3D作业-牧师与魔鬼过河演示视频_哔哩哔哩_bilibili

六、项目地址

huanghj78/Unity-Homework - 码云 - 开源中国 (gitee.com)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值