Unity实现简易打飞碟改进版(Hit UFO)

前言

这是中山大学数据科学与计算机学院2019年3D游戏编程与设计的第六次作业
所有项目与代码已上传至github当中,欢迎大家访问。
github个人主页: https://starashzero.github.io
3D游戏编程与设计主页: https://starashzero.github.io/3DGameDesign
本次作业项目地址: https://github.com/StarashZero/3DGameDesign/tree/master/hw6

游戏简介

鼠标作为武器,打爆飞碟!

(比上一代多了两个按钮)

游戏玩法

点击飞出来的UFO(各种圆盘)即可得分。

游戏规则

  1. 正常模式共有五轮飞碟,飞碟数列遵循斐波那契数列,当所有飞碟发射完毕时游戏结束。
  2. 无限模式拥有无限轮数 (其实就是重复)
  3. 红色飞碟为1分,绿色飞碟为2分,蓝色飞碟为3分 (RGB!)
  4. 物理学模式下飞碟会互相碰撞,这不是一个bug,是一个feature!

游戏试玩

项目要求

在Hit UFO初版的基础上

  • 按 adapter模式 设计图修改飞碟游戏

  • 使它同时支持物理运动与运动学(变换)运动

项目代码结构

在初版项目的基础上进行了进一步的封装,同时需要实现物理学运动,所以增加了几个文件。

UML图:

各部分代码解释

许多文件的代码都是没有变动或者变动较少的,新增或有修改我会指出。

  • 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;}}
    }
    

    场景单实例类,当所需的实例第一次被需要时,在场景内搜索该实例,下一次使用时不需要搜索直接返回。

  • DiskData.cs

    public class DiskData : MonoBehaviour
    {public float speed;         //水平速度public int points;          //得分public Vector3 direction;   //初始方向
    }
    

    飞碟数据,携带飞碟的飞行速度、得分、以及飞行方向。

  • DiskFactory.cs

    public class DiskFactory : MonoBehaviour
    {public GameObject disk_Prefab;              //飞碟预制private List<DiskData> used;                //正被使用的飞碟private List<DiskData> free;                //空闲的飞碟public void Start(){used = new List<DiskData>();free = new List<DiskData>();disk_Prefab = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/Disk_Prefab"), Vector3.zero, Quaternion.identity);disk_Prefab.SetActive(false);}public GameObject GetDisk(int round){GameObject disk;//如果有空闲的飞碟,则直接使用,否则生成一个新的if (free.Count > 0){disk = free[0].gameObject;free.Remove(free[0]);}else{disk = GameObject.Instantiate<GameObject>(disk_Prefab, Vector3.zero, Quaternion.identity);disk.AddComponent<DiskData>();}//按照round来设置飞碟属性//飞碟的等级 = 0~2之间的随机数 * 轮次数//0~4:  红色飞碟  //4~7:  绿色飞碟  //7~10: 蓝色飞碟float level = UnityEngine.Random.Range(0, 2f) * (round + 1);if (level < 4){disk.GetComponent<DiskData>().points = 1;disk.GetComponent<DiskData>().speed = 4.0f;disk.GetComponent<DiskData>().direction = new Vector3(UnityEngine.Random.Range(-1f, 1f) > 0 ? 2 : -2, 1, 0);disk.GetComponent<Renderer>().material.color = Color.red;}else if (level > 7){disk.GetComponent<DiskData>().points = 3;disk.GetComponent<DiskData>().speed = 8.0f;disk.GetComponent<DiskData>().direction = new Vector3(UnityEngine.Random.Range(-1f, 1f) > 0 ? 2 : -2, 1, 0);disk.GetComponent<Renderer>().material.color = Color.blue;}else{disk.GetComponent<DiskData>().points = 2;disk.GetComponent<DiskData>().speed = 6.0f;disk.GetComponent<DiskData>().direction = new Vector3(UnityEngine.Random.Range(-1f, 1f) > 0 ? 2 : -2, 1, 0);disk.GetComponent<Renderer>().material.color = Color.green;}used.Add(disk.GetComponent<DiskData>());return disk;}public void FreeDisk(GameObject disk){//找到使用中的飞碟,将其踢出并加入到空闲队列foreach (DiskData diskData in used){if (diskData.gameObject.GetInstanceID() == disk.GetInstanceID()){disk.SetActive(false);free.Add(diskData);used.Remove(diskData);break;}}}
    }
    

    飞碟工厂,负责生产与释放飞碟。

    • GetDisk:
      GetDisk用于生产飞碟,首先从free空闲队列中查找是否有可用的飞碟,如果没有则新建一个飞碟。
      飞碟属性的设置依赖轮次数,将一个0~2之间的随机数乘以轮次数获得level,再根据level来设置属性。
      这样就具有了轮次越多优质飞碟越多的特性,且前期不会产生高分飞碟。
    • FreeDisk:
      FreeDisk用于释放飞碟,将飞碟从used队列中移除并添加到free队列中。
  • SSAction.cs

    public class SSAction : ScriptableObject
    {public bool enable = true;public bool destroy = false;public GameObject gameObject { get; set; }public Transform transform { get; set; }public ISSActionCallback callback { get; set; }protected SSAction(){}// Start is called before the first frame updatepublic virtual void Start(){throw new System.NotImplementedException();}// Update is called once per framepublic virtual void Update(){throw new System.NotImplementedException();}
    }
    

    动作的基类,与之前的项目用途一致。

  • CCFlyAction.cs
    CCFlyAction有一些少量修改。
    因为CCFlyAction是运动学的飞行,因此生成CCFlyAction时会将对象的刚体设为运动学的。

    public override void Start()
    {gameObject.GetComponent<Rigidbody>().isKinematic = true;
    }
    

    完整代码

    public class CCFlyAction : SSAction
    {float gravity;          //重力加速度float speed;            //水平速度Vector3 direction;      //飞行方向float time;             //时间//生产函数(工厂模式)public static CCFlyAction GetSSAction(Vector3 direction, float speed){CCFlyAction action = ScriptableObject.CreateInstance<CCFlyAction>();action.gravity = 9.8f;action.time = 0;action.speed = speed;action.direction = direction;return action;}public override void Start(){gameObject.GetComponent<Rigidbody>().isKinematic = true;}public override void Update(){time += Time.deltaTime;transform.Translate(Vector3.down * gravity * time * Time.deltaTime);transform.Translate(direction * speed * Time.deltaTime);//如果飞碟到达底部,则动作结束,进行回调if (this.transform.position.y < -6){this.destroy = true;this.enable = false;this.callback.SSActionEvent(this);}}
    }

    飞行动作,将飞行拆分成水平和垂直两个方向的运动,水平速度恒定,垂直方向施加重力加速度。
    当飞碟到达底部时,动作结束,将进行回调。

  • ISSActionCallback

    public enum SSActionEventType : int { Started, Competed }
    public interface ISSActionCallback
    {//回调函数void SSActionEvent(SSAction source,SSActionEventType events = SSActionEventType.Competed,int intParam = 0,string strParam = null,Object objectParam = null);
    }
    

    回调函数接口,与之前项目用途一致。

  • SSActionManager.cs

    public class SSActionManager : MonoBehaviour
    {//动作集,以字典形式存在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(){//将waitingAdd中的动作保存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();}}//销毁waitingDelete中的动作foreach (int key in waitingDelete){SSAction ac = actions[key];actions.Remove(key);Destroy(ac);}waitingDelete.Clear();}//准备运行一个动作,将动作初始化,并加入到waitingAddpublic void RunAction(GameObject gameObject, SSAction action, ISSActionCallback manager){action.gameObject = gameObject;action.transform = gameObject.transform;action.callback = manager;waitingAdd.Add(action);action.Start();}// Start is called before the first frame updateprotected void Start(){}}
    

    动作管理者的基类,和之前项目用途一致。

  • CCActionManager.cs
    现在CCActionManager需要实现IActionManager的接口了

    public class CCActionManager : SSActionManager, ISSActionCallback, IActionManager
    {//飞行动作CCFlyAction flyAction;//控制器FirstController controller;protected new void Start(){controller = (FirstController)SSDirector.GetInstance().CurrentScenceController;}public void Fly(GameObject disk, float speed, Vector3 direction){flyAction = CCFlyAction.GetSSAction(direction, speed);RunAction(disk, flyAction, this);}//回调函数public void SSActionEvent(SSAction source,SSActionEventType events = SSActionEventType.Competed,int intParam = 0,string strParam = null,Object objectParam = null){//飞碟结束飞行后进行回收controller.FreeDisk(source.gameObject);}
    }
    

    飞行动作管理者,负责生成飞行动作,并接受飞行动作的回调信息,使飞碟被回收。

  • IUserAction.cs
    IUserAction进行了少量修改
    现在IUserAction提供了设置飞行模式的接口

    void SetFlyMode(bool isPhysis);
    

    完整代码

    public interface IUserAction
    {void SetFlyMode(bool isPhysis);void Hit(Vector3 position);void Restart();void SetMode(bool isInfinite);
    }
    

    用户动作接口,提供点击、重置、选择模式三个函数的接口。

  • 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;}
    }
    

    导演类,与之前项目用途一致。

  • ISceneController.cs

    public interface ISceneController
    {void LoadResources();
    }
    

    场景控制类接口,与之前项目用途一致。

  • FirstController
    FirstController进行了大量修改
    FirstController现在只负责与UserAction交互及少量功能函数了,其他功能下放置RoundController

    public class FirstController : MonoBehaviour, ISceneController, IUserAction
    {DiskFactory diskFactory;                         //飞碟工厂RoundController roundController;UserGUI userGUI;void Start(){SSDirector.GetInstance().CurrentScenceController = this;gameObject.AddComponent<DiskFactory>();gameObject.AddComponent<CCActionManager>();gameObject.AddComponent<PhysisActionManager>();gameObject.AddComponent<RoundController>();gameObject.AddComponent<UserGUI>();LoadResources();}public void LoadResources(){diskFactory = Singleton<DiskFactory>.Instance;roundController = Singleton<RoundController>.Instance;userGUI = Singleton<UserGUI>.Instance;}public void Hit(Vector3 position){Camera ca = Camera.main;Ray ray = ca.ScreenPointToRay(position);RaycastHit[] hits;hits = Physics.RaycastAll(ray);for (int i = 0; i < hits.Length; i++){RaycastHit hit = hits[i];if (hit.collider.gameObject.GetComponent<DiskData>() != null){//将飞碟移至底端,触发飞行动作的回调hit.collider.gameObject.transform.position = new Vector3(0, -7, 0);//积分roundController.Record(hit.collider.gameObject.GetComponent<DiskData>());//更新GUI数据userGUI.SetPoints(roundController.GetPoints());}}}public void Restart(){userGUI.SetMessage("");userGUI.SetPoints(0);roundController.Reset();}public void SetMode(bool isInfinite){roundController.SetMode(isInfinite);}public void SetFlyMode(bool isPhysis){roundController.SetFlyMode(isPhysis);}public void FreeDisk(GameObject disk){diskFactory.FreeDisk(disk);}void Update(){}
    }
    

    场景控制器,负责游戏主要逻辑。

    • SendDisk:
      SendDisk用于发射一个飞碟,首先从工厂获得一个飞碟,再为其设置初始位置和飞行动作。
    • Hit:
      Hit用于处理用户的点击动作,将用户点击到的飞碟移除,并计算分数。
    • Restart:
      Restart用于重置游戏。
    • Update:
      Update用于发射飞碟与更新状态,飞碟每1s发射一次,每次做多5只,避免太过拥挤,当飞碟发射完毕后判断是否重置或者结束游戏。
  • UserGUI.cs
    UserGUI进行了少量修改
    添加了两个设置飞行模式的按钮

    if (GUI.Button(new Rect(20, 200, 100, 40), "Kinematics"))
    {userAction.SetFlyMode(false);
    }
    if (GUI.Button(new Rect(20, 250, 100, 40), "Physis"))
    {userAction.SetFlyMode(true);
    }
    

    并且将UserGUI的成员设置成了私有(为了更好的封装),现在需要通过函数来修改UserGUI的变量了

    public void SetMessage(string gameMessage)
    {this.gameMessage = gameMessage;
    }public void SetPoints(int points)
    {this.points = points;
    }
    

    完整代码

    public class UserGUI : MonoBehaviour
    {IUserAction userAction;string gameMessage;int points;public void SetMessage(string gameMessage){this.gameMessage = gameMessage;}public void SetPoints(int points){this.points = points;}void Start(){points = 0;gameMessage = "";userAction = SSDirector.GetInstance().CurrentScenceController as IUserAction;}void OnGUI(){//小字体初始化GUIStyle style = new GUIStyle();style.normal.textColor = Color.white;style.fontSize = 30;//大字体初始化GUIStyle bigStyle = new GUIStyle();bigStyle.normal.textColor = Color.white;bigStyle.fontSize = 50;GUI.Label(new Rect(300, 30, 50, 200), "Hit UFO", bigStyle);GUI.Label(new Rect(20, 0, 100, 50), "Points: " + points, style);GUI.Label(new Rect(310, 100, 50, 200), gameMessage, style);if (GUI.Button(new Rect(20, 50, 100, 40), "Restart")){userAction.Restart();}if (GUI.Button(new Rect(20, 100, 100, 40), "Normal Mode")){userAction.SetMode(false);}if (GUI.Button(new Rect(20, 150, 100, 40), "Infinite Mode")){userAction.SetMode(true);}if (GUI.Button(new Rect(20, 200, 100, 40), "Kinematics")){userAction.SetFlyMode(false);}if (GUI.Button(new Rect(20, 250, 100, 40), "Physis")){userAction.SetFlyMode(true);}if (Input.GetButtonDown("Fire1")){userAction.Hit(Input.mousePosition);}}
    }
    

    界面类,构建UI并捕捉用户动作。

  • IActionManager

    public interface IActionManager
    {void Fly(GameObject disk, float speed, Vector3 direction);
    }
    

    动作管理类的接口,要求动作管理类的Adapter模式就是基于这个接口来实现,不过我认为在本次项目中并没有体现的很好,因为设计的两个动作管理类都是可以实现这个接口的功能的,因此感觉只起到了接口多态的作用。
    不过拥有这个接口后,以后可以将非Fly类型的动作管理类通过Adapter模式转换成Fly类型的动作管理类。

  • PhysisFlyAction
    PhysisFlyAction是新增的代码
    PhysisFlyAction实现物体的物理学飞行
    由于预制体已经添加了刚体属性,并且选择了Use Gravity,因此只需要为物体增加一个水平初速度即可。

    public class PhysisFlyAction : SSAction
    {float speed;            //水平速度Vector3 direction;      //飞行方向//生产函数(工厂模式)public static PhysisFlyAction GetSSAction(Vector3 direction, float speed){PhysisFlyAction action = ScriptableObject.CreateInstance<PhysisFlyAction>();action.speed = speed;action.direction = direction;return action;}public override void Start(){gameObject.GetComponent<Rigidbody>().isKinematic = false;//为物体增加水平初速度gameObject.GetComponent<Rigidbody>().velocity = speed * direction;}public override void Update(){//如果飞碟到达底部,则动作结束,进行回调if (this.transform.position.y < -6){this.destroy = true;this.enable = false;this.callback.SSActionEvent(this);}}
    }
    
  • PhysisActionManager
    PhysisActionManager是新增的代码。
    虽然是新增的,但是代码几乎与CCActionManager一致,只是从管理CCFlyAction变为管理PhysisFlyAction

    public class PhysisActionManager : SSActionManager, ISSActionCallback, IActionManager
    {//飞行动作PhysisFlyAction flyAction;//控制器FirstController controller;protected new void Start(){controller = (FirstController)SSDirector.GetInstance().CurrentScenceController;}public void Fly(GameObject disk, float speed, Vector3 direction){flyAction = PhysisFlyAction.GetSSAction(direction, speed);RunAction(disk, flyAction, this);}//回调函数public void SSActionEvent(SSAction source,SSActionEventType events = SSActionEventType.Competed,int intParam = 0,string strParam = null,Object objectParam = null){//飞碟结束飞行后进行回收controller.FreeDisk(source.gameObject);}
    }
    
  • RoundController
    RoundController承接了初版中FirstController每回合对飞碟的发送、计分等功能的代码
    目的是减轻FirstController的负担并使代码结构更加清晰

    public class RoundController : MonoBehaviour
    {FirstController controller;IActionManager actionManager;                   //动作管理者DiskFactory diskFactory;                         //飞碟工厂ScoreRecorder scoreRecorder;UserGUI userGUI;int[] roundDisks;           //对应轮次的飞碟数量bool isInfinite;            //游戏当前模式int round;                  //游戏当前轮次int sendCnt;                //当前已发送的飞碟数量float sendTime;             //发送时间void Start(){controller = (FirstController)SSDirector.GetInstance().CurrentScenceController;actionManager = Singleton<CCActionManager>.Instance;diskFactory = Singleton<DiskFactory>.Instance;scoreRecorder = new ScoreRecorder();userGUI = Singleton<UserGUI>.Instance;sendCnt = 0;round = 0;sendTime = 0;isInfinite = false;roundDisks = new int[] { 3, 5, 8, 13, 21 };}public void Reset(){sendCnt = 0;round = 0;sendTime = 0;scoreRecorder.Reset();}public void Record(DiskData disk){scoreRecorder.Record(disk);}public int GetPoints(){return scoreRecorder.GetPoints();}public void SetMode(bool isInfinite){this.isInfinite = isInfinite;}public void SetFlyMode(bool isPhysis){actionManager = isPhysis ? Singleton<PhysisActionManager>.Instance : Singleton<CCActionManager>.Instance as IActionManager;}public void SendDisk(){//从工厂生成一个飞碟GameObject disk = diskFactory.GetDisk(round);//设置飞碟的随机位置disk.transform.position = new Vector3(-disk.GetComponent<DiskData>().direction.x * 7, UnityEngine.Random.Range(0f, 8f), 0);disk.SetActive(true);//设置飞碟的飞行动作actionManager.Fly(disk, disk.GetComponent<DiskData>().speed, disk.GetComponent<DiskData>().direction);}// Update is called once per framevoid Update(){sendTime += Time.deltaTime;//每隔1s发送一次飞碟if (sendTime > 1){sendTime = 0;//每次发送至多5个飞碟for (int i = 0; i < 5 && sendCnt < roundDisks[round]; i++){sendCnt++;SendDisk();}//判断是否需要重置轮次,不需要则输出游戏结束if (sendCnt == roundDisks[round] && round == roundDisks.Length - 1){if (isInfinite){round = 0;sendCnt = 0;userGUI.SetMessage("");}else{userGUI.SetMessage("Game Over!");}}//更新轮次if (sendCnt == roundDisks[round] && round < roundDisks.Length - 1){sendCnt = 0;round++;}}}
    }
    
  • ScoreRecorder
    ScoreRecorder是新增的代码
    实现了对游戏计分的功能

    public class ScoreRecorder
    {int points;                 //游戏当前分数public ScoreRecorder(){points = 0;}public void Record(DiskData disk){points += disk.points;}public int GetPoints(){return points;}public void Reset(){points = 0;}
    }
    

Unity实现简易打飞碟改进版(Hit UFO)相关推荐

  1. unity实现鼠标打飞碟(Hit UFO)游戏

    unity实现鼠标打飞碟(Hit UFO)游戏 游戏规则及要求 规则:玩家初始有5条生命,每漏过一个飞碟,生命减一.游戏分数的增加与命中飞碟时的位置有关,越早击中飞碟分数越高.游戏有四档难度,随分数递 ...

  2. Unity实现鼠标打飞碟(Hit UFO)adapter模式

    Unity实现鼠标打飞碟(Hit UFO)adapter模式 与上一版本的变化 按 adapter模式设计图修改飞碟游戏 使它同时支持物理运动与运动学(变换)运动 适配器模式 适配器模式(Adapte ...

  3. Unity实现简易打飞碟(Hit UFO)

    Unity实现简易打飞碟(Hit UFO) 前言 这是中山大学数据科学与计算机学院2019年3D游戏编程与设计的第四次作业 所有项目与代码已上传至github当中,欢迎大家访问. github个人主页 ...

  4. unity 实现简易打飞碟游戏

    unity 实现简易打飞碟游戏 一.简介 游戏共有5个回合,每个回合中会有随机产生的飞碟飞过屏幕,玩家需要做的事情就是用鼠标尽量快和多地点击飞碟. 每个飞碟对应一定的分数,目前的设置是: [红色飞碟 ...

  5. Unity3d Note5(鼠标打飞碟(Hit UFO)游戏)

    1.作业要求 2.具体设计 (1).制备预制体作为飞碟 (2).了解一下Singleton模板类 (3).了解一下工厂模式 (3).设计具体要实现的类 3.程序代码 成果视频 1.作业要求 编写一个简 ...

  6. 改进飞碟(Hit UFO)游戏

    1.改进飞碟(Hit UFO)游戏: 游戏内容要求: 按 adapter模式 设计图修改飞碟游戏 使它同时支持物理运动与运动学(变换)运动 新增的类或者有修改的类如下: ActionManagerAd ...

  7. 3D游戏设计作业5:改进飞碟(Hit UFO)游戏

    改进飞碟(Hit UFO)游戏: 游戏截图: 1,作业要求 游戏内容要求: 1,按adapter模式设计图修改飞碟游戏 2,使它同时支持物理运动与运动学(变换)运动 2,设计思路 1,分析adapte ...

  8. 游戏开发之Unity学习(五)——鼠标打飞碟(Hit UFO)

    一.游戏内容要求: 游戏有 n 个 round,每个 round 都包括10 次 trial: 每个 trial 的飞碟的色彩.大小.发射位置.速度.角度.同时出现的个数都可能不同.它们由该 roun ...

  9. 3D游戏编程实践——打飞碟(Hit UFO)游戏运动与物理兼容版

    这次编程实践是在上一次实践的基础上,增加了物理运动,做到了运动学和物理运动会的兼容.而原来的代码只实现了运动学部分. 首先需要实现物理运动部分的Action类和ActionManager类. 相比起运 ...

最新文章

  1. Laravel之Eloquent ORM访问器调整器及属性转换
  2. JAVA入门[17]-ControllerAdvice处理exception
  3. Spring-依赖注入
  4. 脑科学助力人工智能,离不开大数据
  5. 日期格式校验方法工具
  6. matlab径向分布函数作图_常见的概率分布(matlab作图)
  7. 64位win10系统无法安装.Net framework3.5的两种解决方法
  8. 计算机操作系统(2):OS的发展过程
  9. Windows直接获取文件的哈希值
  10. concat效率 mysql_MYSQL数据库mysql中or效率高还是in效率高
  11. html5 m3u8 直播,html5 让video支持m3u8播放
  12. 现如今安卓手机的系统优化哪家好呢?
  13. 冶金工程在计算机应用,冶金工程专业计算机应用能力
  14. selectpicker.js的属性和方法
  15. 科目三道路驾驶技能考试使用计算机系统,科目三道路驾驶技能考试方法是怎样的?...
  16. 优质短视频内容有哪些特质?不要迷信爆款,通俗易懂很重要
  17. win10添加打印机--无法访问指定设备,路径或文件。。
  18. H3C新华三链路聚合介绍
  19. 输入数据练习-JAVA
  20. Excel整行数据自动标颜色

热门文章

  1. mac下如何查看真机上应用的沙盒文件
  2. 从零开始定义自己的JavaScript框架(一)
  3. Vue学习(一)基本属性、Axios通信、插槽、官方脚手架搭建
  4. 冰点还原自动执行任务该怎么设置?
  5. 常用算法 之五 数据校验(CRC 原理、LRC、奇偶校验、校验和)详解
  6. 低概率事件在样本量足够大时总会出现
  7. 在虾皮shopee,一定要知道这四点,收到开店邀请我要怎么做?
  8. 鸿蒙IOT开发板 小熊派上手体验
  9. 判断日期是否为法定节假日的API接口与示例函数
  10. VirtualBox下ubuntu安装中文输入法