关于Unity的入门游戏飞机大战的开发(下)

 开发思路:

1: 修改测试模式,去掉开始按钮方便开发,加入敌机的资源
2: 创建敌机 添加刚体,碰撞器组件,添加帧动画播放组件;
3: 创建敌机出现的队形;
4: 根据队形随机 生成我们的敌机,调整敌机的速度,和敌机出去后,删除;
5: 碰撞配置分组,TAG 标记不同对象, 刚体加上trigger;
6: 玩家被敌人击中,爆炸与恢复;
7: 子弹打死敌人后删除自己,敌人也要做爆炸;
8: 加上玩家得分的情况;
9: 打开menu主页, 做好GUI 适配

步骤一>>>>>>修改测试模式,去掉开始按钮方便开发,加入敌机的资源

1.把menu_root节点隐藏起来,在game_scene脚本里面的Start函数里直接调用on_start_click()函数,这样游戏就不用菜单的开始按钮触发,而是自己触发开始了,比较好调试。

2.在Resources文件夹下面的tex文件夹中,添加进敌机的资源(9种)和敌机爆炸的动画资源文件夹dead

步骤二>>>>>>创建敌机 添加刚体,碰撞器组件,添加帧动画播放组件

1.创建一个叫enemy_root的敌机空节点,作为Canvas节点的子节点。节点大小设置为0。

2.给enemy_root创建一个叫e1的Image子节点,把敌机e1的贴图拖进去,set native size,缩放设置为X为2,Y为2。

3.由于有很多敌机(8种)要添加碰撞形状和物理刚体组件,所以可以手动添加组件,也可以用脚本代码实现组件的添加,比较轻松。

4.创建一个叫enemy的脚本用来对每个敌机添加刚体和碰撞组件,以及对他们进行一些初始化,记得是先写好脚本再挂载到e1到e8下面,不然先挂载再写脚本这里会出错。

using UnityEngine;
using System.Collections;
using System;

//用代码添加刚体组件和形状碰撞组件
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(BoxCollider2D))]

public class enemy : MonoBehaviour {
    public int e_type; // 敌机的类型;

    private BoxCollider2D box;
    private Rigidbody2D body;
   
    // Use this for initialization
    void Start () {
        
        this.body = this.GetComponent<Rigidbody2D>();
        this.box = this.GetComponent<BoxCollider2D>();

     //设置敌机碰撞器的形状的大小
        RectTransform r_trans = (RectTransform) this.transform;
        r_trans.localScale = new Vector3(2, 2, 1);//放大节点两倍
        Vector2 size = r_trans.sizeDelta;//取出节点的大小

        this.box.size = size;//设置好碰撞器大小
        this.box.isTrigger = true; // 只做碰撞触发;只触发不产生碰撞效果
        this.body.freezeRotation = true; // 不让他旋转;刚体不旋转
    }
    
    // Update is called once per frame
    void Update () {
     
    }
}

5.记得把Rigidbody2D组件的Is Trigger打钩,只做碰撞触发,其实不打钩也没事,在脚本里面有写。

6.给每个敌机创建一个节点,把自己的贴图拖进自己的贴图属性中,set native size,记得填写enemy脚本的公开属性E_type,第几个类型的就填第几编号。

7.由于等下要生成非常多的敌机,所以要把这些敌机设置成预制体,就在prefabs文件夹下面再创建一个叫enemies的文件夹,然后把e1到e8的敌机节点拖进enemies变成蓝色预制体,删除原来的e1到e8节点。

步骤三>>>>>>创建敌机出现的队形

1.开始排飞机的队形,在enemy_root下面创建一个group1的空子节点,然后把prefabs里面的前三个敌机预制体拖进group1,修改三个敌机的坐标位置,排成一排或者其他的。

2.排好之后先把这三台敌机取消预制体形态,还原成普通节点GameObject-->Break Prefab Instance,然后把整个group1拖进prefab文件夹里面当作一个预制体。

3.每次制作好预制体之后,都可以把原来的那个节点删除,这样重复制作组合大概5、6组就可以。

4.最后只剩一个enemy_root空节点,下面什么也没有。

步骤四>>>>>>根据队形随机 生成我们的敌机,调整敌机的速度,和敌机出去后,删除

我们首先是做了敌机的group,这个group下面可能有三驾敌机e1e2e3,每驾敌机都有相对于group相对偏移的位置pos1pos2pos3,接下来把所有的group都从一个很高很高的位置如(0,912,0)开始,当我们产生敌机的时候,把敌机的初始位置设置为(0,912,0)

保证了敌机可以从那个高度开始移动,接下来我们遍历每个group下面的孩子,有几个孩子就产生几个enemy,每个enemy的位置就是912+pos,这样再把新创建的enemy加到enemy_root空节点下面,这样我们就生成了一个group实例。所以我们在enemy_root下其实是不直接生成group实例,因为group里面的每一个enemy位置都是写死的,我们希望里面的每个敌机的位置是随机的。所以我们新产生一个enemy,位置等于grooup的初始位置912+每个敌机相对group的偏移位置pos。

1.有了队形之后就是怎么随机产生这些队形和移动这些队形,以及最后超出屏幕后删除的操作。

2.写一个脚本gen_enemy挂载在enemy_root节点下面实现第一点

3.打开gen_enemy脚本,其实里面对预制体的实例化并不是对group的实例化,而是对一个一个enemy敌机的实例化。

  而且产生的飞机的位置也是随机的,队形是一个一个随机的,而且具体哪个位置哪台飞机是随机的。并不是我刚开始以为的对整个group进行实例化。如果那样的话位置和飞机就会对应,不灵活。

记得在enemy_root的Inspector面板里面把对应的属性绑定好,自己手动拖进去。

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class gen_enemy : MonoBehaviour {
public GameObject[] group_set; // 敌机组的集合 public GameObject[] enemy_set; // 敌机的集合 // Use this for initialization void Start () { }
public void start_gen_enemy() { this.Invoke("gen_one_group", 3); }
  
void gen_one_group() { // [0, this.group_set.Length) 随机数,生成一个0到敌机组总数-1的随机数 int group_index = Random.Range(0, this.group_set.Length); //设置一个初始位置,敌机组从这里生成并开始移动 Vector3 start_pos = new Vector3(0, 912, 0); // 循环遍历group下面的孩子数目;遍历随机到的那一个敌机组,遍历里面的每一个敌机 for (int i = 0; i < this.group_set[group_index].transform.childCount; i++) {        //生成一个0到敌机总数-1的随机数,随机取1到8敌机类型的一种类型        int e_type = Random.Range(0, this.enemy_set.Length);        //获得孩子的位置,获得当前指定敌机的位置 Transform group_enemy = this.group_set[group_index].transform.GetChild(i); // 随机的生成了一个敌人,开始随机生成一架敌机 GameObject e_item = GameObject.Instantiate(this.enemy_set[e_type]); e_item.transform.SetParent(this.transform, false); Vector3 pos = start_pos + group_enemy.localPosition; e_item.transform.localPosition = pos; } // [0.0, 3.0f] this.Invoke("gen_one_group", 3 + Random.Range(0.0f, 3.0f)); }   // Update is called once per frame void Update () { } }

4.在enemy脚本中,由于每一个敌机我们给它一个向下的初速度,这个速度的设置是写在原来的enemy脚本中的Start函数里面

this.body.velocity = new Vector2(0, -8);

还要写实现敌机飞出屏幕后删除的代码,这个和子弹删除的原理是一样的

void Start () {

  ...

  float scale = 640.0f / (float)Screen.width;
  this.dead_line_y = -(Screen.height * scale * 0.5f + 100);

}

void Update () {
  if (this.transform.localPosition.y < this.dead_line_y) {
  MonoBehaviour.Destroy(this.gameObject);
  }
}

5.在game_scene脚本中,为了开始生成敌机队列,首先要在里面的Start函数中获得enemy_root下的脚本

 this.gen_enmey_ctrl = this.transform.Find("game_root/enemy_root").GetComponent<gen_enemy>();

然后在game_realy_started函数里面调用gen_enmey脚本里面的start_gen_enemy函数开始生成敌机

 this.gen_enmey_ctrl.start_gen_enemy();//记得要在gen_enmey脚本中把start_gen_enemy函数的权限改为public才可以访问

步骤五>>>>>>碰撞配置分组,TAG 标记不同对象, 刚体加上trigger

1.给plane添加碰撞器形状组件,大小设置为飞机的大小128X128,为了准确也可以做一个多边形的Collider,那样就必须使用Polygon Collider 2D组件,然后编辑。

2.把Box Collider 2D的Is Trigger打钩,表示不发生碰撞效果,但是还是有碰撞响应。

3.编辑右上角的层,把游戏内的物体分成3个层,plane,plane_bullet,enemy,给每个节点这里设置plane节点及其子节点和resources/prefabs/enemies里面所以预制体和resources/prefabs/plane_bullet预制体都设置对应的层。

4.对每个层的碰撞情况进行编辑,Edit-->project Settings-->Physics 2D,在Layer Collision Matris碰撞矩阵里面编写打钩碰撞的情况,不打钩的意思是连碰撞响应都不发生。

5.编辑左上角的标记,把游戏内的物体进行标记,这里分成3个标记plane,plane_bullet,enemy,给每个节点和预制体都设置对应的标记,有了标记后面才能知道是谁和谁在碰撞。

步骤六>>>>>>玩家被敌人击中,爆炸与恢复

1.由于plane,plane_bullet,enemy的碰撞类型都是trigger,所以我们要在相应的脚本里面写Trigger的响应函数。

2.我们还需要一个帧动画的组件,在第22的文件里面,里面的frame_anim.cs,把它拷贝到项目的scripts文件夹下面

3.在飞机plane节点下面添加frame_anim.cs组件,因为飞机爆炸有7张图,属性size就写7,并把每一张爆炸图都拖进去。

4.在OnTriggerEnter2D函数里面调用frame_anim.cs的play_once函数,记得把play_once改成public才能调用,而且我们还要改写一下frame_anim.cs的函数play_once,使得它变成一个回调函数,这样爆炸结束的时候还能自动调用别的函数。

改写的frame_anim.cs

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using System;

// 我们当前代码强制要求要加入一个Image组件,
// 如果没有Image组件,那么自动加上,如果有就使用;
// 如果你的代码要求这个节点必须挂某个组件,那么
// 使用RequireComponent
[RequireComponent(typeof(Image))]

public class frame_anim : MonoBehaviour {
    // 我们这个动画所需要的画面;
    public Sprite[] sprite_frames;
    // 帧动画的间隔时间
    public float duration = 0.1f;
    // 是否循环播放
    public bool is_loop = false;
    // 是否在加载的时候开始播放;
    public bool play_onload = false;

    private float played_time;
    private bool is_playing = false;

    private Image img;
    Action end_func = null;//----补充----加一个结束的动作


    // Use this for initialization
    void Start () {
        this.img = this.GetComponent<Image>();
        if (this.play_onload) {
            if (this.is_loop) {
                this.play_loop();
            }
            else {
                this.play_once(null);//----补充----改写成传递参数的调用
            }
        }
    }

    // 只播放一次
    public void play_once(Action end_func) {//----补充----改写成传递参数的调用
        if (this.sprite_frames.Length <= 1) {
            return;
        }

        this.end_func = end_func;//----补充----赋值

        this.played_time = 0;
        this.is_playing = true;
        this.is_loop = false;
    }

    // 循环播放
    void play_loop() {
        if (this.sprite_frames.Length <= 1) {
            return;
        }
        this.played_time = 0;
        this.is_playing = true;
        this.is_loop = true;
    }
    // 停止当前的动画播放
    void stop_anim() {
        this.is_playing = false;
    }
    // Update is called once per frame
    void Update () {
        if (this.is_playing == false) {
            return;
        }

        // 
        float dt = Time.deltaTime;
        this.played_time += dt;
        // 向下取整;
        int index = (int)(this.played_time / this.duration);
        if (this.is_loop == false) {
            // 结束了
            if (index >= this.sprite_frames.Length) { // 停止播放
                this.is_playing = false;
                this.played_time = 0;
                if (this.end_func != null) {//----补充----停止播放的时候执行结束函数
                    this.end_func();
                } 
            }
            else {
                this.img.sprite = this.sprite_frames[index];
            }
        }
        else {
            // 超过了范围,减掉一个周期
            while (index >= this.sprite_frames.Length) {
                this.played_time -= (this.duration * this.sprite_frames.Length);
                index -= this.sprite_frames.Length;
            }

            this.img.sprite = this.sprite_frames[index];
        }
        // end 
    }
}

改写的plane.cs

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

//定义两个飞机的状态用来后面判断是否播放爆炸动画
enum State
{
    NORMAL = 0,
    DEADED = 1,
};

public class plane : MonoBehaviour
{
    //播放帧动画要用的变量
    private frame_anim bomb_anim;
    public Sprite ship_idle;
    private Image ship_icon;
    private State state;

    //碰撞器
    private BoxCollider2D box;
    //飞机随着鼠标运动需要定义飞机坐标和鼠标坐标
    Vector3 start_plane_pos; // 按钮按下的时候飞机的开始的坐标
    Vector3 start_mouse_pos; // 鼠标开始的坐标;

    //子弹预制体
    public GameObject bullet_prefab;//预制体子弹节点
    public Transform bullet_root;//预制体子弹节点的父节点
    public float shoot_rate = 0.2f; // 子弹发射的频率;我感觉是一个时间间隔,设定的子弹发射时间间隔
    private float shoot_time = 0.0f; // 距离上一次发射过去的时间
    private bool is_shooting = false;//是否处于发射子弹的状态

    //-----优化-----
    private bool is_touch = false;//是否点到飞机
    private bool is_super = false;//是否处于无敌状态

    // Use this for initialization
    void Start()
    {
        //帧动画播放初始化
        this.bomb_anim = this.transform.Find("anim").GetComponent<frame_anim>();
        this.ship_icon = this.bomb_anim.GetComponent<Image>();
        this.state = State.NORMAL;

        //获得碰撞器
        this.box = this.GetComponent<BoxCollider2D>();
    }

    public void start_game()
    {
        this.is_shooting = true;

    }

    void shoot_bullet()
    {
        //使用预制体生成一颗子弹
        GameObject bullet = GameObject.Instantiate(this.bullet_prefab);
        // 注意这个参数要使用false
        bullet.transform.SetParent(this.bullet_root, false);

        //使用localPosition是因为子弹和plane都有相同的父节点,两者之间是相对坐标
        Vector3 offset = new Vector3(0, 64, 0);
        bullet.transform.localPosition = this.transform.localPosition + offset;
    }

    // 响应我们的鼠标事件,GetMouseButton(0)
    void Update()
    {
        //鼠标按下的情况
        if (Input.GetMouseButtonDown(0))
        {
            //-----修改-----
            this.is_touch = false;//每次鼠标点下去,不管有没有点到飞机,初始化为没点到
            Ray myRay = Camera.main.ScreenPointToRay(Input.mousePosition);//从摄像机发出一条射线
            RaycastHit2D hit = Physics2D.Raycast(new Vector2(myRay.origin.x, myRay.origin.y), Vector2.zero);//射线从鼠标点击屏幕的那个点出发,射到以当前点击位置为原点的坐标系中的垂直于(0,0)的位置,
                                                                    //如果从3D的视角看就是摄像机的射线垂直射到Canvas上
            if (hit.collider)//如果碰到有Collider2D组件的物体,就做一些事情
            {
                if (hit.transform.gameObject.name == "plane")//如果碰到的是飞机
                {
                    //Debug.Log(hit.transform.name);//打印出碰撞到的节点的名字
                    this.is_touch = true;//点到飞机
                }
            }

            if (is_touch)//如果点到飞机
            {
                //获得鼠标的初始点击位置
                this.start_mouse_pos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
                //获得飞机的初始位置
                this.start_plane_pos = this.transform.position;
            }
        }

        //鼠标滑动的情况
        else if (Input.GetMouseButton(0) && this.is_touch)
        {
            Vector3 w_pos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            //获得偏移量
            Vector3 offset = w_pos - this.start_mouse_pos;
            //设置飞机偏移后的位置
            this.transform.position = this.start_plane_pos + offset;
        }

        // 子弹发射逻辑控制,update里面嵌套自定义的update,自定义刷新函数
        this.shoot_update(Time.deltaTime);
    }

    void shoot_update(float dt)
    {
        if (!this.is_shooting)
        {
            return;
        }

        this.shoot_time += dt;
        if (this.shoot_time < this.shoot_rate)
        {
            return;
        }

        this.shoot_time = 0;
        this.shoot_bullet();
    }

    //写一个触发器来响应碰撞
    void OnTriggerEnter2D(Collider2D c)
    {
        if (this.state == State.DEADED)
        {
            return;
        }

        if (!is_super)//飞机不是超级状态
        {
            this.state = State.DEADED;

            this.box.enabled = false;// 把物理碰撞区域给隐藏,这样他就不会触发碰撞了。
            this.is_shooting = false; // 不能发射子弹了

            this.bomb_anim.play_once(this.on_bomb_anim_ended);//回调函数
        }
    }

    //写一个爆炸后调用的函数
    void on_bomb_anim_ended()
    {
        //Debug.Log("on_bomb_anim_ended called");
        this.bomb_anim.gameObject.SetActive(false);//把飞机的形状隐藏起来,不参与碰撞,也可以把刚体隐藏起来,也不参与碰撞
        this.Invoke("plane_relive", 3.0f);
    }

    //写一个爆炸后调用的函数的定时函数
    void plane_relive()
    {
        this.state = State.NORMAL;
        this.is_shooting = true;
        this.shoot_time = 0;

        this.ship_icon.sprite = this.ship_idle;//图像
        this.bomb_anim.gameObject.SetActive(true);//把飞机的形状显示出来,参与碰撞
        this.ship_icon.color = Color.red;//图像颜色变红
        this.is_super = true;//设置为超级状态
        this.box.enabled = true;//显示碰撞器
        this.Invoke("enable_collider", 3.0f);// 允许3秒的无敌状态,3秒后再打开我们的碰撞无敌区域
    }

    //写一个爆炸后调用的函数的定时函数的定时函数
    void enable_collider()
    {
        this.is_super = false;//再设置回正常状态
        this.ship_icon.color = Color.white;//图像颜色变原色
    }
}

步骤七>>>>>>子弹打死敌人后删除自己,敌人也要做爆炸

1.由于子弹要在碰到敌机后马上删除,所以它自己也要写一个OnTriggerEnter2D的函数,在发生碰撞的时候调用,在plane_bullet.cs里面写OnTriggerEnter2D函数并直接Destroy自己的节点。

    // 子弹如果碰到了敌机,那么马上删除;
    void OnTriggerEnter2D(Collider2D c) {
        MonoBehaviour.Destroy(this.gameObject);
    }

2.由于敌机要在碰到子弹后自爆,所以它自己也要写一个OnTriggerEnter2D的函数,在发生碰撞的时候调用。

3.在敌机e1到e8预制节点下面分别添加frame_anim.cs组件,因为敌机爆炸有7张图,属性size就写7,并把每一张爆炸图都拖进去。

改写后的enemy.cs

using UnityEngine;
using System.Collections;
using System;

//用代码添加刚体组件和形状碰撞组件
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(BoxCollider2D))]

public class enemy : MonoBehaviour {
    //敌机爆炸所需要的变量
    private frame_anim bomb_anim;
    //委托
    public event Action dead_event;  

   //敌机的类型
   public int e_type; 

    private BoxCollider2D box;
    private Rigidbody2D body;
    private float dead_line_y;

  // Use this for initialization
    void Start () {
        
        this.body = this.GetComponent<Rigidbody2D>();
        this.box = this.GetComponent<BoxCollider2D>();

      //设置敌机碰撞器的形状的大小
      RectTransform r_trans = (RectTransform)this.transform;
      r_trans.localScale = new Vector3(2, 2, 1);//放大节点两倍
      Vector2 size = r_trans.sizeDelta;//取出节点的大小

      this.box.size = size;//设置好碰撞器大小
      this.box.isTrigger = true;//只触发不产生碰撞效果
      this.body.freezeRotation = true;//刚体不旋转
      this.body.velocity = new Vector2(0, -8);//给敌机一个初始向下的速度

      //敌机飞出屏幕后删除
      float scale = 640.0f / (float)Screen.width;
      this.dead_line_y = -(Screen.height * scale * 0.5f + 100);

      //获得播放动画的组件
      this.bomb_anim=this.GetComponent<frame_anim>();

    }
    
    // Update is called once per frame
    void Update () {
        if (this.transform.localPosition.y < this.dead_line_y) {
            MonoBehaviour.Destroy(this.gameObject);
        }
    }

    //写一个触发器来响应碰撞,碰到子弹后自爆,碰到飞机一点事情都没有
   void OnTriggerEnter2D(Collider2D c) {
        // 敌人碰到玩家的子弹,敌人碰到玩家,敌人不爆炸,玩家爆炸,敌人碰到子弹,敌人爆炸
        if (!c.gameObject.tag.Equals("plane_bullet")) { // 飞机碰到玩家,玩家爆炸;
            return;
        }
        //  敌机就不能有碰撞区域
        this.box.enabled = false;
        // 子弹打到敌人
        this.bomb_anim.play_once(this.on_bomb_anim_end);
        // end 
    }

  //写一个回调函数来等触发函数执行后来调用
    void on_bomb_anim_end() {
        this.dead_event(); // 触发事件
        MonoBehaviour.Destroy(this.gameObject);
    }
}

步骤八>>>>>>加上玩家得分的情况

1.我们需要一个统计分数的节点,我们在Canvas节点下面再创建一个空节点叫game_ui,Hierarchy视图中要放在game_root和menu_root之间。

2.把game_ui的节点大小设置为640X960,同时把菊花分开,让父亲有多大,孩子就有多大。

3.在tex文件夹下面创建一个美术字资源create-->Custom Font,把它的名字命名地和我们的字体资源一样的名字,是win_score.fontsettings。这个资源需要有个材质属性。于是我们创建一个字体材质create-->Material,名字命名地和字体资源的名字是一样的,叫win_score.mat。把材质的Shader设置为Mobile/Diffuse,然后把我们的原始字体资源win_score.png文件拖进去,再把材质球win_score.mat拖进刚才创建的Custom Font字体资源win_score.fontsettings。

4.我们还需要导入字模,在第24的文件夹里面,在Resources文件夹下面创建一个叫做Editor的文件夹,表示里面的东西是对Unity编辑器的扩展。然后把24里面的CreateFontEditor.cs脚本拷贝进去。

5.对win_score.fnt文件右键-->create-->CreateFBMFont,然后字模就导入进刚才的字体资源文件了,注意这里的文件是那个全是字的文件,不是我们刚才创建的win_score.fontsettings文件。

6.在game_ui节点下,创建一个Text类型的叫score的UI节点,把它的菊花设置为左上角对齐,然后设置具体坐标位置让它在左上角x,y(32,-32)

7.在enemy_root节点下的gen_enemy脚本里面写一个public的score对象属性,然后把刚才的score节点拖进去绑定。(绑定我的理解是:绑定不是赋值,而是引用,所以会同步发生变化)

8.字体颜色设置为白色,字体居中,把刚才我们创建的那个字体资源文件win_score.fontsettings拖进去Text组件的Character的Font属性中。

8.写一个事件委托,在enemy里面敌机爆炸的那个函数里面,触发事件,然后在gen_enemy脚本里面对事件进行响应,调用add_score函数,每次有事件发生就调用add_score函数。

改写后的gen_enemy脚本

using UnityEngine;
using System.Collections;
using UnityEngine.UI;


public class gen_enemy : MonoBehaviour {
    public GameObject[] group_set; // 分组的集合
    public GameObject[] enemy_set; // 敌人的集合

    //分数统计
    public Text score;
    int score_value;

    // Use this for initialization
    void Start () {
        this.score.text = "0";
        this.score_value = 0;
    }

    //开始生成敌机的函数
    public void start_gen_enemy() {
        this.score.text = "0";
        this.score_value = 0;
        this.Invoke("gen_one_group", 3);
    }
  
void gen_one_group() { // [0, this.group_set.Length) 随机数,生成一个0到敌机组总数-1的随机数 int group_index = Random.Range(0, this.group_set.Length); //设置一个初始位置,敌机组从这里生成并开始移动 Vector3 start_pos = new Vector3(0, 912, 0); // 循环遍历group下面的孩子数目;遍历随机到的那一个敌机组,遍历里面的每一个敌机 for (int i = 0; i < this.group_set[group_index].transform.childCount; i++) {        //生成一个0到敌机总数-1的随机数,随机取1到8敌机类型的一种类型        int e_type = Random.Range(0, this.enemy_set.Length);        //获得孩子的位置,获得当前指定敌机的位置 Transform group_enemy = this.group_set[group_index].transform.GetChild(i); // 随机的生成了一个敌人,开始随机生成一架敌机 GameObject e_item = GameObject.Instantiate(this.enemy_set[e_type]); e_item.transform.SetParent(this.transform, false); Vector3 pos = start_pos + group_enemy.localPosition; e_item.transform.localPosition = pos;              //-----非常重要的一句话-----这句话就是把委托和响应函数进行了关联        e_item.GetComponent<enemy>().dead_event += this.add_score; } // [0.0, 3.0f] this.Invoke("gen_one_group", 3 + Random.Range(0.0f, 3.0f)); } //增加分数的函数 void add_score() { this.score_value ++; this.score.text = "" + this.score_value; }   // Update is called once per frame void Update () { } }

改写后的enemy脚本在步骤7里面,上次写的超前了

步骤九>>>>>>打开menu主页, 做好GUI 适配

1.把步骤1里面的对menu_root的隐藏显示出来,打钩。再注释掉game_scene里面的Start函数的this.on_game_start_click();这样只有按了按钮之后才会真正地开始游戏。

2.把menu_root节点的菊花也是设置为最右下角那个图案,使得父节点怎么变化,子节点就怎么变化。

3.把menu_root节点下面的menu_bg背景图节点的菊花设置为正下方那个图案,使得它可以随着分辨率的变化而上下拉伸。

4.屏幕适配完成。

原文地址:https://www.cnblogs.com/HangZhe/p/7096233.html