Unity 3D 鼠标打飞碟之改进版

效果图:

效果图

场景也可加天空盒等组件进行优化,选择很多。

要求:

  1. adapter模式 设计图修改飞碟游戏
  2. 使它同时支持物理运动与运动学(变换)运动

思路:

  新建一个 IActionManager 类来管理动作,CCActionManagerPhysisActionManager 负责具体的运动学和物理引擎实现动作。其余 DiskFactory , DiskData 稍作修改即可。

实现:

  1. Action

    接口,声明基类的动作:

    1
    2
    3
    4
    5
    6
    public interface IAction
    {
    void play();
    void randomAction(int num, int level);
    void remove(DiskData disk);
    }
  2. IActionManager

    基类,实现接口动作,有两个类的对象和一个模式选择变量,当用户选择不同的模式时,实现不同的动作,以 play() 为例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    CCActionManager cc;
    PhysisActionManager ph;
    int mode;
    public IActionManager()
    {
    cc = new CCActionManager();
    ph = new PhysisActionManager();
    mode = 0;
    }
    public void setMode(int i)
    {
    mode = i;
    }
    public void play()
    {
    if (mode == 0)
    {
    cc.play();
    }
    else
    {
    ph.play();
    }
    }
  3. CCAction

    运动学类,通过坐标变化来实现运动,包含一个单例类的 DiskFactory ,用来回收和生产飞碟:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    List<DiskData> diskList;
    DiskFactory diskFactory;
    float gravity = 9.8f;
    Color color;

    public CCActionManager()
    {
    diskList = new List<DiskData>();
    diskFactory = DiskFactory.getInstance();
    }

    其次,在 randomAction() 类中,通过不同的 level 来区分不同的难度,主要体现在速度不同:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public void randomAction(int ufoNum, int level)
    {
    setColor(level);
    for (int i = 0; i < ufoNum; i++)
    {
    Vector3 position = new Vector3(0, 0, 0);//飞碟的起始位置
    float speed = level * 10f;//飞碟速度
    Vector3 direction = new Vector3(
    UnityEngine.Random.Range(-10f, 10f),
    UnityEngine.Random.Range(40f, 80f),
    UnityEngine.Random.Range(50f, 100f));//飞碟方向
    direction.Normalize();//单位化该方向向量,以免产生较大误差
    Ruler ruler = new Ruler(color, position, speed, direction);//新建一个规则
    diskList.Add(diskFactory.getDisk(ruler));//添加飞碟
    }
    }

    再者,在 FirstSceneCtroller 类中每一帧会调用 play() 函数,以此实现飞碟的运动和回收:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public void play()
    {
    for (int i = 0; i < diskList.Count; i++)
    {
    move(diskList[i]);//移动飞碟
    }
    for (int i = 0; i < diskList.Count; i++)
    {
    if (diskList[i].gameObject.transform.position.y < 0)//若飞碟低于地面,则回收
    {
    remove(diskList[i]);
    }
    }
    }
  4. PhysisActionManager

    此类负责实现物理引擎实现UFO运动,基本思路与运动学较为相似,区别是需要给UFO加一个外力,让其自己运动而不是人为的坐标变换。

    我选择的方法是给UFO添加 ConstantForce 组件,一个恒力,以此实现运动效果,当物体被回收时,去掉这个恒力,具体实现代码在 DiskData 类中,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public void setForce(Vector3 f)
    {
    this.gameObject.AddComponent<ConstantForce>().force = f;
    }
    public void removeForce()
    {
    Destroy(this.gameObject.GetComponent<ConstantForce>());
    Destroy(this.gameObject.GetComponent<Rigidbody>());
    }

    randomAction() 如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public void randomAction(int ufoNum, int level)
    {
    setColor(level);
    for (int i = 0; i < ufoNum; i++)
    {
    Vector3 position = new Vector3(0, 0, -20f);
    float speed = level * 10f;//飞碟速度
    Vector3 force = new Vector3(
    UnityEngine.Random.Range(-5f, 5f),
    UnityEngine.Random.Range(10f, 12f),
    speed);
    Ruler ruler = new Ruler(color, position, 0, Vector3.zero);//新建一个规则
    diskList.Add(diskFactory.getDisk(force,ruler));//添加飞碟
    }
    }

    此外,在 play() 函数中,就不需设置 move 函数了,只需回收飞碟即可。

  5. FirstSceneCtroller

    场景控制器,负责整个场景的调度,其中加入 IActionManager 的对象,实现物体运动:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    Director director;
    IActionManager ac;
    ScoreRecorder score;
    float interval = 0;//发射飞镖的间隔时间
    int level = 0;//难度级别
    public int ufoNum = 1;//每一次发射飞碟的数目

    void Awake()
    {
    director = Director.getInstance();//单例类
    director.currentSceneCtroller = this;
    score = ScoreRecorder.getInstance();
    LoadResource();
    }

    public void LoadResource()
    {
    ac = new IActionManager();
    }

    注意这里除了导演类的实例外,还有个记分员的实例,其实现与导演类相似,负责场景中的计分,实现十分简单,详见代码,在此不多加赘述。

    在场景中,每一帧都会调用一次物体的运动函数,实现刷新效果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    void Update()
    {
    interval += Time.deltaTime;
    if (interval >= 1f && director.state == State.PLAYING && level > 0)//若间隔大于1s且处于play且已设置好level
    {
    ac.randomAction(ufoNum, level);
    interval = 0;//间隔重新置为0
    }
    ac.play();

    if (Input.GetButtonDown("Fire1"))//开火
    {
    Vector3 mousePosition = Input.mousePosition;
    Camera cam = Camera.main;
    Ray ray = cam.ScreenPointToRay(mousePosition);
    RaycastHit hit;
    if (Physics.Raycast(ray, out hit))
    {
    score.record(level);//计分
    recycle(hit.transform.gameObject.GetComponent<DiskData>());//回收
    }
    }
    }
  6. DiskFactory

    该类负责UFO的制作与回收,即工厂类,主要有两个list,分别存已用过的飞碟和回收的:

    1
    2
    3
    4
    public DiskData diskPrefab;//飞碟预制
    public static DiskFactory _instance;
    List<DiskData> used;//已用过的
    List<DiskData> free;//未用

    负责生产飞碟的函数共有两个版本,分别是对于 CCActionManagerPhysisActionManager 类的,差别不大,主要区别在是否添加恒力,以下以运动学的为例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public DiskData getDisk(Ruler ruler)//根据参数设置飞碟
    {
    DiskData disk;
    if (_instance.free.Count > 0)//若不空,则从中找
    {
    disk = _instance.free[0];
    _instance.free.RemoveAt(0);
    }
    else
    {
    disk = Instantiate(diskPrefab);
    }
    disk.ruler = ruler;
    _instance.used.Add(disk);
    return disk;
    }

    回收飞碟类:

    1
    2
    3
    4
    5
    6
    7
    8
    public void freeDisk(DiskData disk)//回收飞碟
    {
    _instance.free.Add(disk);
    if (!_instance.used.Remove(disk))//抛出错误
    {
    throw new System.Exception();
    }
    }
  7. DiskData

    飞碟类,包含一个 Ruler 类,包含飞碟的种种规则,也可不封装成类,皆可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    Ruler _ruler;
    public float x = 0, y = 0, z = 0;//用于存储该飞碟在飞行方向上的速度
    public Ruler ruler
    {
    get
    {
    return _ruler;
    }
    set
    {
    _ruler = value;
    x = value.speed * value.direction.x;
    y = value.speed * value.direction.y;
    z = value.speed * value.direction.z;
    gameObject.GetComponent<Renderer>().material.color = value.color;
    gameObject.transform.position = value.position;
    }
    }
    public void setForce(Vector3 f)
    {
    this.gameObject.AddComponent<ConstantForce>().force = f;
    }
    public void removeForce()
    {
    Destroy(this.gameObject.GetComponent<ConstantForce>());
    Destroy(this.gameObject.GetComponent<Rigidbody>());
    }

    需要注意的是,在使用该类时,需将其绑定到制作好的预制上,然后删除对象,即可正常使用。此时,代码中的 gameObject 就指的是脚本文件绑定的对象,即预制。

    UI.csDiskFactory.csFirstSceneController.cs 拖到空对象上,拖入预制,即可运行,可自主设置每次出现的飞碟数。

还有 BaseCode.csUI.cs 等文件就不加赘述了,详情可见Github 源码

如果有任何不懂的话,可参考第一版 鼠标打飞碟演示视频

分享到 评论