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

这次实验让我们用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 initialization
	void 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看,这是传送门

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值