Vuforia AR篇(九)— AR塔防下篇

news2024/11/20 23:18:12

目录

  • 前言
  • 一、搭建UI
  • 二、创建脚本

前言

在增强现实(AR)技术快速发展的今天,Vuforia作为一个强大的AR开发平台,为开发者提供了许多便捷的工具和功能。在本篇博客中,我们将介绍如何使用Vuforia在Unity中创建一个简单的塔防游戏。通过结合Vuforia的图像识别和增强现实技术,我们可以将传统的塔防游戏带入一个全新的维度。


一、搭建UI

创建Canvas,并设置16:9

在这里插入图片描述

创建button,然后设置图片

在这里插入图片描述

在素材中选择一个合适的塔,然后做成自己的预制体

在这里插入图片描述

设置所有炮塔基座的tag和layer

在这里插入图片描述

设置敌人的tag和layer

在这里插入图片描述

设置所有炮塔基座的碰撞器

在这里插入图片描述

创建敌人显示的UI

创建一个image,然后选择上一期素材的图片

在这里插入图片描述

在图片下面创建一个text用于显示血量的数值

在这里插入图片描述

复制几份,然后显示不同的数值

在这里插入图片描述

在这里插入图片描述

创建敌人血条

创建一个Slider,用于制作血条显示,把图片修改成line并且删除子物体

在这里插入图片描述

创建一个子image,把图片也改成line,修改图片的填充选项,然后血条就完成了

在这里插入图片描述

在这里插入图片描述

设置Scrollbar的Handle Rect,至此血条UI设计完成

在这里插入图片描述

二、创建脚本

GameManager脚本

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


public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }

    public List<Transform> pointList;

    //玩家当前点击的塔
    public int currentChoice;

    //存储塔的预制体
    public List<GameObject> towerList;

    //存储塔的费用
    public List<int> towerCostList;


    //玩家总金币
    public int totalCoin;

    //玩家剩下的金币
    public int remainingCoin;

    //敌人血量
    public int maxHealth;
    public int currentHealth;

    private GameUi _gameUi;

    private SpawnManager _sm;

    //需要创建的敌人数量
    public int totalCreateEnemys;

    //击杀的敌人数量
    public int hasKilledEnemys;

    //当前游戏状态
    public bool isFinish;
    public bool isPause;

    public bool isPlant;

    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
        }
        else
        {
            Destroy(gameObject);
        }

        _sm = GetComponent<SpawnManager>();

        //初始化
        totalCreateEnemys = 4;
        _sm.createNum = 4;
        totalCoin = remainingCoin = 100;
        maxHealth = currentHealth = 5;
    }

    void Start()
    {
        //开启敌人生成携程
        StartCoroutine(_sm.CreateEnemyCoroutine(3));
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            if (Camera.main != null)
            {
                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
                RaycastHit hit;
                bool isHit = Physics.Raycast(ray, out hit);

                if (isHit)
                {
                    if (hit.transform.CompareTag("BaseBox"))
                    {
                        //如果点击到了炮台基座
                        if (hit.transform.childCount == 0)
                        {
                            if (remainingCoin >= towerCostList[currentChoice])
                            {
                                if (isPlant)
                                {
                                    GameObject go = Instantiate(towerList[currentChoice], hit.transform.position,
                                        Quaternion.identity);
                                    go.transform.SetParent(hit.transform);
                                    go.transform.localPosition = new Vector3(0, 0.02f, 0);

                                    //更新金币和UI
                                    remainingCoin -= towerCostList[currentChoice];
                                    _gameUi.SetCoinUI();
                                }
                            }
                        }
                    }
                }
            }
        }

        if (currentHealth <= 0 || hasKilledEnemys >= totalCreateEnemys)
        {
            GameEnd();
        }
    }

    //游戏结束
    private void GameEnd()
    {
        isFinish = true;
        StopAllCoroutines();
        _gameUi.ExitPanelShow();
    }

    //根据扫描情况开始或者暂停游戏
    public void PauseOrContinueGame()
    {
        Time.timeScale = !isPause ? 0f : 1f;

        isPause = !isPause;
    }
}

在这里插入图片描述

创建塔的脚本

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


public class Tower : MonoBehaviour
{
    // 存储检测到的碰撞体数组
    public Collider[] colliders; 
    public int maxSize;
    public float rotationSpeed;

    //攻击间隔
    public float attackInternal;

    //攻击开始的时间
    public float startTime;

    
    //tower的特效
    public GameObject hitEffect;
    public GameObject firePoint1;
    public GameObject firePoint2;
    private void Awake()
    {
        maxSize = 10; // 初始化最大碰撞体数量为10
        colliders = new Collider[maxSize]; // 初始化碰撞体数组
        rotationSpeed = 100;
        attackInternal = 0.5f;
    }


    private void Update()
    {
        //获取最近的敌人
        GameObject go = LookForTarget();
        if (go!=null)
        {
            //修改炮台方向
            RotateTower(go);
        }

        startTime += Time.deltaTime;
        if (startTime>attackInternal)
        {
            NormalAttack();
            startTime = 0;
        }
    }


    // 检测塔范围内的敌人
    public virtual GameObject LookForTarget()
    {
        // 清空数据
        Array.Clear(colliders,0,colliders.Length);
        // 使用OverlapSphereNonAlloc方法检测塔范围内的敌人,结果存储在colliders数组中
        Physics.OverlapSphereNonAlloc(transform.position, 1, colliders, LayerMask.GetMask("Enemy"));
        GameObject closestObj = null; 
        float distance = float.MaxValue; 
        for (int i = 0; i < colliders.Length; i++) 
        {
            if (colliders[i]!=null)
            {
                GameObject currentObj = colliders[i].gameObject; 
                // 计算当前对象与塔的距离
                float tempDistance = Vector3.Distance(transform.position, currentObj.transform.position); 

                // 如果当前对象距离小于已知最小距离
                if (tempDistance < distance) 
                {
                    distance = tempDistance; 
                    closestObj = colliders[i].gameObject; 
                }
            }

        }

        return closestObj; 
    }
    
    // 控制塔朝向敌人
    public void RotateTower(GameObject go)
    {
        Vector3 dir = go.transform.position - transform.position;
        Vector3 rotationDir = Vector3.RotateTowards(transform.forward, dir, 0.5f, 360f);
        Quaternion targetRotation = Quaternion.LookRotation(rotationDir);
        transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
    }
    
    // 塔的攻击
    public void NormalAttack()
    {
        GameObject target = LookForTarget();    
        if (target!=null)
        {
            Enemy enemy = target.GetComponent<Enemy>();
            
            if (enemy!=null)
            {
                //生成攻击特效
                var o = Instantiate(hitEffect, firePoint1.transform.position, transform.rotation);
                //删除特效
                Destroy(o,0.2f);
                enemy.Hurt();
            }
        }
    }
}

在这里插入图片描述

创建UI的脚本


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


public class GameUi : MonoBehaviour
{
    public Button towerBtn;

    public Text enemyText;

    public Text coinText;
    
    public Text healthText;

    //存储血条
    public List<Slider> sliders;

    //游戏结束UI
    public Image gameOverUI;

    //游戏结束按钮
    public Button gameOverBtn;

    void Awake()
    {
        towerBtn.onClick.AddListener(CreateTower);
        gameOverBtn.onClick.AddListener(ExitGame);
        SetBloodPositionInfo();
        SetGamOverShow();
    }

    void Start()
    {
    }

    void Update()
    {
    }

    private void CreateTower()
    {
        GameManager.Instance.isPlant = true;
        GameManager.Instance.currentChoice = 0;
    }

    public void SetEnemyUI()
    {
        enemyText.text = $"{GameManager.Instance.hasKilledEnemys}/{GameManager.Instance.totalCreateEnemys}";
    }

    public void SetCoinUI()
    {
        coinText.text = $"{GameManager.Instance.remainingCoin}/{GameManager.Instance.totalCoin}";
    }
    public void SetHealthUI()
    {
        healthText.text = $"{GameManager.Instance.currentHealth}/{GameManager.Instance.maxHealth}";
    }
    //设置血条的位置信息
    public void SetBloodPositionInfo()
    {
        for (int i = 0; i < sliders.Count; i++)
        {
            var rectTransform = sliders[i].GetComponent<RectTransform>();
            rectTransform.position = new Vector3(-10000f, 0f, 0f);
            sliders[i].gameObject.SetActive(false);
        }
    }

    //查找到没有激活的血条然后显示出来
    public Slider FindNoActivationBloodShow()
    {
        //获取一个没有激活的slider
        var slider = sliders.Find(x => !x.gameObject.activeSelf);
        slider.gameObject.SetActive(true);
        return slider;
    }

    public void SetGamOverShow()
    {
        var rectTransform = gameOverUI.GetComponent<RectTransform>();
        rectTransform.position = new Vector3(-10000f, 0f, 0f);
    }

    public void ExitPanelShow()
    {
        var rectTransform = gameOverUI.GetComponent<RectTransform>();
        rectTransform.position = new Vector3(Screen.width / 2f, Screen.height / 2f, 0f);
    }

    //退出游戏
    public void ExitGame()
    {
        Application.Quit();
    }
}

在这里插入图片描述

创建SpawnManager脚本

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


public class SpawnManager : MonoBehaviour
{

    public GameObject enemyPrefab;

    public GameObject parent;

    
    //需要创建的敌人数量
    public int createNum;
    void Awake()
    {
        createNum = 4;
    }

    void Start ()
    {
        CreateEnemy();
    }
	
    void Update ()
    {

    }

    public void CreateEnemy()
    {
        GameObject enemy = Instantiate(enemyPrefab, parent.transform);
        enemy.transform.SetParent(parent.transform);
        enemy.transform.localPosition = new Vector3(-1f, 0.25f, 4f);
    }

    public IEnumerator CreateEnemyCoroutine(float duration)
    {
        int count = 0;
        while (true)
        {
            if (count<createNum)
            {
                yield return new WaitForSeconds(duration);
                CreateEnemy();
                count++;
            }
            else
            {
                break;
            }
        }
    }

}

在这里插入图片描述

创建Enemy脚本


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


public class Enemy : MonoBehaviour
{
    public int currentIndex;

    public float moveSpeed;

    //血量
    public int maxHp;
    public int currentHp;


    private Slider _slider;
    private RectTransform _rt;
    //血条跟随偏移
    public float xOffest;
    public float yOffest;
    
    private GameUi _gameUi;
    void Awake()
    {
        moveSpeed = 0.2f;
        currentIndex = 0;
        currentHp = maxHp = 10;
        _gameUi = GameObject.Find("Canvas").GetComponent<GameUi>();
        _slider = _gameUi.FindNoActivationBloodShow();
        _rt = _slider.GetComponent<RectTransform>();
        xOffest = 5;
        yOffest = 5;
    }

    void Start()
    {
    }

    void Update()
    {
        if (!GameManager.Instance.isFinish)
        {
            BloodFollow();
            Move();
        }
        //当摄像头没有扫描到图片的时候,暂停游戏并隐藏UI
        if (GameManager.Instance.isPause)
        {
            SetSliderInfoWhenPause();
        }
    }


    public void Move()
    {
        int nextPoint = currentIndex + 1;
        if (GameManager.Instance.pointList.Count <= nextPoint)
        {
            ReachFinalPoint();
            return;
        }

        Vector3 v3 = transform.InverseTransformPoint(GameManager.Instance.pointList[nextPoint].position);
        transform.Translate(v3 * (Time.deltaTime * moveSpeed));

        if (IsArrive(GameManager.Instance.pointList[nextPoint]))
        {
            currentIndex++;
        }
    }

    bool IsArrive(Transform t)
    {
        float distance = Vector3.Distance(transform.position, t.position);
        if (distance < 0.05f)
        {
            return true;
        }

        return false;
    }
    
    // 受伤的方法
    public void Hurt(int damge = 2)
    {
        currentHp -= damge;
        _slider.value = currentHp * 1.0f / (maxHp * 1.0f);
        if (currentHp<=0)
        {
            Die();
        }
    }

    //死亡效果
    public void Die()
    {
        _rt.position = new Vector3(-10000f, 0f, 0f);
        _slider.value = 1;
        _slider.gameObject.SetActive(false);
        GameManager.Instance.hasKilledEnemys += 1;
        _gameUi.SetEnemyUI();
        Destroy(gameObject);
    }

    //血条跟随
    public void BloodFollow()
    {
        if (Camera.main != null)
        {
            Vector2 point = Camera.main.WorldToScreenPoint(transform.position);
            _rt.position = point + new Vector2(xOffest, yOffest);
        }
    }
    //隐藏UI
    public void SetSliderInfoWhenPause()
    {
        if (_rt!=null)
        {
            _rt.position = new Vector3(-10000f, 0, 0);
        }
    }
    //移动到最后一个点处理方法
    public void ReachFinalPoint()
    {
        _rt.position = new Vector3(-10000f, 0, 0);
        _slider.value = 1;
        _slider.gameObject.SetActive(false);
        GameManager.Instance.currentHealth -= 1;
        _gameUi.SetHealthUI();
        GameObject.Destroy(gameObject);
    }

}

在这里插入图片描述

根据扫描情况开始或者暂停游戏方法赋值

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1978798.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

46 class添加与颜色分配47 区域规则介绍与添加48 走线修线介绍49 复制、改变、删除操作

46 class添加与颜色分配&&47 区域规则介绍与添加&&48 走线修线介绍&&49 复制、改变、删除操作 第一部分 46 class添加与颜色分配创建网络类CLS创建网络组net-group NG颜色分配**填充类型设置****高亮关闭****修改颜色面板的颜色**从其它已有PCB设计中导…

用Manim实现——计算和绘制图形下方区域

用Manim实现——计算和绘制图形下方区域 get_area 函数 get_area是一个用于计算和绘制图形下方区域的函数&#xff0c;常用于图形动画库&#xff08;如 Manim&#xff09; get_area(graph, x_rangeNone, color(ManimColor(#58C4DD),ManimColor(#83C167)), opacity0.3, bounde…

市场主流 AI 视频生成技术的迭代路径

AI视频生成技术的迭代路径经历了从GANVAE、Transformer、Diffusion Model到Sora采用的DiT架构&#xff08;TransformerDiffusion&#xff09;等多个阶段&#xff0c;每个阶段的技术升级都在视频处理质量上带来了飞跃性的提升。这些技术进步不仅推动了AI视频生成领域的快速发展&…

大载重无人机必备:适航证技术详解

随着无人机技术的飞速发展&#xff0c;大载重无人机在物流运输、农业植保、应急救援等领域展现出巨大潜力。然而&#xff0c;为确保这些无人机在空中运行的安全性与高效性&#xff0c;获取适航证成为不可或缺的关键步骤。本文将深入探讨大载重无人机适航证的必备要素&#xff0…

用phpstudy搭建MySQL数据库

使用环境&#xff1a;win11 使用软件&#xff1a;phpstudy 下载地址&#xff1a;小皮面板(phpstudy) - 让天下没有难配的服务器环境&#xff01; MySQL数据库搭建步骤&#xff1a; 1、在小皮的设置界面检测 3306 端口&#xff0c;保障 3306 端口可用&#xff1b; 2、在小皮…

42 PCB布线叠层与阻抗介绍43 PCB布线过孔添加与设置44 差分对添加与设置45 布线间距规则与介绍

42 PCB布线叠层与阻抗介绍&&43 PCB布线过孔添加与设置&44 差分对添加与设置&&45 布线间距规则与介绍 第一部分 42 PCB布线叠层与阻抗介绍1 板子是怎么来的。2 四层板为例&#xff0c;做叠层和阻抗计算。 第二部分 43 PCB布线过孔添加与设置介绍PCBEdotor中…

STM32F28335实验:蜂鸣器实验

实验三个文档&#xff1a; 蜂鸣器驱动&#xff1a; 1.5KHZ-5KHZ之间&#xff0c;我们取5KHZ 也就是高低点平各100um&#xff0c;周期为200um的方波 LED1还是GPIO68管脚 蜂鸣器管脚GPIO6 蜂鸣器驱动&#xff1a; beep.c /** heep.c** Created on: 2024年8月4日* Au…

Studying-代码随想录训练营day54| 110.字符串接龙、105.有向图的完全可达性、106.岛屿的周长

第53天&#xff0c;图论04&#xff0c;加强广搜和深搜的理解练习&#x1f4aa;(ง •_•)ง&#xff0c;编程语言&#xff1a;C 目录 110.字符串接龙 105.有向图的完全可达性 106.岛屿的周长 总结 110.字符串接龙 文档讲解&#xff1a;手撕字符串接龙 题目&#xff1a;110…

黑马Java零基础视频教程精华部分_12_面向对象进阶(4)_内部类

《黑马Java零基础视频教程精华部分》系列文章目录 黑马Java零基础视频教程精华部分_1_JDK、JRE、字面量、JAVA运算符 黑马Java零基础视频教程精华部分_2_顺序结构、分支结构、循环结构 黑马Java零基础视频教程精华部分_3_无限循环、跳转控制语句、数组、方法 黑马Java零基础视…

【九】Hadoop3.3.4HA高可用配置

文章目录 1.高可用基本原理1.NameNode 高可用性主备 NameNodeJournalNode 2.Zookeeper 协调3.Quorum Journal Manager (QJM)4.Failover 控制器5.元数据共享6.检查点机制7.切换过程 2.Hadoop高可用配置1.环境背景2.hdfs-site.xml基本配置高可用配置 3.core-site.xml基本配置代理…

【C++】模版:范式编程、函数模板、类模板

目录 一.范式编程 二.函数模板 1.概念与格式 2.原理 3.实例化 4.匹配规则 三.类模板 一.范式编程 在写C函数重载的时候&#xff0c;可能会写很多同一类的函数&#xff0c;例如交换函数&#xff1a; void Swap(int& left, int& right) {int temp left;left r…

推荐 3款电脑上不可或缺的神仙软件,一用就再也离不开

WinForGIFSicle WinForGIFSicle是一款基于GIFSicle的可视化批量GIF压缩工具&#xff0c;具有多种功能和特点。首先&#xff0c;它是一个小巧、免费且开源的软件&#xff0c;能够有效地压缩GIF动画文件。该工具支持按比例压缩和按压缩比压缩两种模式&#xff0c;用户可以根据需要…

MySQL-分库分表

目录 介绍 问题分析 拆分策略 垂直拆分 垂直分库 垂直分表 水平拆分 水平分库 水平分表 实现技术 MyCat 介绍 目录 结构 入门 配置 schema.xml schema标签 datanode标签 datahost标签 rule.xml server.xml system标签 user标签 分片 垂直拆分 水平拆…

【QT】绘图API

目录 绘图API核心类 第一步&#xff1a;重写paintEvent事件函数 第二步&#xff1a;创建QPainter 第三步&#xff1a;设置QPainter绘制的文字类型&#xff08;非必须&#xff09; 第四步&#xff1a;设置画笔属性&#xff08;线条&#xff09; 第五步&#xff1a;设置画…

【复旦微FM33 MCU 外设开发指南】总集篇

各位好&#xff0c;这里是冷凝雨。 本系列旨在为复旦微的MCU提供全面的开发指南 以FM33LC0xx&#xff08;Arm M0&#xff09;为例&#xff0c;与现有的其余MCU型号&#xff0c;如FM33LG0xx、FM33M0xx等有许多相同之处。 本系列将侧重寄存器开发&#xff0c;分享一些各外设使用的…

线上静态的前端、nginx环境基础、基于域名的虚拟主机、基于ip的访问、部署nfs

一、环境基础&#xff1a; 1.查看文件&#xff08;不要注释及空行&#xff09; [root17dns ~]# grep -Ev "#|^$" /usr/local/nginx/conf/nginx.conf 2.备份源文件&#xff1a; [root17dns ~]# cp /usr/local/nginx/conf/nginx.conf /usr/local/nginx/conf/nginx.con…

希尔排序, 插入排序, 冒泡排序, 选择排序【C++】

希尔排序&#xff0c; 插入排序&#xff0c; 冒泡排序&#xff0c; 选择排序 测试代码希尔排序选择排序冒泡排序插入排序 测试代码 #include <iostream> using namespace std;int main() {int arr[6] { 0 };int len sizeof(arr) / sizeof(int);for (int i 0; i < …

混合精度、异构计算——杂记

1、英伟达GPU架构 Figure 1 shows a full GA100 GPU with 128 SMs. The A100 is based on GA100 and has 108 SMs. SM是streaming multiprocessor的简写&#xff0c;4个处理单元组成一个SM&#xff0c;如Figure 2。 每个SM有64个INT32&#xff0c;64个FP32&#xff0c;32个F…

【Linux网络】网络层协议:IP

本篇博客整理了 TCP/IP 分层模型中网络层的 IP 协议&#xff0c;旨在让读者更加深入理解网络协议栈的设计和网络编程。 目录 一、网络层 二、IP 报头 1&#xff09;报头与有效载荷的分离 2&#xff09;有效载荷的上交 3&#xff09;源 IP 与目的 IP 4&#xff09;生存时间…

大模型学习笔记 - 大纲

LLM 大纲 LLM 大纲 1. LLM 模型架构 LLM 技术细节 - 注意力机制LLM 技术细节 - 位置编码 2. LLM 预训练3. LLM 指令微调 LLM 高效微调技术 4. LLM 人类对齐 LLM InstructGPTLLM PPO算法LLM DPO 算法 5. LLM 解码与部署6. LLM 模型LLaMA 系列7. LLM RAG 1. LLM 模型架构 大模…