关于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.屏幕适配完成。