作业6-物理系统与碰撞

1、改进飞碟(Hit UFO)游戏:

  • 游戏内容要求:
    • adapter模式设计图修改飞碟游戏
    • 使它同时支持物理运动与运动学(变换)运动

游戏规则的变化:

游戏开始前增加了一个模式选择,可以选择运动学模式和物理学模式。选择后游戏开始,和改进前规则一样。图片

原游戏规则:

  1. 一共有三个回合。第一个回合获得5分可以进入第二个回合,第二个回合获得15分可以进入第三个回合,第三回合获得30分就获胜。每个回合的飞碟速度依次增加,飞碟发射的间隔时间依次减少。每回合生命值会恢复初始值。
  2. 击中飞碟加上相应分数,错过飞碟扣除生命值,当生命值降为0时,游戏失败。每个回合有10次trial,如果10次trial结束,没有进入下一回合,游戏失败。游戏失败会跳出restart按键,游戏界面右上角也有restart按键,点击可以重新开始游戏。
  3. 游戏界面右上角有pause按键,点击可以暂停发射飞碟,并且页面中间出现continue按键,点击continue按键可以继续游戏。

代码:

https://github.com/loudax/3d-game/tree/master/hw6-FlyingSaucer(Adapter)

项目设置:

  1. 创建三个飞碟预设,挂载上DiskData,设置score、scale、color。这就是作业5中第二道选做题实现的自定义组件。
  2. 新建一个空对象,挂载上Controllor和DiskFactory。

代码说明:

大部分代码没有改变,只对改进的部分进行说明。

  1. Actions.cs

新增ActionManagerAdapter类,作用是根据flag变量的值,选择调用不同的类的实例来实现飞碟的运动。如果flag为真,说明是物理学,那么调用PhysicsFlyActionManager类的UFOfly函数,如果flag为假,说明是运动学,那么调用FlyActionManager类的UFOfly函数。

public class ActionManagerAdapter : MonoBehaviour, IActionManager
{
    public FlyActionManager action_manager;
    public PhysicsFlyActionManager phy_action_manager;

    public void UFOFly(GameObject disk, float angle, float power, bool go, bool flag) { //flag == true 物理学, flag == false 运动学
        if(flag) phy_action_manager.UFOfly(disk, angle, power, go); //如果是物理学,调用PhysicsFlyActionManager类的UFOfly
        else action_manager.UFOfly(disk, angle, power, go); //如果是运动学,调用FlyActionManager类的UFOfly
    }
    
    void Start () 
    {
        action_manager = gameObject.AddComponent<FlyActionManager>() as FlyActionManager;
        phy_action_manager = gameObject.AddComponent<PhysicsFlyActionManager>() as PhysicsFlyActionManager;
    }
}

FlyActionManager类和FlyAction类,用于管理和实现运动学方式下飞碟的运动。其中FlyAction类和原项目一致,FlyActionManager类稍有改动,去掉了Controllor类实例。

public class FlyActionManager : SSActionManager
{
    public FlyAction fly;

    protected void Start()
    {
        
    }

    public void UFOfly(GameObject disk, float angle, float power, bool go)
    {
        fly = FlyAction.GetSSAction (disk.GetComponent<DiskData> ().direction, angle, power, go);
        this.RunAction (disk, fly, this);
    }
}

public class FlyAction : SSAction
{
    public float gravity = -0.1f; //飞碟飞行时受重力
    public bool go = true; //用于表示是否暂停,go为false时表示游戏暂停
    private float time;
    private Vector3 start_vector;                              
    private Vector3 gravity_vector = Vector3.zero;
    private Vector3 current_angle = Vector3.zero; 

    private FlyAction()
    {

    }

    public static FlyAction GetSSAction(Vector3 direction, float angle, float power, bool go_)
    {
        FlyAction action = CreateInstance<FlyAction>();
        action.go = go_;
        action.start_vector = Quaternion.Euler(new Vector3(0, 0, angle)) * Vector3.right * power;
        return action;
    }

    public override void Update()
    {
        if(go){
            time += Time.fixedDeltaTime;
            gravity_vector.y = gravity * time;
            transform.position += (start_vector + gravity_vector) * Time.fixedDeltaTime;
            current_angle.z = Mathf.Atan((start_vector.y + gravity_vector.y) / start_vector.x) * Mathf.Rad2Deg;
            transform.eulerAngles = current_angle;    
        }

        if (this.transform.position.x > 20){ //当x坐标大于20时,回收飞碟
            this.destroy = true;
            this.callback.SSActionEvent(this);      
        }
    }

    public override void Start()
    {
        
    }
}

PhysicsFlyActionManager类和PhysicsFlyAction类,用于管理和实现物理学方式下飞碟的运动,方法是给刚体加上向右方的速度。

public class PhysicsFlyActionManager : SSActionManager
{
    public PhysicsFlyAction fly;  

    protected void Start()
    {

    }

    public void UFOfly(GameObject disk, float angle, float power, bool go)
    {
        fly = PhysicsFlyAction.GetSSAction(disk.GetComponent<DiskData>().direction, angle, power, go);
        this.RunAction(disk, fly, this);
    }
}

public class PhysicsFlyAction : SSAction
{
    private Vector3 start_vector;
    public float power;
    public bool go = true; //用于表示是否暂停,go为false时表示游戏暂停
    private PhysicsFlyAction() { }

    public static PhysicsFlyAction GetSSAction(Vector3 direction, float angle, float power, bool go_) 
    {
        PhysicsFlyAction action = CreateInstance<PhysicsFlyAction>();
        action.start_vector = Quaternion.Euler(new Vector3(0, 0, angle)) * Vector3.right * power;
        action.power = power;
        return action;
    }

    public override void Update() 
    {
        if(go == false){
            gameobject.GetComponent<Rigidbody>().velocity = new Vector3(0, 0, 0);
            gameobject.GetComponent<Rigidbody>().useGravity = false;
        }
        else{
            gameobject.GetComponent<Rigidbody>().velocity = power * 2 * start_vector;
            gameobject.GetComponent<Rigidbody>().useGravity = true;
        }
        if (this.transform.position.x > 20) {
            this.destroy = true;
            this.callback.SSActionEvent(this);
        }
    }

    public override void Start() 
    {
        gameobject.GetComponent<Rigidbody>().velocity = power * 2 * start_vector;
        gameobject.GetComponent<Rigidbody>().useGravity = true;
    }
}
  1. Controllor.cs

加入了两个变量mode和modeSetting。mode指的是运动模式,如果是物理学模式,mode为真;如果是运动学模式,mode为假。modeSetting指是否在模式选择界面,如果还在模式选择,modeSetting为真,游戏还不能开始;如果已选择,modeSetting为假,且可以开始发射飞碟。

加入了与以上两个变量相关的get、set函数。

public class Controllor : MonoBehaviour,ISceneControllor,IUserAction
{
    public IActionManager ima;
    public DiskFactory df;
    public UserGUI ug;
    public ScoreRecorder sr;
    public RoundControllor rc;

    private Queue<GameObject> dq = new Queue<GameObject> ();
    private List<GameObject> dfree = new List<GameObject> ();

    private int round = 1;
    private int trail = 0;
    private float t = 1;
    private float speed = 2; //飞碟发射间隔时间
    private bool mode = false; //mode == true 物理学, mode == false 运动学
    private bool modeSetting = true;

    void Start(){
        SSDirector director = SSDirector.GetInstance();     
        director.CurrentSceneControllor = this;             
        df = Singleton<DiskFactory>.Instance;
        sr = gameObject.AddComponent<ScoreRecorder> () as ScoreRecorder;
        ima = gameObject.AddComponent<ActionManagerAdapter>() as IActionManager;
        ug = gameObject.AddComponent<UserGUI>() as UserGUI;
        rc = gameObject.AddComponent<RoundControllor> () as RoundControllor;
        t = speed;
    }

    void Update ()
    {
        if(ug.go){
            t-=Time.deltaTime;

            if(trail > 10){ //超过10次trial后,判为游戏失败
                ug.life = 0;
            }

            if (t < 0) { //发射一个飞碟
                LoadResources ();
                SendDisk ();
                t = speed;
                trail++;
            }
            
            if ((sr.score >= 5 && round == 1) || (sr.score >= 15 && round == 2) || (sr.score >= 30 && round == 3)) { //判断是否进入下一轮
                round++;
                trail = 0;
                rc.loadRoundData (round);
                ug.RecoverBlood();
            }
        }
    }

    public void setting(float speed_)
    {
        speed = speed_; 
    }

    public void LoadResources()
    {
        dq.Enqueue(df.GetDisk(round)); 
    }

    private void SendDisk() //发射飞碟
    {
        float position_x = 16;                       
        if (dq.Count != 0)
        {
            GameObject disk = dq.Dequeue();
            dfree.Add(disk);
            disk.SetActive(true);
            float ran_y = Random.Range(1f, 4f);
            disk.GetComponent<DiskData>().direction = new Vector3(1f, ran_y, 0);
            Vector3 position = new Vector3(-disk.GetComponent<DiskData>().direction.x * position_x, ran_y, 0);
            disk.transform.position = position;
            float power = Random.Range(1f * round, 2f * round);
            float angle = Random.Range(0f, 5f);
            ima.UFOFly(disk, angle, power, ug.go, mode);
        }

        for (int i = 0; i < dfree.Count; i++)
        {
            GameObject temp = dfree[i];
            if (temp.transform.position.x > 20 && temp.gameObject.activeSelf == true)
            {
                df.FreeDisk(dfree[i]);
                dfree.Remove(dfree[i]);
                ug.ReduceBlood();
            }
        }
    }

    public void Hit (Vector3 pos){ //集中飞碟
        Ray ray = Camera.main.ScreenPointToRay(pos);
        RaycastHit[] hits;
        hits = Physics.RaycastAll(ray);
        bool not_hit = false;
        for (int i = 0; i < hits.Length; i++)
        {
            RaycastHit hit = hits[i];
            if (hit.collider.gameObject.GetComponent<DiskData>() != null)
            {
                for (int j = 0; j < dfree.Count; j++)
                {
                    if (hit.collider.gameObject.GetInstanceID() == dfree[j].gameObject.GetInstanceID())
                    {
                        not_hit = true;
                    }
                }
                if(!not_hit)
                {
                    return;
                }
                dfree.Remove(hit.collider.gameObject);
                sr.Record(hit.collider.gameObject);
                hit.collider.gameObject.transform.position = new Vector3(0, -100, 0);
                df.FreeDisk(hit.collider.gameObject);
                break;
            }
        }
    }

    public void Restart (){ //重启游戏
        SceneManager.LoadScene(0);
    }

    public int GetScore (){ //得到得分
        return sr.score;
    }

    public int GetRound(){ //得到轮次
        return round;
    }

    public int GetTrail(){
        return trail;
    }

    public bool GetMode(){
        return mode;
    }

    public void SetMode(bool flag){
        mode = flag;
    }

    public bool GetModeSetting(){
        return modeSetting;
    }

    public void StartGame(){
        modeSetting = false;
        ug.go = true;
    }

}
  1. UserGUI.cs

OnGUI函数加了一个if else判断。如果action.GetModeSetting()为真,说明处在模式选择阶段,显示模式选择的两个按键,选择后相应的调用函数设置mode的值并开始游戏。如果action.GetModeSetting()为假,说明可以开始游戏,此处代码和原项目的代码是相同的,不再解释了。

public class UserGUI : MonoBehaviour
{
    private IUserAction action;
    public int life = 6;   
    public bool go = false;

    void Start ()
    {
        action = SSDirector.GetInstance().CurrentSceneControllor as IUserAction;
    }

    void OnGUI ()
    {   
        if(action.GetModeSetting()){
            GUI.Label(new Rect(Screen.width / 2 - 30, Screen.width / 2 - 320, 100, 50), "模式选择");
            if (GUI.Button(new Rect(Screen.width / 2 - 55, Screen.width / 2 - 200, 100, 50), "物理学模式"))
            {
                action.SetMode(true);
                action.StartGame();
                return;
            }
            if (GUI.Button(new Rect(Screen.width / 2 - 55, Screen.width / 2 - 270, 100, 50), "运动学模式"))
            {
                action.SetMode(false);
                action.StartGame();
                return;
            }
        }
        else{
            if (Input.GetButtonDown("Fire1")){
                Vector3 pos = Input.mousePosition;
                action.Hit(pos);
            }

            GUI.Label(new Rect(20, 10, 200, 50), "score:");
            GUI.Label(new Rect(60, 10, 200, 50), action.GetScore().ToString());
            GUI.Label(new Rect(20, 30, 50, 50), "hp:");
            for (int i = 0; i < life; i++){
                GUI.Label(new Rect(40 + 10 * i, 30, 50, 50), "X");
            }
            GUI.Label(new Rect(20, 50, 50, 50), "trail:");
            GUI.Label(new Rect(60, 50, 50, 50), action.GetTrail().ToString());

            if(action.GetRound() == 1){
                GUI.Label(new Rect(200, 10, 200, 50), "ROUND");
                GUI.Label(new Rect(250, 10, 200, 50), action.GetRound().ToString());
                GUI.Label(new Rect(200, 30, 300, 50), "分数到达5分进入下一关,打中一个飞碟得1分,错过一个飞碟生命值减1,生命值归零时游戏失败。");
            }
            else if(action.GetRound() == 2){
                GUI.Label(new Rect(200, 10, 200, 50), "ROUND");
                GUI.Label(new Rect(250, 10, 200, 50), action.GetRound().ToString());
                GUI.Label(new Rect(200, 30, 300, 50), "分数到达15分进入下一关,打中一个飞碟得2分,错过一个飞碟生命值减1,生命值归零时游戏失败。");
            }
            else if(action.GetRound() == 3){
                GUI.Label(new Rect(200, 10, 200, 50), "ROUND");
                GUI.Label(new Rect(250, 10, 200, 50), action.GetRound().ToString());
                GUI.Label(new Rect(200, 30, 300, 50), "分数到达30分获得胜利,打中一个飞碟得3分,错过一个飞碟生命值减1,生命值归零时游戏失败。");
            }
            else{
                GUI.Label(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 300, 100, 100), "You Win!");
            }

            if(GUI.Button(new Rect(550, 10, 100, 40), "Pause")){
                go = false;
            }
            if(go == false){
                if(GUI.Button(new Rect(Screen.width / 2 - 60, Screen.width / 2 - 250, 100, 40), "Continue")){
                    go = true;
                }
            }

            if (GUI.Button(new Rect(670, 10, 100, 40), "Restart")){
                action.Restart();
                return;
            }

            if (life == 0){
                GUI.Label(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 300, 100, 100), "Game Over!");
                if (GUI.Button(new Rect(Screen.width / 2 - 60, Screen.width / 2 - 250, 100, 40), "Restart")){
                    action.Restart();
                    return;
                }
            }               
        }
    }
    
    public void ReduceBlood() //生命值减1
    {
        if(life > 0)
            life--;
    }

    public void RecoverBlood() //生命值恢复初始状态
    {
        life = 6;
    }
}
  1. interface.cs

加上了Controllor中新增的函数,修改了Action中的函数的参数。

public interface IUserAction
{
    void Hit(Vector3 pos);
    void Restart();
    int GetScore();
    void setting(float speed);
    int GetRound();
    int GetTrail();
    bool GetMode();
    void SetMode(bool flag);
    bool GetModeSetting();
    void StartGame();
}

public interface IActionManager
{
    void UFOFly(GameObject disk, float angle, float power, bool go, bool flag); // flag == true 物理学, flag == false 运动学 
}

游戏效果:

和修改前基本一致,运动学部分是相同的,不再展示,以下动图展示物理学模式的效果。

图片

2、打靶游戏(可选作业):

  • 游戏内容要求:
    • 靶对象为 5 环,按环计分;
    • 箭对象,射中后要插在靶上
      • 增强要求:射中后,箭对象产生颤抖效果,到下一次射击 或 1秒以后
    • 游戏仅一轮,无限 trials;
      • 增强要求:添加一个风向和强度标志,提高难度

游戏规则:

点击空格,产生一根箭,点击鼠标左键可以随鼠标方向发射箭,风向和强度标志在屏幕右上角,会影响箭的飞行。箭射到箭靶上有颤抖效果,根据射到箭靶上的位置计分,从外环到内环分别是1到5分,分数显示在左上角。左下角有一个第二摄像机,近距离观看箭靶,可以通过这个部分看到箭射的位置。

代码:

https://github.com/loudax/3d-game/tree/master/hw6-Archery

项目设置:

  1. 新建arrow预设,由长圆柱pole和球tip组成。

图片

图片

pole加上Capsule Collider,和棕色Material。

图片

tip加上Sphere Collider、灰色Material和Arrow Collider。

图片

  1. 新建预设target。由5个圆柱叠起来,为它们加上不同的颜色Material进行环和环之间的区分,加上Capsule Collider和Mesh Collider,且选择Convex和IsTrigger

图片

  1. 新建Camera,设置Viewport Rect。

图片

  1. 新建一个空对象,放入Controller和ArrowFactory。

图片

代码说明:

  1. SSDirector.cs

和前面的打飞碟项目相同,不再说明了。

  1. Singletion.cs

和前面的打飞碟项目相同,不再说明了。

  1. Actions.cs

用于控制箭的飞行和颤抖。飞行时,给箭加上一个受风影响的力,当击中或是z>20时回收箭,在ArrowFlyActionManager类和ArrowFlyAction类实现。颤抖时间是0.8s,在ArrowTremble类中实现。

public class ArrowFlyActionManager : SSActionManager
{
    private ArrowFlyAction fly; 
    public Controllor scene_controller; 

    protected void Start()
    {
        scene_controller = (Controllor)SSDirector.GetInstance().CurrentSceneControllor;
        scene_controller.action_manager = this;
    }

    public void ArrowFly(GameObject arrow,Vector3 wind,Vector3 f)
    {
        fly = ArrowFlyAction.GetSSAction(wind, f);
        this.RunAction(arrow, fly, this);
    }
}
public class ArrowFlyAction : SSAction
{
    public Vector3 force;
    public Vector3 wind;

    private ArrowFlyAction() { }

    public static ArrowFlyAction GetSSAction(Vector3 wind,Vector3 force_)
    {
        ArrowFlyAction action = CreateInstance<ArrowFlyAction>();
        action.force = force_;
        action.wind = wind;
        return action;
    }

    public override void Update(){}

    public override void FixedUpdate()
    {
        this.gameobject.GetComponent<Rigidbody>().AddForce(wind, ForceMode.Force);
        if (this.transform.position.z > 25 || this.gameobject.tag == "hit"){
            this.destroy = true;
            this.callback.SSActionEvent(this,this.gameobject);
        }
    }

    public override void Start()
    {
        gameobject.transform.LookAt(force);
        gameobject.GetComponent<Rigidbody>().isKinematic = false;
        gameobject.GetComponent<Rigidbody>().AddForce(force, ForceMode.Impulse);
        gameobject.GetComponent<Rigidbody>().velocity = Vector3.zero;
        gameobject.GetComponent<Rigidbody>().AddForce(force, ForceMode.Impulse);
    }
}

public class ArrowTremble : SSAction
{
    public float radian = 0;
    public float per_radian = 3f;
    public float radius = 0.01f;
    public float left_time = 0.8f;
    Vector3 old_pos;
    
    private ArrowTremble() 
    { 

    }

    public override void Start()
    {
        old_pos = transform.position;
    }

    public static ArrowTremble GetSSAction()
    {
        ArrowTremble action = CreateInstance<ArrowTremble>();
        return action;
    }

    public override void Update()
    {
        left_time -= Time.deltaTime;
        if (left_time <= 0){
            transform.position = old_pos;
            this.destroy = true;
            this.callback.SSActionEvent(this);
        }
        radian += per_radian;
        float dy = Mathf.Cos(radian) * radius; 
        transform.position = old_pos + new Vector3(0, dy, 0);
    }
    
    public override void FixedUpdate()
    {

    }
}
  1. ArrowCollider.cs

控制箭射中箭靶后的操作,调用ScoreRecorder计算分数,加上环数对应的分数。

public class ArrowCollider : MonoBehaviour
{
    public Controllor scene_controller;
    public ScoreRecorder recorder;                   

    void Start()
    {
        scene_controller = SSDirector.GetInstance().CurrentSceneControllor as Controllor;
        recorder = Singleton<ScoreRecorder>.Instance;
    }

    void OnTriggerEnter(Collider c)
    { 
        if (c.gameObject.name == "Cylinder1"||c.gameObject.name == "Cylinder2"||c.gameObject.name == "Cylinder3"||c.gameObject.name == "Cylinder4"||c.gameObject.name == "Cylinder5") {
            gameObject.transform.parent.gameObject.GetComponent<Rigidbody>().isKinematic = true;
            gameObject.SetActive(false);
            float point = Mathf.Sqrt (this.gameObject.transform.position.x * this.gameObject.transform.position.x + this.gameObject.transform.position.y * this.gameObject.transform.position.y);
            recorder.Record(5-(int)Mathf.Floor(point*2));
        }

    }
}
  1. ArrowFactory.cs

用于管理arrow。调用GetArrow得到箭,如果unusedArrowList中有箭,就可以直接用,如果没有就需要使用预制创造新的箭。调用FreeArrow释放箭,将usingArrowList中的箭转移到unusedArrowList中。

public class ArrowFactory : MonoBehaviour {
    public GameObject arrow = null;
    private List<GameObject> usingArrowList = new List<GameObject>();
    private Queue<GameObject> unusedArrowList = new Queue<GameObject>();
    public Controllor sceneControler;

    public GameObject GetArrow()
    {
        if (unusedArrowList.Count == 0){ 
            arrow = Instantiate(Resources.Load<GameObject>("Prefabs/arrow"));
        }
        else{
            arrow = unusedArrowList.Dequeue();
            arrow.gameObject.SetActive(true);
        }
        arrow.transform.position = new Vector3 (0, 0, -10);
        usingArrowList.Add(arrow);
        return arrow;
    }
    
    public void FreeArrow()
    {
        for (int i = 0; i < usingArrowList.Count; i++) {
            if (usingArrowList[i].transform.position.y <= -8||usingArrowList[i].transform.position.y >= 8) {
                usingArrowList[i].GetComponent<Rigidbody>().isKinematic = true;
                usingArrowList[i].SetActive(false);
                usingArrowList[i].transform.position =  new Vector3 (0, 0, 0);
                unusedArrowList.Enqueue(usingArrowList[i]);
                usingArrowList.Remove(usingArrowList[i]);
                i--;
            }
        }
    }
}
  1. Controllor.cs

进行一些操作的控制。Update函数用于在游戏开始时释放箭,LoadResources函数用于加载资源,haveArrowOnPort函数用于确认有箭准备发射,create函数用于得到箭(依赖于Arrow.cs的GetArrow函数),MoveBow函数使箭随着鼠标移动,Shoot函数用于控制箭的发射,CreateWind函数用于随机生成风(具体来说是风的向量的x和y值)并生成右上角显示的字符串,还包括一些基础的set变量、get变量的函数。

public class Controllor : MonoBehaviour, IUserAction, ISceneControllor
{ 
    public ScoreRecorder recorder;    
    public ArrowFactory arrow_factory;   
    public ArrowFlyActionManager action_manager;
    public UserGUI user_gui;

    private GameObject arrow;                
    private GameObject target;  
    private bool game_start = false;
    private string wind = "";   
    private float wind_directX;     
    private float wind_directY;  

    void Update()
    {
        if(game_start) arrow_factory.FreeArrow ();
    }

    public void LoadResources()
    {
        target = Instantiate(Resources.Load("Prefabs/target", typeof(GameObject))) as GameObject;
    }

    public bool haveArrowOnPort() 
    {
        return (arrow != null);
    }

    void Start ()
    {
        SSDirector director = SSDirector.GetInstance();
        director.CurrentSceneControllor = this;
        arrow_factory = Singleton<ArrowFactory>.Instance;
        recorder = gameObject.AddComponent<ScoreRecorder>() as ScoreRecorder;
        user_gui = gameObject.AddComponent<UserGUI> () as UserGUI;
        action_manager = gameObject.AddComponent<ArrowFlyActionManager>() as ArrowFlyActionManager;
        LoadResources();
    }

    public void create(){
        if (arrow == null) {
            CreateWind();
            arrow = arrow_factory.GetArrow();
        }
    }

    public void MoveBow(Vector3 mousePos)
    {
        if (!game_start){
            return;
        }
        arrow.transform.LookAt(mousePos * 30);
    }

    public void Shoot(Vector3 mousePos)
    {
        if(game_start){
            Vector3 wind = new Vector3(wind_directX, wind_directY, 0);
            action_manager.ArrowFly(arrow, wind,mousePos*30);
            arrow = null;
        }
    }

    public void CreateWind()
    {
        string Horizontal = "", Vertical = "", level = "";
        wind_directX = Random.Range(-4, 4);
        wind_directY = Random.Range(-4, 4);

        if (wind_directX > 0){
            Horizontal = "西";
        }
        else if (wind_directX <= 0){
            Horizontal = "东";
        }

        if (wind_directY > 0){
            Vertical = "南";
        }
        else if (wind_directY <= 0){
            Vertical = "北";
        }

        if ((wind_directX + wind_directY) / 2 > -1 && (wind_directX + wind_directY) / 2 < 1){
            level = "1 级";
        }
        else if ((wind_directX + wind_directY) / 2 > -2 && (wind_directX + wind_directY) / 2 < 2){
            level = "2 级";
        }
        else if ((wind_directX + wind_directY) / 2 > -3 && (wind_directX + wind_directY) / 2 < 3){
            level = "3 级";
        }
        else if ((wind_directX + wind_directY) / 2 > -5 && (wind_directX + wind_directY) / 2 < 5){
            level = "4 级";
        }

        wind = Horizontal + Vertical + "风" + " " + level;
    }

    public int GetScore()
    {
        return recorder.score;
    }

    public string GetWind()
    {
        return wind;
    }

    public void Restart()
    {
        SceneManager.LoadScene(0);
    }

    public void BeginGame()
    {
        game_start = true;
    }
}
  1. Interface.cs
public interface ISceneControllor
{
    void LoadResources();
}

public interface IUserAction
{
    void MoveBow(Vector3 mousePos);
    void Shoot (Vector3 mousePos);
    int GetScore();
    void Restart();
    string GetWind();
    void BeginGame();
    void create ();
    bool haveArrowOnPort();
}
  1. ScoreRecorder.cs

用于计算分数,比较简单,将point加到score就完成了。

public class ScoreRecorder : MonoBehaviour 
{
    public int score = 0;

    void Start()
    {
        score = 0;
    }
    
    public void Record(int point)
    {
        score = score + point;
    }
}
  1. UserGUI.cs

game_start变量标志游戏是否开始。OnGUI函数中,game_start为false时,显示游戏开始按键,game_start为true时,显示分数、风向和重新开始的按键。Update函数中,按动空格可以得到箭,有箭准备发射时,箭跟随鼠标移动,鼠标按动时发射。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UserGUI : MonoBehaviour {
    private IUserAction action;
    GUIStyle style1 = new GUIStyle();
    GUIStyle style2 = new GUIStyle();
    private bool game_start = false;       

    void Start ()
    {
        action = SSDirector.GetInstance().CurrentSceneControllor as IUserAction;
        style1.fontSize = 15;
        style2.fontSize = 25;
    }

    void Update()
    {
        if(game_start)
        {
            if (action.haveArrowOnPort ()) {
                if (Input.GetMouseButton(0)) {
                    Vector3 mousePos = Camera.main.ScreenPointToRay(Input.mousePosition).direction;
                    action.MoveBow(mousePos);
                }
                if (Input.GetMouseButtonUp(0)) {
                    Vector3 mousePos = Camera.main.ScreenPointToRay(Input.mousePosition).direction;
                    action.Shoot(mousePos);
                }
            }
            if (Input.GetKeyDown(KeyCode.Space)) action.create();
        }
    }

    private void OnGUI()
    {
        if(game_start){
                GUI.Label(new Rect(10, 10, 200, 50), "分数:", style1);
                GUI.Label(new Rect(55, 10, 200, 50), action.GetScore().ToString(), style1);
                GUI.Label(new Rect(Screen.width - 170, 30, 200, 50), "风向:", style1);
                GUI.Label(new Rect(Screen.width - 140, 30, 200, 50), action.GetWind(), style1);
            if (GUI.Button(new Rect(Screen.width - 170, 0, 100, 30), "重新开始")){
                    action.Restart();
                    return;
                }
        }
        else{
            GUI.Label(new Rect(Screen.width / 2 - 80, Screen.width / 2 - 320, 100, 100), "Arrow Shooting", style2);
            if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 200, 100, 50), "游戏开始")){
                game_start = true;
                action.BeginGame();
            }
        }
    }
}

游戏效果:

图片

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值