自动巡逻兵游戏设计

巡逻兵游戏

要求:

  • 创建一个地图和若干巡逻兵(使用动画);
  • 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
  • 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
  • 巡逻兵在设定范围内感知到玩家,会自动追击玩家;失去玩家目标后,继续巡逻;
  • 计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束

未完成要求:

  • 使用动画实现巡逻兵行走,追捕以及玩家行走,死亡的动作;
  • 使用工厂模式生产巡逻兵;

Bug:

  • Restart后的游戏场景颜色大变,感觉是重新加载场景中使用的函数加载的场景问题;
  • 游戏模式要求玩家严格从一个门进,从另一个门出,也就是说,存在无限从一个门进入刷分的可能性。这应该设计当玩家离开地图范围时游戏自动结束,只有当玩家在地图中时游戏状态才处于“游戏中”。

参考博客
我的项目
演示视频

设计思路

本次实验大致分为以下几个部分:

游戏对象预设

游戏对象预设这部分需要完成巡逻兵(Cube)玩家(Cube)房间(Maze)的设计。

房间(Maze)
这里写图片描述
如图,房间底部由三个正方形拼接而成,墙则是由Cube凭借而成。材质图片在网上下载导入即可。
房间需要获取当前房间内的巡逻兵(Patrol),以便改变其当前运动状态。

//获取当前房间内的patrol
public class CurrentPatrol : MonoBehaviour {
    public GameObject patrol;
    void OnCollisionEnter(Collision other)
    {
        if (other.gameObject.tag == "patrol") {
            patrol = other.gameObject;
        }
    }
}

在地图的边界有碰撞体Gate,用于检测玩家进入或离开该房间。Gate在碰撞发生时发布消息,由计分器(ScoreController)完成分数++的操作。

//使用订阅与发布模式传递消息
public class GateController : MonoBehaviour {
    public delegate void AddScore ();
    public static event AddScore addScore;

    void OnTriggerEnter(Collider other)
    {
        //碰撞时检测,改变patrol状态,发布加分的事件
        if (other.gameObject.tag == "player") {
            if (this.transform.parent.GetComponent<CurrentPatrol> ().patrol.GetComponent<PatrolController> ().state == 0)
                this.transform.parent.GetComponent<CurrentPatrol> ().patrol.GetComponent<PatrolController> ().state = 1;
            else {
                this.transform.parent.GetComponent<CurrentPatrol> ().patrol.GetComponent<PatrolController> ().state = 0;
                if (addScore != null)
                    addScore ();
            }
        }

玩家(Cube)
外形为红色正方体。
这里写图片描述
玩家部分检测与巡逻兵的碰撞。如碰撞,发布游戏结束的消息,由场记(SceneController)处理。

public class PlayerController : MonoBehaviour {

    public delegate void GameOver ();
    public static event GameOver gameOver_message;

    void OnCollisionEnter(Collision other)
    {
        //检测碰撞,如碰撞,发布游戏结束消息
        if (other.gameObject.tag == "patrol") {
            if (gameOver_message != null)
                gameOver_message ();
        }
        this.GetComponent<Rigidbody> ().velocity = new Vector3 (0, 0, 0);
    }

    void Start()
    {
        //取消碰撞后的旋转
        this.GetComponent<Rigidbody> ().freezeRotation = true;
    }
}

巡逻兵(Cube)
滑稽正方体。
这里写图片描述
巡逻兵需要完成:

  • 没有玩家进入时循环巡逻(patrol());
  • 有玩家进入时追捕玩家(chase(player))。
public class PatrolController : MonoBehaviour {

    private float x, z; //记录巡逻兵位置
    public float speed = 0.5f; //巡逻兵速度
    public int state = 0; //巡逻兵状态,0为巡逻,1为追捕
    public GameObject player; //追捕对象(玩家)
    int turn = 0; 
    private bool flag = true;
    private float dis = 0;

    public SceneController currentsceneController;
    // Use this for initialization
    void Start () {
        currentsceneController = Director.get_instance ().current_scene;
        player = currentsceneController.player;
        x = this.transform.position.x;
        z = this.transform.position.z;
    }

    void FixedUpdate()
    {
        if (state == 0) {
            patrol ();
        } else if (state == 1) {
            chase (player);
        }
    }

    void patrol()
    {
        //六边形巡逻
        if (flag) {
            switch (turn) {
            case 0:
                x++;
                z--;
                break;
            case 1:
                x = x + 2;
                break;
            case 2:
                x++;
                z++;
                break;
            case 3:
                x--;
                z++;
                break;
            case 4:
                x = x - 2;
                break;
            case 5:
                x--;
                z--;
                break;
            }
            flag = false;
        }
        dis = Vector3.Distance (this.transform.position, new Vector3 (x, 1f, z));
        //判断是否到达指定位置(由于浮点数没有相等,则当dis小于某个值时判定到达)
        if (dis > 0.7) {
            transform.position = Vector3.MoveTowards (this.transform.position, new Vector3 (x, 1f, z), speed * Time.deltaTime);
        } else {
            turn = (turn + 1) % 6;
            flag = true;
        }
    }

    void chase(GameObject other)
    {
        //追捕,朝玩家位置移动
        transform.position = Vector3.MoveTowards (this.transform.position, player.transform.position, 1.5f * speed * Time.deltaTime);
    }

}
游戏控制

控制部分包括计分器(ScoreController)导演(Director)场记(SceneController)

计分器(ScoreController)
计分器订阅Gate发布的分数增加事件并处理。

public class ScoreController : MonoBehaviour {
    public int Score = 0;
    // Use this for initialization
    void OnEnable(){
        GateController.addScore += add;
    }

    void OnDisable(){
        GateController.addScore -= add;
    }

    void add(){
        Score++;
    }

    void Start ()
    {
        //设置当前场景的计分器
        Director.get_instance ().current_score = this;
    }
}

导演(Director)
与之前几次实验大致相同,采用单例模式。

public class Director : System.Object{
    private static Director instance;
    public SceneController current_scene; //当前场景
    public ScoreController current_score; //当前计分器

    public static Director get_instance(){
        if (instance == null) {
            instance = new Director ();
        }
        return instance;
    }
}

场记(SceneController)
场记需要实现两个接口。

public interface scene_interface
{
    void LoadResources(); //加载资源
}
public interface UserAction
{
    void Restart(); //重启
}

除此之外,场记需要订阅玩家发布的游戏结束消息并进行处理。

//加载资源,这里偷懒了就没用工厂模式了。。
//加载地图(5个房间拼接),每个房间加载一个巡逻兵
    public void LoadResources ()
    {
        player = Instantiate (Resources.Load ("Prefabs/Player")) as GameObject;
        int x = 0, z = 0;
        int temp = 1;
        Instantiate (Resources.Load ("Prefabs/maze"), new Vector3 (x, 0, z), Quaternion.identity);
        Instantiate (Resources.Load ("Prefabs/Patrol"), new Vector3 (x - 3, 1.5f, z), Quaternion.identity);
        for (int i = 0; i < 2; i++) {
            for (int k = 0; k < 2; k++) {
                temp = -temp;
                x = 15 * i * temp;
                z = (2 - i) * 5 * temp;
                Instantiate (Resources.Load ("Prefabs/maze"), new Vector3 (x, 0, z), Quaternion.identity);
                Instantiate (Resources.Load ("Prefabs/Patrol"), new Vector3 (x - 3, 1.5f, z), Quaternion.identity);
            }
        }
    }
    //对玩家发布的游戏结束消息的处理
    void OnEnable()
    {
        PlayerController.gameOver_message += GameOverEvent;
    }

    void OnDisable()
    {
        PlayerController.gameOver_message -= GameOverEvent;
    }

    void GameOverEvent()
    {
        result = "Game Over!!!";
        game_state = 0;
    }
    //重新加载场景(感觉这个函数有点问题。。Restart之后场景颜色大变,有点难受)
    public void Restart()
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
        game_state = 1;
    }
UI及其他

这部分包括UI设计,以及增加相机随玩家移动而移动

UI设计
UI需要完成使用方向键操控玩家移动,并展示游戏规则,游戏结束标志以及当前分数。

    //方向键控制移动
    void Update () {
        if (currentscene.game_state != 0) {
            float translationX = Input.GetAxis ("Horizontal") * speed * Time.deltaTime;
            float translationZ = Input.GetAxis ("Vertical") * speed * Time.deltaTime;
            player.transform.Translate (translationX, 0, 0);
            player.transform.Translate (0, 0, translationZ);
        }
    }
    void OnGUI()
    {
        //字体样式
        GUIStyle font = new GUIStyle ();
        font.fontSize = 30;
        font.normal.textColor = new Color (255, 255, 255);

        if (currentscene.game_state == 0) {
            GUI.Label (new Rect (220, 80, 120, 40), currentscene.result, font); // 游戏结束标志
        } else {
            if (GUI.RepeatButton (new Rect (0, 0, 120, 40), "Rule")) {
                    GUI.Label (new Rect(220, 50, 500, 500), "Use direction key to move your ball.\nTry you best to avoid the chaser to \nget one point. If you are caught, game over.", font);
            } //游戏规则
            //当前分数
            GUI.Label (new Rect (0, 120, 120, 40), "Score: " + Director.get_instance ().current_score.Score, font); //
        }
        //重启按钮
        if (GUI.Button (new Rect (0, 60, 120, 40), "Restart")) 
            ui.Restart ();
    }

相机移动

public class CameraMove : MonoBehaviour {
    public GameObject player;
    private SceneController currentscene;
    private Vector3 offset;

    // Use this for initialization
    void Start () {
        currentscene = Director.get_instance ().current_scene;
        player = currentscene.player;
        offset = player.transform.position - this.transform.position;
    }

    // Update is called once per frame
    void Update () {
        this.transform.position = player.transform.position - offset;
    }
}

总结

做完实验对发布与订阅模式有点思路了,之前感觉有点迷。动画资源还是个没掌握的点,之后要抽时间自己学习一下,再把这个巡逻兵的游戏改进一下。现有做出来的东西仍然摆脱不了“用户体验极差“的烙印,还需继续努力。

猜你喜欢

转载自blog.csdn.net/liyike12/article/details/80287462