Unity太空避障Demo总结

太空避障:主要是实现飞机躲避子弹
面板基类、音乐类、排行榜类、json等等都和上一篇Unity坦克迷宫Demo总结一样,太空避障主要是对四元数的练习和使用
1.选择飞机面板
(1)通过左右按钮对显示的模型进行切换
(2)通过点击鼠标左键可以实现对飞机的拖动 观察飞机的整体
知识点
(1)四元数 * 四元数:会得到一个新的四元数,大多数用于旋转量相乘,相当于旋转(这个旋转的坐标是物体自身的坐标系)
(2)四元数 * 向量:会得到一个新的向量,对一个向量进行一定旋转,相当于旋转向量

public class SelectPanel : BasePanel<SelectPanel>
{
    public Button btnRight;
    public Button btnLeft;
    public Button btnStart;
    public Button btnClose;

    //标识当前已经创建的飞机 当切换时要删除该飞机
    private GameObject nowAirObj;
    //飞机的父物体位置
    public Transform airPos;

    //飞机的属性列表 通过隐藏和显示GameObject实现
    public GameObject[] hpList;
    public GameObject[] speedList;
    public GameObject[] voluemeList;

    public override void Init()
    {
        //在GameDataManager中保存了当前选中的飞机id 默认从0开始 点右按钮++ 左按钮-- 还有对边界的判断
        btnRight.onClick.AddListener(() =>
        {
            ++GameDataManager.Instance.nowSelectAir;
            if(GameDataManager.Instance.nowSelectAir > GameDataManager.Instance.airData.Count - 1)
                GameDataManager.Instance.nowSelectAir = 0;
            ChangAir();
        });
        btnLeft.onClick.AddListener(() =>
        {
            --GameDataManager.Instance.nowSelectAir;
            if (GameDataManager.Instance.nowSelectAir < 0)
                GameDataManager.Instance.nowSelectAir = GameDataManager.Instance.airData.Count - 1;
            ChangAir();
        });
        btnStart.onClick.AddListener(() =>
        {
            SceneManager.LoadScene("GameScene");
        });
        btnClose.onClick.AddListener(() =>
        {
            BeginPanel.Instance.ShowPanel();
            HidePanel();
        });
        HidePanel();
    }

    //重写了隐藏和显示方法 这样就可以同时实现一些逻辑
    public override void ShowPanel()
    {
        base.ShowPanel();
        GameDataManager.Instance.nowSelectAir = 0;
        ChangAir();
    }

    public override void HidePanel()
    {
        base.HidePanel();
        DestroyObj();
    }
    public void ChangAir()
    {
        //删除上个飞机 创建新飞机 并记录
        DestroyObj();
        AirData airData = GameDataManager.Instance.airData[GameDataManager.Instance.nowSelectAir];
        nowAirObj = Instantiate(Resources.Load<GameObject>(airData.res));
        nowAirObj.transform.SetParent(airPos.transform, false);

        for(int i = 0; i < hpList.Length;i++)
        {
            hpList[i].SetActive(i < airData.hp);
            speedList[i].SetActive(i < airData.speed);
            voluemeList[i].SetActive(i < airData.volume);
        }
    }

    public void DestroyObj()
    {
        if(nowAirObj != null)
        {
            Destroy(nowAirObj);
        }
    }

    private float time;
    //点中可以进行拖动
    public bool isSelect;
    private void Update()
    {
        //让飞机上下缓缓飞(展示作用)通过sin函数 乘内测的数代表飞的频率 乘外侧的数代表飞的位移
        //基于世界坐标 飞机的展示面会倾斜 基于自己坐标是错的
        time += Time.deltaTime;
        this.airPos.Translate(Vector3.up * Mathf.Sin(time) * 0.001f,Space.World);

        //鼠标左键实现拖动 通过射线检测 将飞机的旋转角度以y旋转 度数为Mouse X * 10度
        if(Input.GetMouseButtonDown(0))
        {
            if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), 1000, 1 << LayerMask.NameToLayer("Air")))
            {
                isSelect = true;
            }
        }
        if(Input.GetMouseButtonUp(0))
        {
            isSelect = false;
        }
        if(isSelect && Input.GetMouseButton(0))
        {
            airPos.rotation *= Quaternion.AngleAxis(Input.GetAxis("Mouse X") * 10,Vector3.up);
        }
    }
}

2.玩家类(飞机超出屏幕问题未解决)
(1)当飞机左右移动时,需要有一定的倾斜,停止时恢复
(2)玩家类需要受伤 死亡 移动方法
知识点
(1)射线检测时销毁碰撞体(子弹)

public class PlayerObj : MonoBehaviour
{
    //写成一个单例类
    private static PlayerObj instance;
    public static PlayerObj Instance => instance;

    //maxHp代表最大血量 nowHp代表变量
    public int nowHp;
    public int maxHp;

    public int moveSpeed;
    public int rotaSpeed;

    //是否死亡
    public bool isDead;

    //目标四元数旋转角度
    Quaternion targetQ;

    private float hValue;
    private float vValue;

    //世界坐标的点 没有去屏幕外的点
    private Vector3 frontPos = new Vector3(-41,0,23);
    private Vector3 frontPos2 = new Vector3(41, 0, -23);
    //世界坐标转换为屏幕坐标的点
    private Vector3 nowPos;

    private void Awake()
    {
        instance = this;
    }
    void Update()
    {
        if (isDead)
            return;

        //GetAxisRaw 不是渐变 -1 1两个数
        //ad
        hValue = Input.GetAxisRaw("Horizontal");
        //ws
        vValue = Input.GetAxisRaw("Vertical");

        //为0时说明静止
        if (hValue == 0)
            targetQ = Quaternion.identity;
        else
            //不为0时 按左键值会小于0 右键值会大于0 决定一下旋转的方向
            //可以得到一个目标的四元数
            targetQ = hValue < 0 ? Quaternion.AngleAxis(20, Vector3.forward) : Quaternion.AngleAxis(-20, Vector3.forward);

        //趋近于目标四元数
        this.transform.rotation = Quaternion.Slerp(this.transform.rotation, targetQ,Time.deltaTime * rotaSpeed);
        //正常移动
        this.transform.Translate(Vector3.forward * vValue * Time.deltaTime * moveSpeed);
        //左右移动不能使用自身坐标系 越动越会往下掉 要使用世界
        this.transform.Translate(Vector3.right * hValue * Time.deltaTime * moveSpeed,Space.World);
        //当前坐标的屏幕坐标点
        this.nowPos = Camera.main.WorldToScreenPoint(this.transform.position);
        //******************临时判断屏幕边缘的逻辑 当分辨率变化时会出错 暂未解决******************
        if (nowPos.x <= 0)
        {
            //只管理x 不管理yz
            this.transform.position = new Vector3(frontPos.x, this.transform.position.y, this.transform.position.z);
        }
        if (nowPos.y <= 0)
        {
            this.transform.position = new Vector3(this.transform.position.x, this.transform.position.y, frontPos2.z);
        }
        if(nowPos.x >= Screen.width)
        {
            this.transform.position = new Vector3(frontPos2.x, this.transform.position.y, this.transform.position.z);
        }
        if (nowPos.y >= Screen.height)
        {
            this.transform.position = new Vector3(this.transform.position.x, this.transform.position.y, frontPos.z);
        }

        //获取碰到的对象的信息
        RaycastHit hit;
        if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit, 1000, 1 << LayerMask.NameToLayer("Bullet")))
        {
            //通过hit可以得到他身上所有的信息
            BulletObj bullet = hit.transform.GetComponent<BulletObj>();
            bullet.Dead();
        }
    }
    public void Wound()
    {
        this.nowHp -= 1;
        Debug.Log("当前血量" + nowHp);
        GamePanel.Instance.ChangHp(nowHp);
        if(nowHp <= 0)
        {
            nowHp = 0;
            Dead();
        }
    }
    public void Dead()
    {
        isDead = true;
        EndPanel.Instance.ShowPanel();
    }
}

3.子弹类
(1)子弹的初始化数据 移动方式 生命周期 销毁以及碰撞检测

public class BulletObj : MonoBehaviour
{
    private BulletData data;

    private float time;

    /// <summary>
    /// 一个初始化方法 创建子弹时调用 传入目标数据
    /// </summary>
    /// <param name="id"></param>
    public void Init(int id)
    {
        data = GameDataManager.Instance.bulletDatas[id - 1];
        //生命周期 到一定时间自动销毁子弹
        Invoke("DelayDestroy", data.destroyTime);
    }
    //startPos代表起始位置 下面做四元数匀速运动用的
    private void Awake()
    {
        startPos = this.transform;
    }
    //销毁子弹 创建特效
    public void Dead()
    {
        GameObject effObj = Instantiate(Resources.Load<GameObject>(data.effRes));
        effObj.transform.position = this.transform.position;
        Destroy(effObj,0.5f);
        Destroy(this.gameObject);
    }

    //碰撞检测
    public void OnTriggerEnter(Collider other)
    {
        if(other.CompareTag("Player"))
        {
            if(!PlayerObj.Instance.isDead)
            {
                PlayerObj obj = other.GetComponent<PlayerObj>();
                obj.Wound();
                Dead();
            }
            else
            {
                this.Dead();
            }
        }
    }

    //不确定子弹会不会提前被移除掉 所以使用延迟函数 当子弹已经被移除的时候 就不会执行了
    public void DelayDestroy()
    {
        GameObject effObj = Instantiate(Resources.Load<GameObject>(data.effRes));
        effObj.transform.position = this.transform.position;
        Destroy(effObj, 0.5f);
        Destroy(this.gameObject);
    }
    //时间标识 做匀速运动用
    private float uniform;
    private Transform startPos;
    // Update is called once per frame
    void Update()
    {
        //共同的特征 面朝向移动
        this.transform.Translate(Vector3.forward * data.forwardSpeed * Time.deltaTime);
        //1 直线运动
        //2 曲线运动
        //3 左抛物线
        //4 右抛物线
        //5 跟踪
        switch (data.type)
        {
            case 2:
            //sin里面的值 决定 左右变化的频率
            //sin后面的值 决定 左右位移的多少
                time += Time.deltaTime;
                this.transform.Translate(Vector3.right * Mathf.Sin(time * data.rightSpeed) * data.rotaSpeed * Time.deltaTime);
                break;
            case 3:
                this.transform.rotation *= Quaternion.AngleAxis(-data.rotaSpeed * Time.deltaTime,Vector3.up);
                break;
            case 4:
                this.transform.rotation *= Quaternion.AngleAxis(data.rotaSpeed * Time.deltaTime, Vector3.up);
                break;
            case 5:
                //先快后慢 出现问题 "子弹无线接近目标旋转位置过远" 那要看一下是不是子弹的位置没有归0
                //这里使用匀速
                uniform += Time.deltaTime;
                Quaternion q = Quaternion.LookRotation(PlayerObj.Instance.transform.position - this.transform.position);
                this.transform.rotation = Quaternion.Slerp(this.startPos.rotation,q, uniform * data.rotaSpeed);
                break;
        }

    }
}

4.炮口点
(1)炮口的位置为上方三点 中间左右两点 下方三点
(2)炮口的初始角度(为了方便开火点四元数的计算)
(3)上下左右四个点的旋转角度是180度,四个角旋转角度都是90度,这样子弹才能合理地发射在屏幕内

public enum Type_PointType
{
    topLeft,
    topRight,
    topCenter,
    Left,
    Right,
    bottomLeft,
    bottomRight,
    bottomCenter
}
public class PointObj : MonoBehaviour
{
    public Type_PointType type = new Type_PointType();

    private Vector3 pos;

    //炮口的初始方向
    private Vector3 initPos;

    //炮口去拿到配置表中的数据 方便使用
    private int num;
    private float offset;
    private float delay;

    //全部的子弹数据
    List<BulletData> bullets;
    //单个开火数据
    public FireData fire;
    //单个子弹数据
    public BulletData bullet;
    // Update is called once per frame
    void Update()
    {
        //初始化各个炮口的位置
        UpdatePos();
        //每次发射子弹的随机数 以及旋转角度的设置
        Reset();
        //发射子弹
        Fire();
    }

    public void UpdatePos()
    {
        //我们需要找到在z这一层的切面 因为其他子弹也需要在这一层
        //先根据玩家的世界坐标找到屏幕坐标的z是多少
        //然后设置世界坐标的z给pos
        //最后用屏幕转为世界坐标 让子弹出现的位置都在这一层即可
        pos.z = 40;
        switch(type)
        {
            case Type_PointType.topLeft:
                pos.x = 0;
                pos.y = Screen.height;
                initPos = Vector3.right;
                break;
            case Type_PointType.topRight:
                pos.x = Screen.width;
                pos.y = Screen.height;
                initPos = Vector3.left;
                break;
            case Type_PointType.topCenter:
                pos.x = Screen.width/2;
                pos.y = Screen.height;
                initPos = Vector3.right;
                break;
            case Type_PointType.Left:
                pos.x = 0;
                pos.y = Screen.height/2;
                initPos = Vector3.right;
                break;
            case Type_PointType.Right:
                pos.x = Screen.width;
                pos.y = Screen.height/2;
                initPos = Vector3.left;
                break;
            case Type_PointType.bottomLeft:
                pos.x = 0;
                pos.y = 0;
                initPos = Vector3.right;
                break;
            case Type_PointType.bottomRight:
                pos.x = Screen.width;
                pos.y = 0;
                initPos = Vector3.left;
                break;
            case Type_PointType.bottomCenter:
                pos.x = Screen.width/2;
                pos.y = 0;
                initPos = Vector3.right;
                break;
        }
        this.transform.position = Camera.main.ScreenToWorldPoint(pos);
    }
    //旋转的角度
    private float angle;
    /// <summary>
    /// 
    /// 随机数 将子弹的四种移动方式和开火的两种方式随机排列组合 想实现的功能看个人
    /// bullet 四种类型1.直线 2.曲线 3.左抛物线 4.右抛物线 5.跟踪  
    /// fire 两种类型1.连发 2.散弹
    /// </summary>
    public void Reset()
    {
        //终止条件的判断 如果子弹间隔时间和数量没有发完 不能从新随机
        if (offset != 0 && num != 0)
            return;
        if(fire != null)
        {
            //delay代表每组子弹的间隔时间
            delay -= Time.deltaTime;
            if(delay > 0)
            {
                return;
            }
        }
        //拿到全部的开火数据 随机选择开火方式
        List<FireData> fires = GameDataManager.Instance.fireData;
        fire = fires[Random.Range(0, fires.Count)];
        //本地拿到初始值
        this.num = fire.num;
        this.offset = fire.offset;
        this.delay = fire.delay;
        //拿到全部的子弹数据 随机选择子弹移动方式
        bullets = GameDataManager.Instance.bulletDatas;
        bullet = bullets[Random.Range(0, bullets.Count)];

        //1.左上角和右下角发射右抛物线
        //2.左下角和右上角发射左抛物线
        //3.type = 3 是左抛物线 type = 4 是右抛物线
        if(fire.type == 2)
        {
            //2类型是散弹 需要随机角度
            switch (type)
            {
                case Type_PointType.topLeft:
                case Type_PointType.bottomLeft:
                case Type_PointType.topRight:
                case Type_PointType.bottomRight:
                    angle = 90 / num;
                    break;

                case Type_PointType.topCenter:
                case Type_PointType.Left:
                case Type_PointType.Right:
                case Type_PointType.bottomCenter:
                    angle = 180 / num;
                    break;
            }
        }
    }
    private Vector3 newRot;
    /// <summary>
    /// 开火 个人决定开火口能发射怎样的子弹
    /// 核心点:根据不同的开火方式 去创建子弹 以及子弹特效 子弹的位置 子弹的旋转角度
    /// </summary>
    public void Fire()
    {
        if (type == Type_PointType.topLeft || type == Type_PointType.bottomRight)
        {
            bullet.type = 4;
        }
        if (type == Type_PointType.topRight || type == Type_PointType.bottomLeft)
        {
            bullet.type = 3;
        }
        //间隔时间和数量归0 不会进入循环
        if (offset == 0 && num == 0)
            return;
        //每个子弹的判断放到最前面
        offset -= Time.deltaTime;
        //当间隔时间大于0 还在等待期 
        if (offset > 0)
            return;
        GameObject go;
        switch (fire.type)
        {
            case 1:
                go = Instantiate(Resources.Load<GameObject>(bullet.bulletRes));
                go.GetComponent<BulletObj>().Init(bullet.id);
                go.transform.position = this.transform.position;
                go.transform.rotation = Quaternion.LookRotation(PlayerObj.Instance.transform.position - this.transform.position);
                //子弹减少
                --num;
                //当子弹发射完成后 间隔时间归0 就不会再次进入循环 没发射完就重置间隔时间
                offset = num == 0 ? 0 : fire.offset;
                break;
            case 2:
                //散弹不能发射曲线子弹
                if (bullet.type != 2)
                {
                    for (int i = 0; i < num; i++)
                    {
                        go = Instantiate(Resources.Load<GameObject>(bullet.bulletRes));
                        go.GetComponent<BulletObj>().Init(bullet.id);
                        go.transform.position = this.transform.position;
                        //四元数乘向量 得到新的向量
                        //每次旋转i * 20 
                        newRot = Quaternion.AngleAxis(i * angle, Vector3.up) * initPos;
                        //新的方向给四元数 最后赋值给角度即可
                        go.transform.rotation = Quaternion.LookRotation(newRot);
                    }
                    offset = num = 0;
                }
                else
                {
                    Reset();
                }
                break;
        }
    }
}

5.Main调用
(1)最后通过Main放置在游戏场景,当进入场景时,根据选择的id创建飞机,初始化变量,更新界面的ui,其实放在GamePanel中也是一样的,都可以

  • 10
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。
Unity中实现局部避障可以使用NavMesh Obstacle组件。这个组件可以用来创建障碍物,使代理能够预测地避免障碍。当障碍物移动时,代理可以根据障碍物的位置和形状进行动态避障。 以下是一个使用NavMesh Obstacle组件实现局部避障的示例代码: ```csharp using UnityEngine; public class LocalObstacle : MonoBehaviour { private NavMeshObstacle obstacle; private void Start() { obstacle = GetComponent<NavMeshObstacle>(); } private void Update() { // 根据障碍物的移动情况来启用或禁用NavMeshObstacle组件 if (IsObstacleMoving()) { obstacle.enabled = true; } else { obstacle.enabled = false; } } private bool IsObstacleMoving() { // 判断障碍物是否在移动 // 这里可以根据具体的逻辑来判断障碍物是否在移动,例如检测位置变化或速度等 return true; // 返回true表示障碍物在移动 } } ``` 在这个示例中,我们创建了一个名为LocalObstacle的脚本,并将其附加到障碍物的游戏对象上。在Start方法中,我们获取了NavMeshObstacle组件的引用。在Update方法中,我们根据障碍物的移动情况来启用或禁用NavMeshObstacle组件。IsObstacleMoving方法用于判断障碍物是否在移动,你可以根据具体的逻辑来实现这个方法。 这样,当障碍物移动时,NavMeshObstacle组件会启用,代理就会预测地避免障碍。当障碍物静止时,NavMeshObstacle组件会禁用,代理就会将障碍物视为阻塞了所有代理的路径,从而影响全局导航。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值