3D游戏编程实践——智能巡逻兵

编程实践——智能巡逻兵

github地址:https://github.com/ctlchild/SYSU-unity3d-learning/tree/master/hw6

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

游戏开始时初始化函数如下

void Start () {
        SSDirector director = SSDirector.GetInstance ();
        director.CurrentScenceController = this;
        patrol_factory = Singleton<PropFactory>.Instance;
        action_manager = gameObject.AddComponent<PatrolActionManager> () as PatrolActionManager;
        LoadResources ();
        main_camera.GetComponent<CameraFlow> ().follow = player;
        recorder = Singleton<ScoreRecorder>.Instance;
    }

    public void LoadResources () {
        Instantiate (Resources.Load<GameObject> ("Prefabs/Plane"));
        player = Instantiate (Resources.Load ("Prefabs/Player"), new Vector3 (0, 9, 0), Quaternion.identity) as GameObject;
        patrols = patrol_factory.GetPatrols ();
        //所有侦察兵移动
        for (int i = 0; i < patrols.Count; i++) {
            action_manager.GoPatrol (patrols[i]);
        }
    }

用工厂模式实现巡逻兵的生成,并使用了单例模式。棋盘分成了3*3一共9块区域,每个区域有一位巡逻兵。

public class PropFactory : MonoBehaviour {
    private GameObject patrol = null; //巡逻兵
    private List<GameObject> used = new List<GameObject> (); //正在被使用的巡逻兵
    private Vector3[] vec = new Vector3[9]; //保存每个巡逻兵的初始位置

    public FirstSceneController sceneControler; //场景控制器

    public List<GameObject> GetPatrols () {
        int[] pos_x = {-6, 4, 13 };
        int[] pos_z = {-4, 6, -13 };
        int index = 0;
        //生成不同的巡逻兵初始位置
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                vec[index] = new Vector3 (pos_x[i], 0, pos_z[j]);
                index++;
            }
        }
        for (int i = 0; i < 9; i++) {
            patrol = Instantiate (Resources.Load<GameObject> ("Prefabs/Patrol"));
            patrol.transform.position = vec[i];
            patrol.GetComponent<PatrolData> ().sign = i + 1;
            patrol.GetComponent<PatrolData> ().start_position = vec[i];
            used.Add (patrol);
        }
        return used;
    }
    public void StopPatrol () {
        //切换所有侦查兵的动画
        for (int i = 0; i < used.Count; i++) {
            used[i].gameObject.GetComponent<Animator> ().SetBool ("run", false);
        }
    }
}

摄像机也是单例模式,它会锁定玩家视角,其中使用了Vector3.Lerp()函数用插值的方法对摄像机移动进行了平滑过渡

public class CameraFlow : MonoBehaviour {
    public GameObject follow; //跟随的物体
    public float smothing = 5f; //相机跟随的速度
    Vector3 offset; //相机与物体相对偏移位置

    void Start () {
        offset = transform.position - follow.transform.position;
    }

    void FixedUpdate () {
        Vector3 target = follow.transform.position + offset;
        //摄像机自身位置到目标位置平滑过渡
        transform.position = Vector3.Lerp (transform.position, target, smothing * Time.deltaTime);
    }
}

记分员也是单例模式,实现了分数增加函数和获得当前分数函数

public class ScoreRecorder : MonoBehaviour {
    public FirstSceneController sceneController;
    public int score = 0; //分数
    // Use this for initialization
    void Start () {
        sceneController = (FirstSceneController) SSDirector.GetInstance ().CurrentScenceController;
        sceneController.recorder = this;
    }
    public int GetScore () {
        return score;
    }
    public void AddScore () {
        score++;
    }
}

使用了订阅与发布模式实现分数管理和游戏状态管理,在类GameEventManager中设置了函数public delegate void ScoreEvent ()public delegate void GameoverEvent ()GameEventManager是发布者。

public class GameEventManager : MonoBehaviour {
    //分数变化
    public delegate void ScoreEvent ();
    public static event ScoreEvent ScoreChange;
    //游戏结束变化
    public delegate void GameoverEvent ();
    public static event GameoverEvent GameoverChange;

    //玩家逃脱
    public void PlayerEscape () {
        if (ScoreChange != null) {
            ScoreChange ();
        }
    }
    //玩家被捕
    public void PlayerGameover () {
        if (GameoverChange != null) {
            GameoverChange ();
        }
    }
}

FirstSceneController是订阅者,通过信息的变化通知记分员,相关代码如下。

public int GetScore () {
    return recorder.GetScore ();
}
public bool GetGameover () {
    return game_over;
}
public void Restart () {
    SceneManager.LoadScene ("Scenes/mySence");
}

void OnEnable () {
    GameEventManager.ScoreChange += AddScore;
    GameEventManager.GameoverChange += Gameover;
}
void OnDisable () {
    GameEventManager.ScoreChange -= AddScore;
    GameEventManager.GameoverChange -= Gameover;
}
void AddScore () {
    recorder.AddScore ();
}
void Gameover () {
    game_over = true;
    patrol_factory.StopPatrol ();
    action_manager.DestroyAllAction ();
}

只有触发某些条件时才会调用函数void ScoreEvent ()void GameoverEvent ()

玩家与巡逻兵相撞

public class PlayerCollide : MonoBehaviour {
    void OnCollisionEnter (Collision other) {
        //当玩家与巡逻兵相撞
        if (other.gameObject.tag == "Player") {
            other.gameObject.GetComponent<Animator> ().SetTrigger ("death");
            this.GetComponent<Animator> ().SetTrigger ("shoot");
            Singleton<GameEventManager>.Instance.PlayerGameover ();
        }
    }
}

玩家摆脱巡逻兵

public void SSActionEvent (SSAction source, int intParam = 0, GameObject objectParam = null) {
    if (intParam == 0) {
        //巡逻兵跟随玩家
        PatrolFollowAction follow = PatrolFollowAction.GetSSAction (objectParam.gameObject.GetComponent<PatrolData> ().player);
        this.RunAction (objectParam, follow, this);
    } else {
        //巡逻兵按照初始位置开始继续巡逻
        GoPatrolAction move = GoPatrolAction.GetSSAction (objectParam.gameObject.GetComponent<PatrolData> ().start_position);
        this.RunAction (objectParam, move, this);
        //玩家逃脱
        Singleton<GameEventManager>.Instance.PlayerEscape ();
    }
}

巡逻兵需要判定玩家区域和自身区域是否相同,否则会出线巡逻兵离开自身区域来追玩家的情况。

实现了AreaCollide类,当玩家进入区域时会修改对应参数。

public class AreaCollide : MonoBehaviour {
    public int sign = 0;
    FirstSceneController sceneController;
    private void Start () {
        sceneController = SSDirector.GetInstance ().CurrentScenceController as FirstSceneController;
    }
    void OnTriggerEnter (Collider collider) {
        //标记玩家进入自己的区域
        if (collider.gameObject.tag == "Player") {
            sceneController.wall_sign = sign;
        }
    }
}

这样就可以实现区域一致的判断了。

实验结果:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值