Unity3d-learning 牧师与恶魔

空间与运动_编程题


1、编程实践


  • 阅读以下游戏脚本

  • 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!

程序需要满足的要求:

  • play the game ( http://www.flash-game.net/game/2535/priests-and-devils.html )
  • 列出游戏中提及的事物(Objects)
  • 用表格列出玩家动作表(规则表),注意,动作越少越好
  • 请将游戏中对象做成预制
  • 在 GenGameObjects 中创建 长方形、正方形、球 及其色彩代表游戏中的对象。
  • 使用 C# 集合类型 有效组织对象
  • 整个游戏仅 主摄像机 和 一个 Empty 对象, 其他对象必须代码动态生成!!! 。 整个游戏不许出现 Find 游戏对象, SendMessage 这类突破程序结构的 通讯耦合 语句。 违背本条准则,不给分
  • 请使用课件架构图编程,不接受非 MVC 结构程序
  • 注意细节,例如:船未靠岸,牧师与魔鬼上下船运动中,均不能接受用户事件!

牧师和魔鬼游戏是一款益智类游戏,游戏的目标是将3个牧师和3个魔鬼从河的一端安全地送到河的另一端。在运送过程中,船可以搭载两个人,而且必须有一人掌船。无论何时,只要河一边的魔鬼数量多于牧师的数量,游戏就会以失败结束。

  • 在这个游戏中提及的事物有:

    • 3个牧师
    • 3个恶魔
    • 2个河岸
    • 1个船
    • 1条河
  • 游戏中玩家动作表为:

    • 每次开船时,船上至少有一个牧师或者一个恶魔;
    • 当玩家点击岸上的牧师或者恶魔时,这个牧师或恶魔应该跳上船;
    • 当某一个岸边的魔鬼数大于了牧师数量时,游戏失败;
    • 当3个牧师与3个魔鬼均到达河的另一岸时,游戏成功;
    • 当玩家点击重置按钮时,全部游戏对象回到最初的位置。
玩家动作 条件
船从起始处向终点处开船 船在起始处,船上至少有一个牧师或一个恶魔
船从终点处向起始处开船 船在终点处,船上至少有一个牧师或一个恶魔
恶魔在起始处上船 起始处有恶魔,船上有一个空位
牧师在起始处上船 起始处有牧师,船上有一个空位
船的左侧空位下船 船的左侧位上有牧师或恶魔
船的右侧空位下船 船的右侧位上有牧师或恶魔

先放出游戏成品图:
这里写图片描述
在制作的过程中,我的第一步首先是完成游戏界面的设计。我是先把GameObject先通过实例确定位置、大小。此处的河岸是用Cube加上贴图生成的;船是由Cylinder加上贴图生成的。此处的水,是用Unity自带的资源包生成的。找到水预设的方法如下:

  • 1、点击Assets,找到import package,点击Environment
  • 2、在Standard Assets中找到Environment,再找到Water,勾选后点击右下方的import即可。
    这里写图片描述
    由于恶魔与牧师的3d版图形没有,于是我这里使用了Cube代替牧师,Sphere代替恶魔。
    在大体设置好了游戏界面的时候,即可通过拖动,生成游戏中各个对象的预设。这一步后,效果如下:
    这里写图片描述

之后开始编程。MVC模式的要求,此游戏的架构可以是如下模式的:

模块 功能
GenGameObject 生成游戏中各个对象;判定游戏输赢
Director 控制游戏中各个对象的基类以及各种相应的事件
ClickGUI 检测用户操作,调用各个对象的方法
UserGUI 向用户提供提示信息

对于Director,我们需要实现的首先是个Director的对象,以及实现SceneController与UserAction两个接口:

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;
    }
}

public interface SceneController {
        void loadResources ();
    }

    public interface UserAction {
        void moveBoat();
        void characterIsClicked(MyCharacterController characterCtrl);
        void restart();
    }

在Director我们还需要实现各个游戏对象方法的接口类:
如对于船的移动,我们需要注意的是:判定船上是否有至少一个牧师或恶魔,船是从哪一边的河岸出发,向哪一边的河岸开动。此处定义了一个int型变量moving_status,有一下这个类:

public class Moveable: MonoBehaviour {
    readonly float move_speed = 20;
    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, move_speed * Time.deltaTime);
            if (transform.position == middle) {
                moving_status = 2;
            }
        } else if (moving_status == 2) {
            transform.position = Vector3.MoveTowards (transform.position, dest, move_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;
    }
}

仿照这个类,我们还需要完成在事件列表中其他的几个类对象:

类对象 功能
CharacterController 对牧师与恶魔控制的一个类,完成牧师与恶魔上船、下船、随船一起过等事件时单独游戏对象位置的变化
BoatController 对船的状态进行控制,判断船上有多少个牧师或者恶魔
CoastController 对河岸的状态进行控制,判断岸上有多少牧师与恶魔

在ClickGUI中,我们需要的是检测用户鼠标点击的事件,并通过接口,完成对事件的响应。所以在ClickGUI中,我们的代码实现比较简单,主要如下:

public class ClickGUI : MonoBehaviour {
    UserAction action;
    MyCharacterController characterController;

    public void setController(MyCharacterController characterCtrl) {
        characterController = characterCtrl;
    }

    void Start() {
        action = Director.getInstance ().currentSceneController as UserAction;
    }

    void OnMouseDown() {
        if (gameObject.name == "boat") {
            action.moveBoat ();
        } else {
            action.characterIsClicked (characterController);
        }
    }
}

在UserGUI 中我们需要完成预设的实例化:

readonly Vector3 water_pos = new Vector3(0,0.5F,0);
UserGUI userGUI;

public CoastController fromCoast;
public CoastController toCoast;
public BoatController boat;
private MyCharacterController[] characters;

void Awake() {
    Director director = Director.getInstance ();
    director.currentSceneController = this;
    userGUI = gameObject.AddComponent <UserGUI>() as UserGUI;
    characters = new MyCharacterController[6];
    loadResources ();
}

public void loadResources() {
    GameObject water = Instantiate (Resources.Load ("Perfabs/Water", typeof(GameObject)), water_pos, Quaternion.identity, null) as GameObject;
    water.name = "water";

    fromCoast = new CoastController ("from");
    toCoast = new CoastController ("to");
    boat = new BoatController ();

    loadCharacter ();
}
//加载资源
private void loadCharacter() {
    for (int i = 0; i < 3; i++) {
        MyCharacterController cha = new MyCharacterController ("priest");
        cha.setName("priest" + i);
        cha.setPosition (fromCoast.getEmptyPosition ());
        cha.getOnCoast (fromCoast);
        fromCoast.getOnCoast (cha);

        characters [i] = cha;
    }

    for (int i = 0; i < 3; i++) {
        MyCharacterController cha = new MyCharacterController ("devil");
        cha.setName("devil" + i);
        cha.setPosition (fromCoast.getEmptyPosition ());
        cha.getOnCoast (fromCoast);
        fromCoast.getOnCoast (cha);

        characters [i+3] = cha;
    }
}
//实例化牧师与恶魔对象
int check_game_over() { // 0->not finish, 1->lose, 2->win
    int from_priest = 0;
    int from_devil = 0;
    int to_priest = 0;
    int to_devil = 0;

    int[] fromCount = fromCoast.getCharacterNum ();
    from_priest += fromCount[0];
    from_devil += fromCount[1];

    int[] toCount = toCoast.getCharacterNum ();
    to_priest += toCount[0];
    to_devil += toCount[1];

    if (to_priest + to_devil == 6)      // win
        return 2;

    int[] boatCount = boat.getCharacterNum ();
    if (boat.get_to_or_from () == -1) { // boat at toCoast
        to_priest += boatCount[0];
        to_devil += boatCount[1];
    } else {    // boat at fromCoast
        from_priest += boatCount[0];
        from_devil += boatCount[1];
    }
    if (from_priest < from_devil && from_priest > 0) {      // lose
        return 1;
    }
    if (to_priest < to_devil && to_priest > 0) {
        return 1;
    }
    return 0;           // not finish}
//判断游戏输赢

public void restart() {
    boat.reset ();
    fromCoast.reset ();
    toCoast.reset ();
    for (int i = 0; i < characters.Length; i++) {
        characters [i].reset ();
    }
}
//游戏重新开始

此外我们在游戏的过程中,还需要加载一些对用户的提示信息:

public class UserGUI : MonoBehaviour {
    private UserAction action;
    public int status = 0;
    GUIStyle style;
    GUIStyle buttonStyle;

    void Start() {
        action = Director.getInstance ().currentSceneController as UserAction;

        style = new GUIStyle();
        style.fontSize = 40;
        style.alignment = TextAnchor.MiddleCenter;

        buttonStyle = new GUIStyle("button");
        buttonStyle.fontSize = 30;
    }
    void OnGUI() {
        if (status == 1) {
            GUI.Label(new Rect(Screen.width/2-50, Screen.height/2-85, 100, 50), "Gameover!", style);
            if (GUI.Button(new Rect(Screen.width/2-70, Screen.height/2, 140, 70), "Restart", buttonStyle)) {
                status = 0;
                action.restart ();
            }
        } else if(status == 2) {
            GUI.Label(new Rect(Screen.width/2-50, Screen.height/2-85, 100, 50), "You win!", style);
            if (GUI.Button(new Rect(Screen.width/2-70, Screen.height/2, 140, 70), "Restart", buttonStyle)) {
                status = 0;
                action.restart ();
            }
        }
    }
}

大体的代码就是这样完成的了,放上一张游戏成功的截图吧~
这里写图片描述

完整代码,请点击此处

猜你喜欢

转载自blog.csdn.net/emilybluse/article/details/79809245