Unity笔记-打飞碟游戏

目的:创建一个打飞碟游戏(简陋)


游戏要求:
游戏要分多个 round , 飞碟数量每个 round 都是 n 个,但色彩,大小;发射位置,速度,角度,每次发射数量可以变化。
游戏过程中,仅能创建 n 个飞碟, 且不容许初始化阶段生成任何飞碟。 飞碟线路计算请使用 mathf 类。 向下加速度 a 是常数。 飞碟被用户击中,则回收。并按你定义的规则计算分数。飞碟落地(y < c),则自动回收。


第一步需要做的是先把一些万年不变的代码写出来。

// SSDirector.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SSDirector : Object {
    private static SSDirector _instance;
    public SceneController currentSceneController { get; set; }
    public bool running { get; set; }
    public static SSDirector getInstance()
    {
        if (_instance == null)
        {
            _instance = new SSDirector();
        }
        return _instance;
    }
    public int getFPS()
    {
        return Application.targetFrameRate;
    }
    public void setFPS(int fps)
    {
        Application.targetFrameRate = fps;
    }
    public void NextScene()
    {

    }
}
// SceneController.cs
public class SceneController : MonoBehaviour {
    void Awake()
    {
        SSDirector director = SSDirector.getInstance();
        director.setFPS(60);
        director.currentSceneController = this;
        director.currentSceneController.LoadResources();
    }

    public void LoadResources()
    {

    }
}

第二步:回到Unity界面,新建一个圆柱体,命名为Disk,Disk的厚度调整到你认为它看起来像个碟为止,而半径大小则不着急,因为飞碟大小是变化的。
这里写图片描述
鉴于我是做完才写这个博客的,懒得重新截图,就不放图了
然后在上面新增一个脚本DiskData.cs,并将它做成预制,然后将它从场景中删除。


第三步,需要了解:脚本这个东西可以跟其他Component一样被实例化,实例化之后会有对应的GameObject产生。因此为了记录飞碟的属性,我们新建一个脚本DiskData.cs来记录飞碟的各种属性

// DiskData.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Ruler
{
    public Color color;
    public float size;
    public Vector3 position;
    public float speed;
    public Vector3 direction; // 单位向量

    public Ruler(Color color, float size, Vector3 position, float speed, Vector3 direction)
    {
        this.color = color;
        this.size = size;
        this.position = position;
        this.speed = speed;
        this.direction = direction;
    }
}

public class DiskData : MonoBehaviour {
    public Ruler _ruler;
}

Ruler的作用在之后会提到。
在第四步开始前,一定要记住DiskData都挂着一个GameObject,并且可以通过diskdata.gameObject来访问。搞清楚这一点就不会在后面为了选择GameObject还是DiskData作为函数输入输出而烦恼了。


第四步:建立工厂模式
课件给出的伪代码

getDisk(ruler)
    IF (free list has disk) THEN
        a_disk = remove one from list
    ELSE
        a_disk = clone from Prefabs
    ENDIF
    Set DiskData of a_disk with the ruler
    Add a_disk to used list
    Return a_disk
FreeDisk(disk)
    Find disk in used list
    IF (not found) THEN THROW exception
    Move disk from used to free list

于是我的代码和他几乎一模一样

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DiskFactory : MonoBehaviour {
    public DiskData prefab;
    List<DiskData> used = new List<DiskData>();
    List<DiskData> free = new List<DiskData>();

    public DiskData getDisk(Ruler ruler)
    {
        DiskData a_disk;
        if (free.Count > 0)
        {
            a_disk = free[0];
            free.RemoveAt(0);
        }
        else
        {
            a_disk = Instantiate(prefab);
        }
        a_disk.ruler = ruler;
        used.Add(a_disk);
        return a_disk;
    }

    public void freeDisk(DiskData disk)
    {
        free.Add(disk);
        if (!used.Remove(disk))
        {
            throw new System.Exception();
        }
    }
}

Ruler的作用就显现出来了:如果我们要生产一个飞碟,我们需要知道它的所有属性值,这些属性值由场景提供。如果没有Ruler类,那么getDisk的原型是这样的:

public DiskData getDisk(Color color, float size, Vector3 position, float speed, Vector3 direction)

冗长不美观是一个问题,但更严重的是,如果我想增加或者删除飞碟的属性,那么我就要修改getDisk的参数列表,那么会非常麻烦而且容易出错。


第五步:设置Ruler的目的还有另外一个就是方便对disk的属性进行修改

public class DiskData : MonoBehaviour {
    Ruler _ruler;
    public Ruler ruler {
        get
        {
            return _ruler;
        }
        set
        {
            _ruler = value;
            gameObject.GetComponent<Renderer>().material.color = value.color;
            gameObject.transform.localScale = new Vector3(value.size, 0.05f, value.size);
            gameObject.transform.position = value.position;
        }
    }
}

联系到前面DiskFactory的代码:

a_disk.ruler = ruler;

这样就能轻松修改属性。


第六步:运动轨迹
这里的课程还没有教到刚体,因此用坐标变换来实现飞碟的运动。但其实利用刚体感觉更麻烦,而且unity那个施加力的动作至今我都觉得很迷。
我们使用一个方向向量来代表飞碟的方向。这个方向向量是一个单位向量。那么根据相似性,我们可以知道飞碟的初始速度的三个分量分别是初始速度的值乘上方向向量的对应分量的值,例如:
如果方向向量为(x,y,z),速度为a,那么x方向速度为a*x,以此类推。
然后联系到update的机制,update是不断更新的,所以我们可以用现在飞碟的状态,来算出下一时刻飞碟的状态。
在DiskData中增加三个变量:

public float vx, vy, vz;

ruler的set修改为:

public Ruler ruler {
        get
        {
            return _ruler;
        }
        set
        {
            _ruler = value;
            gameObject.GetComponent<Renderer>().material.color = value.color;
            gameObject.transform.localScale = new Vector3(value.size, 0.05f, value.size);
            gameObject.transform.position = value.position;
            vx = value.speed * value.direction.x;
            vy = value.speed * value.direction.y;
            vz = value.speed * value.direction.z;
        }
    }

然后在场景内增加一个runDisk的函数:

void runDisk(DiskData disk)
    {
        float xt1 = disk.gameObject.transform.position.x + disk.vx * Time.deltaTime;
        float zt1 = disk.gameObject.transform.position.z + disk.vz * Time.deltaTime;
        disk.vy -= gravity * Time.deltaTime;
        float yt1 = disk.gameObject.transform.position.y + disk.vy * Time.deltaTime;
        disk.gameObject.transform.position = new Vector3(xt1, yt1, zt1);
    }

上面代码都是简单的运动学公式。


第七步:
程序的所有代码都在下面的附件全部列出来了。现在将sceneController和DiskFactory挂载在一个空物体上,然后将刚才产生的预制,拖进DiskFactory的inspector标签里的prefab变量。就可以运行了。
你可能会发现里面还有很多代码我没说明,其实这些代码并不是关键,要么是它看起来就很容易理解(例如那个单例模式),要么就是有其它方法代替,例如那个ScoreController,其实完全可以不用。


后记:
这个打飞碟游戏其实还不完善。
- 分数直接print在console里
- round升级没有任何效果


附:所有代码
SSDirector.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SSDirector : Object {
    private static SSDirector _instance;
    public SceneController currentSceneController { get; set; }
    public bool running { get; set; }
    public static SSDirector getInstance()
    {
        if (_instance == null)
        {
            _instance = new SSDirector();
        }
        return _instance;
    }
    public int getFPS()
    {
        return Application.targetFrameRate;
    }
    public void setFPS(int fps)
    {
        Application.targetFrameRate = fps;
    }
    public void NextScene()
    {

    }
}

Singleton.cs:(这是单例模式)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

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 thre is none.");
                }
            }
            return instance;
        }
    }
}

DiskData.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Ruler
{
    public Color color;
    public float size;
    public Vector3 position;
    public float speed;
    public Vector3 direction; // 单位向量

    public Ruler(Color color, float size, Vector3 position, float speed, Vector3 direction)
    {
        this.color = color;
        this.size = size;
        this.position = position;
        this.speed = speed;
        this.direction = direction;
    }
}

public class DiskData : MonoBehaviour {
    Ruler _ruler;
    public Ruler ruler {
        get
        {
            return _ruler;
        }
        set
        {
            _ruler = value;
            gameObject.GetComponent<Renderer>().material.color = value.color;
            gameObject.transform.localScale = new Vector3(value.size, 0.05f, value.size);
            gameObject.transform.position = value.position;
            vx = value.speed * value.direction.x;
            vy = value.speed * value.direction.y;
            vz = value.speed * value.direction.z;
        }
    }
    public float vx, vy, vz;
    // Use this for initialization
    void Start () {
        //gameObject.GetComponent<Renderer>().material.color = color;
        //gameObject.transform.position = position;
        //gameObject.transform.localScale = new Vector3(size, 0.05f, size);
    }

    // Update is called once per frame
    void Update () {

    }
}

DiskFactory.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DiskFactory : MonoBehaviour {
    public DiskData prefab;
    List<DiskData> used = new List<DiskData>();
    List<DiskData> free = new List<DiskData>();

    public DiskData getDisk(Ruler ruler)
    {
        DiskData a_disk;
        if (free.Count > 0)
        {
            a_disk = free[0];
            free.RemoveAt(0);
        }
        else
        {
            a_disk = Instantiate(prefab);
        }
        a_disk.ruler = ruler;
        used.Add(a_disk);
        return a_disk;
    }

    public void freeDisk(DiskData disk)
    {
        free.Add(disk);
        if (!used.Remove(disk))
        {
            throw new System.Exception();
        }
    }

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {

    }
}

SceneController.cs:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ScoreController
{
    public int score = 0;
    public void record(DiskData disk)
    {
        score += Mathf.RoundToInt(disk.ruler.color.b +  disk.ruler.color.g + disk.ruler.color.r);
        score += Mathf.RoundToInt(disk.ruler.size);
        score += Mathf.RoundToInt(disk.ruler.speed);
    }
}

public class SceneController : MonoBehaviour {
    List<DiskData> diskList;
    DiskFactory diskFactory;
    ScoreController scoreCtrl;
    int round = 0;
    void Awake()
    {
        SSDirector director = SSDirector.getInstance();
        director.setFPS(60);
        director.currentSceneController = this;
        director.currentSceneController.LoadResources();
    }

    public void LoadResources()
    {
        diskList = new List<DiskData>();
        diskFactory = Singleton<DiskFactory>.Instance;
        scoreCtrl = new ScoreController();
    }

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    float interval = 0;
    public int upperNum;
    int leftNum;
    public Camera cam;
    Color[] colors = { Color.black, Color.blue, Color.cyan, Color.gray, Color.green, Color.magenta, Color.red, Color.white, Color.yellow };
    void Update () {
        interval += Time.deltaTime;
        if (interval >= 2f)
        {
            int shootNum = UnityEngine.Random.Range(1, upperNum/3);
            leftNum -= shootNum;
            if (leftNum <= 0)
            {
                round++;
                leftNum = upperNum;
            }
            for (int i = 0; i < shootNum; i++) {
                Color color = colors[UnityEngine.Random.Range(0, 9)];
                float size = UnityEngine.Random.Range(1f, 3f);
                Vector3 position = new Vector3(UnityEngine.Random.Range(-10f, 10f), 0, 0);
                float speed = UnityEngine.Random.Range(5f, 100f);
                Vector3 direction = new Vector3(
                    UnityEngine.Random.Range(-1000f, 1000f),
                    UnityEngine.Random.Range(0, 1000f),
                    UnityEngine.Random.Range(0, 1000f)
                    );
                direction.Normalize();
                Ruler ruler = new Ruler(color, size, position, speed, direction);
                diskList.Add(diskFactory.getDisk(ruler));
            }
            interval = 0;
        }
        for (int i = 0; i < diskList.Count; i++)
        {
            if (diskList[i].gameObject.transform.position.y < 0)
            {
                recycleDisk(diskList[i]);
            }
        }
        for (int i = 0; i < diskList.Count; i++)
        {
            runDisk(diskList[i]); 
        }
        if (Input.GetButtonDown("Fire1"))
        {
            Vector3 mousePosition = Input.mousePosition;
            Ray ray = cam.ScreenPointToRay(mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit))
            {
                scoreCtrl.record(hit.transform.gameObject.GetComponent<DiskData>());
                recycleDisk(hit.transform.gameObject.GetComponent<DiskData>());
                print(scoreCtrl.score);
            }
        }
    }

    void recycleDisk(DiskData disk)
    {
        disk.gameObject.transform.position = new Vector3(0, 0, -100);
        diskFactory.freeDisk(disk);
        diskList.Remove(disk);
    }
    public float gravity = 9.8f;
    void runDisk(DiskData disk)
    {
        float xt1 = disk.gameObject.transform.position.x + disk.vx * Time.deltaTime;
        float zt1 = disk.gameObject.transform.position.z + disk.vz * Time.deltaTime;
        disk.vy -= gravity * Time.deltaTime;
        float yt1 = disk.gameObject.transform.position.y + disk.vy * Time.deltaTime;
        disk.gameObject.transform.position = new Vector3(xt1, yt1, zt1);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值