Unity基础实践小项目

项目流程:

需求分析

开始界面

选择角色面板

排行榜面板

设置面板

游戏面板

确定退出面板

死亡面板

UML类图

准备工作

1.导入资源

2.创建需要的文件夹

3.创建好面板基类

开始场景

开始界面

1.拼面板

2.写脚本

注意事项:注意先设置NGUI的分辨率大小,注意控件的分辨率自适应,注意 Draw Calls!

设置界面 ------ 拼面板和基础逻辑

1.拼面板

2.逻辑

BegingPael 里打开设置面板

设置界面------音效数据

1.创建音效数据类

2.创建游戏数据管理类

3.SettingPanel 关联逻辑

实现了音效数据的在设置面板中的获取和修改

设置界面------背景音乐对象

1.创建音乐对象和音乐控制脚本

音乐对象

音乐控制脚本

2.设置面板 调用

排行榜界面------排行榜数据

1.排行榜数据类

2.GameDataMgr 中调用

排行榜界面------拼面板

排行榜界面------逻辑处理

1.单个排行榜控件类------RankItem

2.创建排行榜面板类

选择角色界面 ------ 拼面板

选择角色界面------数据准备

1.创建数据集合

2.创建Xml配置文件

3.GameDataMgr 中调用初始化

选择角色界面------逻辑处理

1.创建 ChoosePanel 类

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class ChoosePanel : BasePanel<ChoosePanel>
{
    //各按钮
    public UIButton btnClose;
    public UIButton btnLeft;
    public UIButton btnRight;
    public UIButton btnStart;

    //模型父对象
    public Transform heroPos;

    //下方属性相关对象
    public List<GameObject> hpObjs;
    public List<GameObject> speedObjs;
    public List<GameObject> volumeObjs;

    //当前显示的飞机模型对象
    private GameObject airPlaneObj;

    public override void Init()
    {
        //选择角色后 点击开始 切换场景
        btnStart.onClick.Add(new EventDelegate(() =>
        {
            SceneManager.LoadScene("GameScene");
        }));

        btnLeft.onClick.Add(new EventDelegate(() =>
        {
            //左按钮 减我们设定的索引
            --GameDataMgr.Instance.nowSelHeroIndex;
            //如果 小于最小的索引 直接让其等于 最后一个索引
            if (GameDataMgr.Instance.nowSelHeroIndex < 0)
                GameDataMgr.Instance.nowSelHeroIndex = GameDataMgr.Instance.roleData.roleList.Count - 1;

            ChangeNowHero();
        }));

        btnRight.onClick.Add(new EventDelegate(() =>
        {
            //右按钮 加我们设定的索引
            ++GameDataMgr.Instance.nowSelHeroIndex;
            //如果大于了最大索引 就置0
            if (GameDataMgr.Instance.nowSelHeroIndex > GameDataMgr.Instance.roleData.roleList.Count - 1)
                GameDataMgr.Instance.nowSelHeroIndex = 0;
            ChangeNowHero();
        }));

        btnClose.onClick.Add(new EventDelegate(() =>
        {
            //关闭自己
            HideMe();
            //显示开始面板
            BeginPanel.Instance.ShowMe();
        }));

        HideMe();

    }

    public override void ShowMe()
    {
        base.ShowMe();
        //每次显示的时候都从第一个开始
        GameDataMgr.Instance.nowSelHeroIndex = 0;
        ChangeNowHero();
    }

    public override void HideMe()
    {
        base.HideMe();
        //删除当前模型
        DestroyObj();
    }

    //切换当前的选择
    private void ChangeNowHero()
    {
        //得到当前选择的 玩家游戏数据
        RoleInfo info = GameDataMgr.Instance.GetNowSelHeroInfo();

        //更新模型
        //先删除上一次的飞机模型
        DestroyObj();
        //再创建当前的飞机模型
        airPlaneObj = Instantiate(Resources.Load<GameObject>(info.resName));
        //设置父对象
        airPlaneObj.transform.SetParent(heroPos);
        //设置角度和位置 缩放
        airPlaneObj.transform.localPosition = Vector3.zero;
        airPlaneObj.transform.localRotation = Quaternion.identity;
        airPlaneObj.transform.localScale = Vector3.one * info.scale;
        //修改层级
        airPlaneObj.layer = LayerMask.NameToLayer("UI");

        //更新属性
        for (int i = 0; i < 10; i++)
        {
            hpObjs[i].SetActive(i < info.hp);
            speedObjs[i].SetActive(i < info.speed);
            volumeObjs[i].SetActive(i < info.volume);
        }

    }

    /// <summary>
    /// 用于删除上一次显示的模型对象
    /// </summary>
    private void DestroyObj()
    {
        if(airPlaneObj != null)
        {
            //移除场景的模型
            Destroy(airPlaneObj);
            //置空
            airPlaneObj = null;
        }
    }

    private float time;
    //是否鼠标选中 模型
    private bool isSel;
    // Update is called once per frame
    void Update()
    {
        //让飞机 上下浮动
        time += Time.deltaTime;
        heroPos.Translate(Vector3.up * Mathf.Sin(time) * 0.0001f, Space.World);

        //射线检测 让飞机 可以左右转动
        if (Input.GetMouseButtonDown(0))
        {
            //如果点击了 UI层碰撞器 认为需要开始 拖动 飞机了
            if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition),
                                1000,
                                1 << LayerMask.NameToLayer("UI")))
            {
                isSel = true;
            }
        }

        //抬起 取消选中
        if (Input.GetMouseButtonUp(0))
            isSel = false;

        //旋转对象
        if (Input.GetMouseButton(0) && isSel)
        {
            heroPos.rotation *= Quaternion.AngleAxis(Input.GetAxis("Mouse X") * 20, Vector3.up);
        }

    }
}

2.GameDataMgr 中逻辑关联

3.BeginPanel 中显隐设置

注意:模型旋转时,发射射线检测的摄像机要是主摄像机,或者就自己指定摄像机发射。

游戏场景

游戏界面

1.拼面板

2.逻辑面板

确定退出界面

1.拼面板

2.写逻辑

3.调用

GamePanel 中调用

结束界面

1.拼面板

2.写逻辑

游戏主逻辑------主玩家相关

1.加一个相加来渲染

一个专门渲染特效

一个专门渲染UI

一个专门渲染其他

2.玩家逻辑

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerObject : MonoBehaviour
{
    //血量
    public int nowHp;
    public int maxHp;

    //速度
    public int speed;
    //旋转速度
    public int roundSpeed;
    //目标四元数角度
    private Quaternion targetQ;

    //是否死亡
    public bool isDead;

    //当前世界坐标系转屏幕上的点
    private Vector3 nowPos;
    //上一次玩家的位置 就是在位移前 玩家的位置
    private Vector3 frontPos;
    
    public void Dead()
    {
        isDead = true;
        //显示游戏结束面板
        GameOverPanel.Instance.ShowMe();
    }

    public void Wound()
    {
        if (isDead)
            return;
        //减血
        this.nowHp -= 1;
        //更新游戏面板上的血量显示
        GamePanel.Instance.ChangeHp(this.nowHp);
        //是否死亡了
        if (this.nowHp <= 0)
            this.Dead();
    }

    private float hValue;
    private float vValue;
    // Update is called once per frame
    void Update()
    {
        //如果死亡了 就没有必要移动了
        if (isDead)
            return;

        //移动 旋转逻辑

        //旋转
        hValue = Input.GetAxisRaw("Horizontal");
        vValue = Input.GetAxisRaw("Vertical");
        //如果没有按我们的AD键 那么目标角度 就是(0,0,0)度
        if (hValue == 0)
            targetQ = Quaternion.identity;
        //如果按AD键 就是(0,0,20)或者 (0,0,-20) 根据按的左右决定
        else
            targetQ = hValue < 0 ? Quaternion.AngleAxis(20, Vector3.forward) : Quaternion.AngleAxis(-20, Vector3.forward);

        //让飞机朝着 这个目标四元数 去旋转
        this.transform.rotation = Quaternion.Slerp(this.transform.rotation, targetQ, roundSpeed * Time.deltaTime);

        //在位移之前 记录 之前的位置
        frontPos = this.transform.position;

        //移动
        this.transform.Translate(Vector3.forward * vValue * speed * Time.deltaTime);
        this.transform.Translate(Vector3.right * hValue * speed * Time.deltaTime, Space.World);

        //进行极限判断
        nowPos = Camera.main.WorldToScreenPoint(this.transform.position);
        //左右 溢出判断
        if(nowPos.x < 0 || nowPos.x >= Screen.width)
        {
            this.transform.position = new Vector3(frontPos.x, this.transform.position.y, this.transform.position.z);
        }
        //上下 溢出判断
        if(nowPos.y < 0 || nowPos.y >= Screen.height)
        {
            this.transform.position = new Vector3(this.transform.position.x, this.transform.position.y, frontPos.z);
        }

    }
}

游戏主逻辑------子弹相关

数据准备

1.制作子弹 和 子弹爆炸特效

2.子弹数据类

3.Xml文件配置

  1. GameDataMgr 管理初始化

写逻辑

1.创建 BullteObject 脚本

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BulletObject : MonoBehaviour
{
    //子弹使用的数据
    private BulletInfo info;

    //用于曲线移动的 计时变量
    private float time;

    

    //初始化子弹数据的方法
    public void InitInfo(BulletInfo info)
    {
        this.info = info;
        //根据生命周期函数 决定自己什么时候 延迟移除
        //Destroy(this.gameObject, info.lifeTime);
        //两种方式来移除 Destroy和延迟函数 看看哪种方法更合适
        Invoke("DealyDestroy", info.lifeTime);
    }

    private void DealyDestroy()
    {
        Destroy(this.gameObject);
    }

    //销毁场景上的子弹
    public void Dead()
    {
        //创建死亡特效
        GameObject eff = Instantiate(Resources.Load<GameObject>(this.info.deadEffRes));
        //设置特效的位置 创建在当前子弹的位置
        eff.transform.position = this.transform.position;
        //1秒后延迟移除特效
        Destroy(eff, 1f);

        //销毁子弹对象
        Destroy(this.gameObject);
    }

    //和对象碰撞时 (触发)
    private void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.CompareTag("Player"))
        {
            //得到玩家脚本
            PlayerObject obj = other.GetComponent<PlayerObject>();
            //玩家受伤减血
            obj.Wound();

            //销毁自己 就是直接调用 Dead方法
            Dead();
        }
    }

    // Update is called once per frame
    void Update()
    {
        //所有移动的共同特点 都是朝自己的面朝向动
        this.transform.Translate(Vector3.forward * info.forwardSpeed * Time.deltaTime);
        //接着再来处理 其他的移动逻辑
        //1 代表 只朝自己面朝向移动 直线移动
        //2 代表 曲线运动
        //3 代表 右抛物线
        //4 代表 左抛物线
        //5 代表 跟踪移动
        switch (info.type)
        {
            case 2:
                time += Time.deltaTime;
                //sin里面值变化的快慢 决定了 左右变化的频率
                //乘以的速度 变化的大小 决定了 左右位移的多少
                this.transform.Translate(Vector3.right * info.rightSpeed * Time.deltaTime * Mathf.Sin(time * info.roundSpeed));
                break;
            case 3:
                //右抛物线 无非 就是 去改变 旋转角度
                this.transform.rotation *= Quaternion.AngleAxis(info.roundSpeed * Time.deltaTime, Vector3.up);
                break;
            case 4:
                //左抛物线 无非 就是 去改变 旋转角度
                this.transform.rotation *= Quaternion.AngleAxis(-info.roundSpeed * Time.deltaTime, Vector3.up);
                break;
            case 5:
                //跟踪移动 不停的计算 玩家和我之间的方向向量 得到四元数 然后自己的角度 不停的 变化为这个目标四元数
                this.transform.rotation = Quaternion.Slerp(this.transform.rotation,
                                                            Quaternion.LookRotation(PlayerObject.Instance.transform.position - this.transform.position), info.roundSpeed * Time.deltaTime);
                break;
        }

    }
}

2.测试

游戏主逻辑------开火点相关

数据准备

1.创建开火点数据 ------ FireData

2.配置Xml文件

  1. GameDataMgr 中初始化

逻辑处理一

主要实现了绘制开火点位置

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 表示 开火点位置的 类型
/// </summary>
public enum E_Pos_Type
{
    TopLeft,
    Top,
    TopRight,

    Left,
    Right,

    BottonLeft,
    Botton,
    BottonRight,
}

public class FireObject : MonoBehaviour
{
    public E_Pos_Type type;

    //表示屏幕上的点
    private Vector3 screenPos;
    //初始发射子弹的方向 主要用于作为散弹的初始方向 用于计算
    private Vector3 initDir;

    // Update is called once per frame
    void Update()
    {
        //用于测试玩家转屏幕坐标后 横截面的 z轴值
        //print(Camera.main.WorldToScreenPoint(PlayerObject.Instance.transform.position));
        //更新 开火点位置 达到分辨率自适应
        UpdatePos();

    }

    //根据点的类型 来更新它的位置
    private void UpdatePos()
    {
        //这里设置z轴 是为了和主玩家位置转屏幕坐标后的 z位置一样 目的是 让点和玩家 所在的 横截面是一致的
        screenPos.z = 150.6f;
        switch (type)
        {
            case E_Pos_Type.TopLeft:
                screenPos.x = 0;
                screenPos.y = Screen.height;

                initDir = Vector3.right;
                break;
            case E_Pos_Type.Top:
                screenPos.x = Screen.width / 2;
                screenPos.y = Screen.height;

                initDir = Vector3.right;
                break;
            case E_Pos_Type.TopRight:
                screenPos.x = Screen.width;
                screenPos.y = Screen.height;

                initDir = Vector3.left;
                break;
            case E_Pos_Type.Left:
                screenPos.x = 0;
                screenPos.y = Screen.height / 2;

                initDir = Vector3.up;
                break;
            case E_Pos_Type.Right:
                screenPos.x = Screen.width;
                screenPos.y = Screen.height / 2;

                initDir = Vector3.up;
                break;
            case E_Pos_Type.BottonLeft:
                screenPos.x = 0;
                screenPos.y = 0;

                initDir = Vector3.right;
                break;
            case E_Pos_Type.Botton:
                screenPos.x = Screen.width / 2;
                screenPos.y = 0;

                initDir = Vector3.right;
                break;
            case E_Pos_Type.BottonRight:
                screenPos.x = Screen.width;
                screenPos.y = 0;

                initDir = Vector3.left;
                break;
        }

        //再把屏幕点 转换为 世界坐标点 那得到的 就是我们想要的坐标点
        this.transform.position = Camera.main.ScreenToWorldPoint(screenPos);
    }
}

逻辑处理二

1.实现了重置开火点数据 和 检测开火逻辑 (代码量较多 要好好了解)

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 表示 开火点位置的 类型
/// </summary>
public enum E_Pos_Type
{
    TopLeft,
    Top,
    TopRight,

    Left,
    Right,

    BottonLeft,
    Botton,
    BottonRight,
}

public class FireObject : MonoBehaviour
{
    public E_Pos_Type type;

    //表示屏幕上的点
    private Vector3 screenPos;
    //初始发射子弹的方向 主要用于作为散弹的初始方向 用于计算
    private Vector3 initDir;

    //当前开火点的数据信息
    private FireInfo fireInfo;
    private int nowNum;
    private float nowCD;
    private float nowDelay;
    //当前组开火点 使用的子弹信息
    private BulletInfo nowBulletInfo;

    //散弹时 每颗子弹的间隔角度
    private float changeAngle;

    //用于发射散弹时 记录上一次的方向
    private Vector3 nowDir;

    // Update is called once per frame
    void Update()
    {
        //用于测试玩家转屏幕坐标后 横截面的 z轴值
        //print(Camera.main.WorldToScreenPoint(PlayerObject.Instance.transform.position));
        //更新 开火点位置 达到分辨率自适应
        UpdatePos();
        //每次 都检测 是否需要 重置 开火点数据
        ResetFireInfo();
        //发射子弹
        UpdateFire();
    }

    //根据点的类型 来更新它的位置
    private void UpdatePos()
    {
        //这里设置z轴 是为了和主玩家位置转屏幕坐标后的 z位置一样 目的是 让点和玩家 所在的 横截面是一致的
        screenPos.z = 150.6f;
        switch (type)
        {
            case E_Pos_Type.TopLeft:
                screenPos.x = 0;
                screenPos.y = Screen.height;

                initDir = Vector3.right;
                break;
            case E_Pos_Type.Top:
                screenPos.x = Screen.width / 2;
                screenPos.y = Screen.height;

                initDir = Vector3.right;
                break;
            case E_Pos_Type.TopRight:
                screenPos.x = Screen.width;
                screenPos.y = Screen.height;

                initDir = Vector3.left;
                break;
            case E_Pos_Type.Left:
                screenPos.x = 0;
                screenPos.y = Screen.height / 2;

                initDir = Vector3.up;
                break;
            case E_Pos_Type.Right:
                screenPos.x = Screen.width;
                screenPos.y = Screen.height / 2;

                initDir = Vector3.up;
                break;
            case E_Pos_Type.BottonLeft:
                screenPos.x = 0;
                screenPos.y = 0;

                initDir = Vector3.right;
                break;
            case E_Pos_Type.Botton:
                screenPos.x = Screen.width / 2;
                screenPos.y = 0;

                initDir = Vector3.right;
                break;
            case E_Pos_Type.BottonRight:
                screenPos.x = Screen.width;
                screenPos.y = 0;

                initDir = Vector3.left;
                break;
        }

        //再把屏幕点 转换为 世界坐标点 那得到的 就是我们想要的坐标点
        this.transform.position = Camera.main.ScreenToWorldPoint(screenPos);
    }

    //重置当前要发射的炮台数据
    private void ResetFireInfo()
    {
        //自己定一个规则 只有当cd和数量都为0时 才认为需要重新获取 发射点数据
        if (nowCD != 0 && nowNum != 0)
            return;
        //组间休息时间判断
        if (fireInfo != null)
        {
            nowDelay -= Time.deltaTime;
            //还在组间休息
            if (nowDelay > 0)
                return;
        }

        //从数据中随机取出一条 来按照规则 发射子弹
        List<FireInfo> list = GameDataMgr.Instance.fireData.fireInfoList;
        fireInfo = list[Random.Range(0, list.Count)];
        //我们不能直接改变数据当中的内容 我们应该拿变量 临时存储下来 这样就不会影响我们数据本身
        nowNum = fireInfo.num;
        nowCD = fireInfo.cd;
        nowDelay = fireInfo.delay;

        //通过 开火点数据 取出 当前要使用的子弹数据信息
        //得到开始id 和 结束id 用于随机取子弹信息
        string[] strs = fireInfo.ids.Split(',');
        int beginID = int.Parse(strs[0]);
        int endID = int.Parse(strs[1]);
        int randomBulletID = Random.Range(beginID, endID + 1);
        nowBulletInfo = GameDataMgr.Instance.bulletData.bulletInfoList[randomBulletID - 1];

        //如果是散弹 就需要计算 间隔角度
        if (fireInfo.type == 2)
        {
            switch (type)
            {
                case E_Pos_Type.TopLeft:
                case E_Pos_Type.TopRight:
                case E_Pos_Type.BottonLeft:
                case E_Pos_Type.BottonRight:
                    changeAngle = 90f / (nowNum + 1);
                    break;
                case E_Pos_Type.Top:
                case E_Pos_Type.Left:
                case E_Pos_Type.Right:
                case E_Pos_Type.Botton:
                    changeAngle = 180f / (nowNum + 1);
                    break;
            }
        }

    }

    //检测开火
    private void UpdateFire()
    {
        //当前状态 是不需要发射子弹的
        if (nowCD == 0 && nowNum == 0)
            return;

        //cd更新
        nowCD -= Time.deltaTime;
        if (nowCD > 0)
            return;

        GameObject bullet;
        BulletObject bulletObj;

        switch (fireInfo.type)
        {
            //一颗一颗的发射子弹 朝向玩家
            case 1:
                //动态创建 子弹对象
                bullet = Instantiate(Resources.Load<GameObject>(nowBulletInfo.resName));
                //动态添加 子弹脚本
                bulletObj = bullet.AddComponent<BulletObject>();
                //把当前的子弹数据传入子弹脚本 进行初始化
                bulletObj.InitInfo(nowBulletInfo);

                //设置子弹的位置 和朝向
                bullet.transform.position = this.transform.position;
                bullet.transform.rotation = Quaternion.LookRotation(PlayerObject.Instance.transform.position - bullet.transform.position);

                //表示已经发射一颗子弹
                --nowNum;
                //重置cd
                nowCD = nowNum == 0 ? 0 : fireInfo.cd;


                break;
                //发射散弹
            case 2:
                //无CD 一瞬间 发射所有的散弹
                if(nowCD == 0)
                {
                    for (int i = 0; i < nowNum; i++)
                    {
                        //动态创建 子弹对象
                        bullet = Instantiate(Resources.Load<GameObject>(nowBulletInfo.resName));
                        //动态添加 子弹脚本
                        bulletObj = bullet.AddComponent<BulletObject>();
                        //把当前的子弹数据传入子弹脚本 进行初始化
                        bulletObj.InitInfo(nowBulletInfo);

                        //设置子弹的位置 和朝向
                        bullet.transform.position = this.transform.position;
                        //每次都会旋转一个角度 得到一个新的方向
                        nowDir = Quaternion.AngleAxis(changeAngle * i, Vector3.up) * initDir;
                        bullet.transform.rotation = Quaternion.LookRotation(nowDir);
                    }

                    //因为是瞬间创建完所有子弹 所以 重置数据
                    nowCD = nowNum = 0;
                }
                else
                {
                    //动态创建 子弹对象
                    bullet = Instantiate(Resources.Load<GameObject>(nowBulletInfo.resName));
                    //动态添加 子弹脚本
                    bulletObj = bullet.AddComponent<BulletObject>();
                    //把当前的子弹数据传入子弹脚本 进行初始化
                    bulletObj.InitInfo(nowBulletInfo);

                    //设置子弹的位置 和朝向
                    bullet.transform.position = this.transform.position;
                    //每次都会旋转一个角度 得到一个新的方向
                    nowDir = Quaternion.AngleAxis(changeAngle * (fireInfo.num - nowNum), Vector3.up) * initDir;
                    bullet.transform.rotation = Quaternion.LookRotation(nowDir);

                    //表示已经发射一颗子弹
                    --nowNum;
                    //重置cd
                    nowCD = nowNum == 0 ? 0 : fireInfo.cd;
                }
                break;
        }
    }

}

2.串联 开始面板和游戏面板

创建 Main 类 用于创建玩家飞机

逻辑功能完善

1.子弹自动销毁

2.不隐藏光标

3.射线检测销毁子弹

游戏展示:

暂时无法上传!

总结

相关推荐
dangoxiba2 小时前
[Unity Demo]从零开始制作空洞骑士Hollow Knight第十三集:制作小骑士的接触地刺复活机制以及完善地图的可交互对象
游戏·unity·visualstudio·c#·游戏引擎
先生沉默先20 小时前
使用Materialize制作unity的贴图,Materialize的简单教程,Materialize学习日志
学习·unity·贴图
十画_8241 天前
Visual Studio 小技巧记录
unity·visual studio
red_redemption1 天前
cpp,git,unity学习
git·unity·游戏引擎
tealcwu1 天前
【Unity踩坑】Unity更新Google Play结算库
unity·游戏引擎
先生沉默先1 天前
unity 默认渲染管线材质球的材质通道,材质球的材质通道
unity·游戏引擎·材质
白鹭float.1 天前
【Unity AI】基于 WebSocket 和 讯飞星火大模型
人工智能·websocket·unity
一个程序员(●—●)1 天前
Unity各个操作功能+基本游戏物体创建与编辑+Unity场景概念及文件导入导出
unity·游戏引擎
Avalon7122 天前
UniVue大版本更新:UniVue2.0.0-preview
游戏·ui·unity·c#
萌萌的提莫队长2 天前
Unity XR 环境检测
unity·xr