与游戏世界交互——Hit UFO

Hit UFO

一、游戏规则

  1. 点击飞碟来摧毁它
  2. 当飞碟飞出屏幕时,损失生命值,生命值归零时游戏结束
  3. 摧毁飞碟会获得点数,随着点数的升高,游戏难度加大
  4. 可以通过Easy,Normal,Hard选项手动调节基础游戏难度(也就是倍速)
  5. 通过Pause暂停游戏;
  6. 通过Start,Restart按钮开始和重置游戏

二、gitee

gitee

三、程序实现

本次作业中,还是采用了前几次作业的UML架构,所以顶层文件结构基本没有变化;

3.1 SSDirector.cs

和前几次的基本相同,作为导演类来监视游戏的运行,由于只有一个界面,所以其实连页面转换的作用也莫得,代码和之前一样;

public class SSDirector : System.Object
{
    private static SSDirector _instance;                  
    public ISceneController CurrentScenceController { get; set; }
    public static SSDirector GetInstance()
    {
        if (_instance == null)
        {
            _instance = new SSDirector();
        }
        return _instance;
    }
}

3.2 SSAction.cs

动作基类

public class SSAction : ScriptableObject            
{
    public bool enable = true;                      
    public bool destroy = false;                    
    public GameObject gameobject;                   
    public Transform transform;                     
    public ISSActionCallback callback;              
                     
    public virtual void Start()
    {
        throw new System.NotImplementedException();
    }
    public virtual void Update()
    {
        throw new System.NotImplementedException();
    }
}

3.3 SSActionManager.cs

动作管理类:实现动作的加入,调用,销毁

public class SSActionManager : MonoBehaviour, ISSActionCallback
{
    private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();    
    private List<SSAction> waitingAdd = new List<SSAction>();                      
    private List<int> waitingDelete = new List<int>();                                             

    protected void Update()
    {
        foreach (SSAction ac in waitingAdd)
        {
            actions[ac.GetInstanceID()] = ac;                                    
        }
        waitingAdd.Clear();

        foreach (KeyValuePair<int, SSAction> kv in actions)
        {
            SSAction ac = kv.Value;
            if (ac.destroy)         
            {
                waitingDelete.Add(ac.GetInstanceID());
            }
            else if (ac.enable)
            {
                ac.Update();
            }
        }

        foreach (int key in waitingDelete)
        {
            SSAction ac = actions[key];
            actions.Remove(key);
            Destroy(ac);
        }
        waitingDelete.Clear();
    }

    public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback manager)
    {
        action.gameobject = gameobject;
        action.transform = gameobject.transform;
        action.callback = manager;
        waitingAdd.Add(action);
        action.Start();
    }

    public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
        int intParam = 0, string strParam = null, Object objectParam = null)
    {
    }
}

3.4 SequenceAction.cs

实现动作的排队执行(见上一次作业的动作分离)

public class SequenceAction : SSAction, ISSActionCallback
{
    
    public List<SSAction> sequence;    
    public int repeat = -1;            
    public int start = 0;              
    public override void Update()
    {
        if (sequence.Count == 0) return;
        if (start < sequence.Count && Time.timeScale != 0)
        {
            sequence[start].Update();    
        }
    }

    public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
        int intParam = 0, string strParam = null, Object objectParam = null)
    {
        source.destroy = false;   
        this.start++;
        if (this.start >= sequence.Count)
        {
            this.start = 0;
            if (repeat > 0) repeat--;
            if (repeat == 0)
            {
                this.destroy = true;
                this.callback.SSActionEvent(this); 
            }
        }
    }

    public override void Start()
    {
        foreach (SSAction action in sequence)
        {
            action.gameobject = this.gameobject;
            action.transform = this.transform;
            action.callback = this;            
            action.Start();
        }
    }

}

3.5 Interface.cs

程序接口,包括场景接口,用户行为接口,动作管理接口

public interface ISceneController
{
    void LoadResources();                                  
}

public interface IUserAction                              
{
    void Hit(Vector3 pos);
    int GetScore();
    void GameOver();
    void ReStart();
    void BeginGame();
}
public enum SSActionEventType : int { Started, Competeted }
public interface ISSActionCallback
{
    void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
        int intParam = 0, string strParam = null, Object objectParam = null);
}

3.6 Singleton.cs

实现工厂的场景单实例,使用课件上的代码实现

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    protected static T instance;
    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                instance = (T)FindObjectOfType(typeof(T));
                if (instance == null)
                {
                    Debug.LogError("An instance of " + typeof(T)
                        + " is needed in the scene, but there is none.");
                }
            }
            return instance;
        }
    }
}

3.7 UFOFlyAction.cs

实现飞碟的飞行

  • 实现飞碟的类抛物线运行方式;
  • 调用update来实现飞碟的实际运动
    public override void Update()
    {
        Debug.Log("UPdate, time.scale = "+ Time.timeScale);
        if (Time.timeScale == 0) return;
        
        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.y < -10)
        {
            this.destroy = true;
            this.callback.SSActionEvent(this);
        }
    }

3.8 DiskFactory.cs

工厂类:用于在需要飞碟时,实例化新的飞碟,并在使用完成时,将对应的disk收回

    public GameObject GetDisk(int round)
    {
        int choice = 0;
        int scope1 = 1, scope2 = 4, scope3 = 7;           
        float start_y = -10f;                             
        string tag;
        disk_prefab = null;

        if (round == 1)
        {
            choice = Random.Range(0, scope1);
        }
        else if(round == 2)
        {
            choice = Random.Range(0, scope2);
        }
        else
        {
            choice = Random.Range(0, scope3);
        }
        if(choice <= scope1)
        {
            tag = "disk1";
        }
        else if(choice <= scope2 && choice > scope1)
        {
            tag = "disk2";
        }
        else
        {
            tag = "disk3";
        }
        for(int i=0;i<free.Count;i++)
        {
            if(free[i].tag == tag)
            {
                disk_prefab = free[i].gameObject;
                free.Remove(free[i]);
                break;
            }
        }
        if(disk_prefab == null)
        {
            if (tag == "disk1")
            {
                disk_prefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk1"), new Vector3(0, start_y, 0), Quaternion.identity);
                disk_prefab.GetComponent<Renderer>().material.color = Color.green;
            }
            else if (tag == "disk2")
            {
                disk_prefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk2"), new Vector3(0, start_y, 0), Quaternion.identity);
                disk_prefab.GetComponent<Renderer>().material.color = Color.blue;
            }
            else
            {
                disk_prefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk3"), new Vector3(0, start_y, 0), Quaternion.identity);
                disk_prefab.GetComponent<Renderer>().material.color = Color.red;
            }
            float ran_x = Random.Range(-1f, 1f) < 0 ? -1 : 1;
            disk_prefab.GetComponent<DiskData>().direction = new Vector3(ran_x, start_y, 0);
            disk_prefab.transform.localScale = disk_prefab.GetComponent<DiskData>().scale;
        }
        used.Add(disk_prefab.GetComponent<DiskData>());
        return disk_prefab;
    }

    public void FreeDisk(GameObject disk)
    {
        for(int i = 0;i < used.Count; i++)
        {
            if (disk.GetInstanceID() == used[i].gameObject.GetInstanceID())
            {
                used[i].gameObject.SetActive(false);
                free.Add(used[i]);
                used.Remove(used[i]);
                break;
            }
        }
    }

3.9 FirstController.cs

主控制器,挂载在GameObject上进行初始化,进行资源的加载以及游戏用户行动函数的实现;

  • 首先还是和之前的相似,在start加载初始元素
   void Start ()
    {
        SSDirector director = SSDirector.GetInstance();     
        director.CurrentScenceController = this;             
        disk_factory = Singleton<DiskFactory>.Instance;
        score_recorder = Singleton<ScoreRecorder>.Instance;
        action_manager = gameObject.AddComponent<FlyActionManager>() as FlyActionManager;
        user_gui = gameObject.AddComponent<UserGUI>() as UserGUI;
    }
  • 之后再update中根据是否点击了开始,来选择加载开始界面或者是游戏界面
	void Update ()
    {
        if(game_start)
        {
            
            if (game_over)
            {
                CancelInvoke("LoadResources");
            }
            
            if (!playing_game)
            {
                InvokeRepeating("LoadResources", 1f, speed);
                playing_game = true;
            }
            
            SendDisk();
            
            if (score_recorder.score >= score_round2 && round == 1)
            {
                round = 2;
                
                speed = speed - 0.6f;
                CancelInvoke("LoadResources");
                playing_game = false;
            }
            else if (score_recorder.score >= score_round3 && round == 2)
            {
                round = 3;
                speed = speed - 0.5f;
                CancelInvoke("LoadResources");
                playing_game = false;
            }
        }
    }
  • sendDisk函数用于随机发射飞碟:
private void SendDisk()
    {
        float position_x = 16;                       
        if (disk_queue.Count != 0)
        {
            GameObject disk = disk_queue.Dequeue();
            disk_notshot.Add(disk);
            disk.SetActive(true);
            
            float ran_y = Random.Range(1f, 4f);
            float ran_x = Random.Range(-1f, 1f) < 0 ? -1 : 1;
            disk.GetComponent<DiskData>().direction = new Vector3(ran_x, 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(10f, 15f);
            float angle = Random.Range(15f, 28f);
            action_manager.UFOFly(disk,angle,power);
        }

        for (int i = 0; i < disk_notshot.Count; i++)
        {
            GameObject temp = disk_notshot[i];
            
            if (temp.transform.position.y < -10 && temp.gameObject.activeSelf == true)
            {
                disk_factory.FreeDisk(disk_notshot[i]);
                disk_notshot.Remove(disk_notshot[i]);
                
                user_gui.ReduceBlood();
            }
        }
    }
  • Hit使用Ray射线来进行击中飞碟的判断
 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 < disk_notshot.Count; j++)
                {
                    if (hit.collider.gameObject.GetInstanceID() == disk_notshot[j].gameObject.GetInstanceID())
                    {
                        not_hit = true;
                    }
                }
                if(!not_hit)
                {
                    return;
                }
                disk_notshot.Remove(hit.collider.gameObject);
                
                score_recorder.Record(hit.collider.gameObject);
                
                StartCoroutine(WaitingParticle(0.08f, hit, disk_factory, hit.collider.gameObject));
                break;
            }
        }
    }

3.10 ScoreRecorder

用于记录得分情况

public class ScoreRecorder : MonoBehaviour
{
    public int score;                   //分数
    void Start ()
    {
        score = 0;
    }
    public void Record(GameObject disk)
    {
        int temp = disk.GetComponent<DiskData>().score;
        score = temp + score;
    }
}

3.11 UserGUI

实现提示语和积分的记录,以及暂停和难度选择等按钮

	void OnGUI ()
    {
        bold_style.normal.textColor = new Color(1, 0, 0);
        bold_style.fontSize = 16;
        text_style.normal.textColor = new Color(0,0,0, 1);
        text_style.fontSize = 16;
        score_style.normal.textColor = new Color(1,0,1,1);
        score_style.fontSize = 16;
        over_style.normal.textColor = new Color(1, 0, 0);
        over_style.fontSize = 25;

        if (game_start)
        {
            Debug.Log("Time scale:" + Time.timeScale);
            //用户射击
            if (Input.GetButtonDown("Fire1"))
            {
                Vector3 pos = Input.mousePosition;
                action.Hit(pos);
            }

            GUI.Label(new Rect(10, 5, 200, 50), "分数:" + action.GetScore().ToString(), text_style);
            if (action.GetScore() < 10)
            {
                GUI.Label(new Rect(10, 55, 200, 50), "Round: 1" , text_style);
            }else if (action.GetScore() >=  10 && action.GetScore() <20)
            {
                GUI.Label(new Rect(10, 55, 200, 50), "Round: 2", text_style);
            }
            else
            {
                GUI.Label(new Rect(10, 55, 200, 50), "Round: 3", text_style);
            }

            if (GUI.Button(new Rect(Screen.width / 2 - 100, 20, 50, 50), "Pause"))
            {
                Time.timeScale = 0;
            }
            if (GUI.Button(new Rect(Screen.width / 2 - 30 , 20, 50, 50), "Easy"))
            {
                Time.timeScale = 0.5f;
            }
            if (GUI.Button(new Rect(Screen.width / 2 + 40, 20, 50, 50), "Normal"))
            {
                Time.timeScale = 1;
            }
            if (GUI.Button(new Rect(Screen.width / 2 + 100, 20, 50, 50), "Hard"))
            {
                Time.timeScale = 2;
            }
            GUI.Label(new Rect(Screen.width - 120, 5, 50, 50), "生命:" + life, text_style);
            //游戏结束
            if (life == 0)
            {
                high_score = high_score > action.GetScore() ? high_score : action.GetScore();
                GUI.Label(new Rect(Screen.width / 2 - 20, Screen.width / 2 - 250, 100, 100), "Game Over", over_style);
                GUI.Label(new Rect(Screen.width / 2 - 10, Screen.width / 2 - 200, 50, 50), "Rank:"+ action.GetScore(), text_style);
                //GUI.Label(new Rect(Screen.width / 2 + 50, Screen.width / 2 - 200, 50, 50), high_score.ToString(), text_style);
                if (GUI.Button(new Rect(Screen.width / 2 - 20, Screen.width / 2 - 150, 100, 50), "Restart"))
                {
                    life = 6;
                    action.ReStart();
                    return;
                }
                action.GameOver();
            }
        }
        else
        {
            GUI.Label(new Rect(Screen.width / 2 - 30, Screen.width / 2 - 350, 100, 100), "Hit UFO!", over_style);
            GUI.Label(new Rect(Screen.width / 2 -10, Screen.width / 2 - 300, 400, 100), "Rule:", text_style);
            GUI.Label(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 280, 400, 100), "Use mouse hit the UFO", text_style);
            GUI.Label(new Rect(Screen.width / 2 - 60, Screen.width / 2 - 260, 400, 100), "Button Pause to stop Game", text_style);
            GUI.Label(new Rect(Screen.width / 2 - 160, Screen.width / 2 - 240, 400, 100), "Button Easy to change into easy game, UFO speed = 0.5x", text_style);
            GUI.Label(new Rect(Screen.width / 2 - 165, Screen.width / 2 - 220, 400, 100), "Button Normal to change into Normal game, UFO speed = 1x", text_style);
            GUI.Label(new Rect(Screen.width / 2 - 165, Screen.width / 2 - 200, 400, 100), "Button Normal to change into Hard game, UFO speed = 2x", text_style);
            if (GUI.Button(new Rect(Screen.width / 2 - 20, Screen.width / 2-150, 100, 50), "Start"))
            {
                game_start = true;
                action.BeginGame();
            }
        }
    }

四、效果展示

在这里插入图片描述
在这里插入图片描述

五、编写一个简单的自定义 Component

  • 实现自定义组件,编辑并赋予飞碟一些属性

我们尝试自制几个飞碟的预设,并在游戏中工厂类”生产“飞碟时,加入我们自己编写的Component预设,我们再组件中,设定了飞碟的颜色,大小,位置和分值,可以通过组件直接调整飞碟对应属性

首先先自己做飞碟的预设:这里因为大小等信息我们通过Component来进行调整,所以我们的预设做的比较模糊就可以了,不需要调整具体形状

在这里插入图片描述
再预设中加入我们自己的Component,来及进行对应属性的直接管理
在这里插入图片描述

具体代码如下:

public class DiskData : MonoBehaviour
{
    public int score;                               
    public Color color = Color.blue;                   
    public Vector3 direction;                           
    public Vector3 scale;   
}

运行时截图:

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值