unity制作一款塔防游戏

文章目录

  • 介绍
  • 寻路系统
  • 怪物生成器
  • 制作3种初级炮台、3种升级炮台
  • 设置炮台属性
  • 选择炮台,添加监听事件
  • 炮弹追踪攻击敌人
  • 拖动鼠标实现相机视角转换
  • 鼠标光标放在cube上变色
  • 文字动画


介绍

在这里插入图片描述

关键技术:

寻路系统
生成怪物算法
粒子系统
line renderer制作追踪射线
相机视角移动、放大
炮弹追踪算法
粒子特效
按钮动画制作


寻路系统

设置几个基准点,用于偏移方向

  1. 定义一个Move方法
  2. 判断当前行数是否超过位置数组的长度,如果是则直接返回
  3. 根据当前位置与目标位置计算出移动方向,并乘以移动速度和时间,用transform.Translate方法进行移动
  4. 判断当前位置是否接近目标位置,如果是则将当前行数index加1
  5. 如果当前行数大于位置数组的长度减1,说明已经到达终点,调用ReachDestination方法
  6. 完成移动方法的定义
    在这里插入图片描述
  void Move()
    {
        if (index > positions.Length - 1) return;
        transform.Translate((positions[index].position - transform.position).normalized * Time.deltaTime * speed);
        if (Vector3.Distance(positions[index].position, transform.position) < 0.2f)
        {
            index++;
        }
        if (index > positions.Length - 1)
        {
            ReachDestination();
        }
    }

怪物生成器

一波一波生成敌人,相邻波时间间隔为wavaRate
相邻敌人的时间间隔为rate

序列化,设置每一波敌人的基本属性
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//保存每一波敌人生成所需要的属性
[System.Serializable]
public class Wave  {
    public GameObject enemyPrefab;
    public int count;
    public float rate;
}

只有当前波完全销毁,才能有下一波攻势
// 这个脚本负责按波次生成敌人,并跟踪当前有多少敌人存活

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

public class EnemySpawner : MonoBehaviour {

    // 一个静态变量,用于跟踪当前有多少敌人存活
    public static int CountEnemyAlive = 0;

    // 一个波次数组,指定每个波次要生成的敌人类型和数量
    public Wave[] waves;

    // 敌人生成的起始位置
    public Transform START;

    // 每个波次之间的时间间隔
    public float waveRate = 0.2f;

    // 生成敌人的协程的引用
    private Coroutine coroutine;

    void Start()
    {
        // 开始生成敌人的协程
        coroutine = StartCoroutine(SpawnEnemy());
    }

    public void Stop()
    {
        // 停止生成敌人的协程
        StopCoroutine(coroutine);
    }

    IEnumerator SpawnEnemy()
    {
        // 循环遍历每个波次的敌人
        foreach (Wave wave in waves)
        {
            // 为这个波次生成指定数量的敌人
            for (int i = 0; i < wave.count; i++)
            {
                // 在起始位置实例化敌人预制件
                GameObject.Instantiate(wave.enemyPrefab, START.position, Quaternion.identity);

                // 增加当前存活敌人的计数
                CountEnemyAlive++;

                // 在生成最后一个敌人之前,等待指定的时间
                if(i != wave.count - 1)
                    yield return new WaitForSeconds(wave.rate);
            }

            // 在这个波次的所有敌人被消灭之前,等待
            while (CountEnemyAlive > 0)
            {
                yield return 0;
            }

            // 等待指定的时间,然后开始下一个波次
            yield return new WaitForSeconds(waveRate);
        }

        // 在最后一个波次的所有敌人被消灭之前,等待
        while (CountEnemyAlive > 0)
        {
            yield return 0;
        }

        // 在GameManager实例上调用Win方法,以胜利结束游戏
        GameManager.Instance.Win();
    }
}

在这里插入图片描述


制作3种初级炮台、3种升级炮台

在这里插入图片描述

在这里插入图片描述


设置炮台属性

炮台预制体、炮台价格、升级后的预制体、升级价格、枚举三种炮台类型

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

[System.Serializable]
public class TurretData  {
    public GameObject turretPrefab;
    public int cost;
    public GameObject turretUpgradedPrefab;
    public int costUpgraded;
    public TurretType type;
}
public enum TurretType
{
    LaserTurret,
    MissileTurret,
    StandardTurret
}

选择炮台,添加监听事件

在这里插入图片描述

	 public TurretData laserTurretData;
    public TurretData missileTurretData;
    public TurretData standardTurretData;

    //表示当前选择的炮台(要建造的炮台)
    private TurretData selectedTurretData;
  public void OnLaserSelected(bool isOn)
    {
        if (isOn)
        {
            selectedTurretData = laserTurretData;
        }
    }

    public void OnMissileSelected(bool isOn)
    {
        if (isOn)
        {
            selectedTurretData = missileTurretData;
        }
    }
    public void OnStandardSelected(bool isOn)
    {
        if (isOn)
        {
            selectedTurretData = standardTurretData;
        }
    }

炮弹追踪攻击敌人

以下是每个函数的作用:

  1. void OnTriggerEnter(Collider col):当有物体进入防御塔触发器范围内时,将其添加到敌人列表中。

  2. void OnTriggerExit(Collider col):当有物体离开防御塔触发器范围时,将其从敌人列表中移除。

  3. void Start():在开始时初始化计时器。

  4. void Update():在每一帧中更新防御塔的逻辑。

  5. void Attack():发射子弹攻击敌人。

  6. void UpdateEnemys():更新敌人列表,移除已经死亡的敌人。

在 Update 函数中:

  1. 如果敌人列表不为空且敌人列表中的第一个敌人不为空,则瞄准敌人的头部位置。

  2. 如果不使用激光攻击,则增加计时器。如果敌人列表不为空且计时器超过攻击间隔时间,则攻击敌人。

  3. 如果使用激光攻击且敌人列表不为空,则显示激光渲染器和激光特效,并攻击敌人。

  4. 如果敌人列表为空,则隐藏激光特效和激光渲染器。

在 Attack 函数中:

  1. 如果敌人列表中的第一个敌人为空,则更新敌人列表。

  2. 如果敌人列表不为空,则发射子弹攻击敌人,否则重置计时器。

在 UpdateEnemys 函数中:

  1. 创建一个空列表,用于存储已经死亡的敌人在敌人列表中的下标。

  2. 遍历敌人列表,找出已经死亡的敌人。

  3. 遍历已经死亡的敌人在敌人列表中的下标,将它们从敌人列表中移除。

// 引入命名空间
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 炮塔脚本
public class Turret : MonoBehaviour {

    // 炮塔攻击的敌人列表
    private List<GameObject> enemys = new List<GameObject>();

    // 敌人进入触发器
    void OnTriggerEnter(Collider col)
    {
        if (col.tag == "Enemy")
        {
            enemys.Add(col.gameObject);
        }
    }

    // 敌人离开触发器
    void OnTriggerExit(Collider col)
    {
        if (col.tag == "Enemy")
        {
            enemys.Remove(col.gameObject);
        }
    }

    // 炮塔攻击频率
    public float attackRateTime = 1;

    // 计时器
    private float timer = 0;

    // 子弹预制体
    public GameObject bulletPrefab;

    // 发射子弹的位置
    public Transform firePosition;

    // 炮塔头部
    public Transform head;

    // 是否使用激光武器
    public bool useLaser = false;

    // 激光武器伤害值
    public float damageRate = 70;

    // 激光武器的LineRenderer组件
    public LineRenderer laserRenderer;

    // 激光武器的特效
    public GameObject laserEffect;

    // 初始化
    void Start()
    {
        timer = attackRateTime;
    }

    // 更新
    void Update()
    {
        // 如果有敌人
        if (enemys.Count > 0 && enemys[0] != null)
        {
            // 瞄准第一个敌人
            Vector3 targetPosition = enemys[0].transform.position;
            targetPosition.y = head.position.y;
            head.LookAt(targetPosition);
        }
        // 如果不使用激光武器
        if (useLaser == false)
        {
            // 计时器累加
            timer += Time.deltaTime;
            // 如果有敌人并且计时器时间到
            if (enemys.Count > 0 && timer >= attackRateTime)
            {
                // 重置计时器
                timer = 0;
                // 进行攻击
                Attack();
            }
        }
        // 如果使用激光武器
        else if(enemys.Count>0)
        {
            // 开启激光线和特效
            if (laserRenderer.enabled == false)
                laserRenderer.enabled = true;
            laserEffect.SetActive(true);
            // 如果第一个敌人为空,更新敌人列表
            if (enemys[0] == null)
            {
                UpdateEnemys();
            }
            // 如果有敌人
            if (enemys.Count > 0)
            {
                // 激光线设置起始位置和结束位置
                laserRenderer.SetPositions(new Vector3[]{firePosition.position, enemys[0].transform.position});
                // 对第一个敌人造成伤害
                enemys[0].GetComponent<Enemy>().TakeDamage(damageRate *Time.deltaTime );
                // 特效跟随第一个敌人
                laserEffect.transform.position = enemys[0].transform.position;
                // 特效朝向炮塔
                Vector3 pos = transform.position;
                pos.y = enemys[0].transform.position.y;
                laserEffect.transform.LookAt(pos);
            }
        }
        // 如果没有敌人,关闭激光特效和线
        else
        {
            laserEffect.SetActive(false);
            laserRenderer.enabled = false;
        }
    }

    // 攻击
    void Attack()
    {
        // 如果第一个敌人为空,更新敌人列表
        if (enemys[0] == null)
        {
            UpdateEnemys();
        }
        // 如果有敌人
        if (enemys.Count > 0)
        {
            // 实例化子弹
            GameObject bullet = GameObject.Instantiate(bulletPrefab, firePosition.position, firePosition.rotation);
            // 设置子弹目标为第一个敌人
            bullet.GetComponent<Bullet>().SetTarget(enemys[0].transform);
        }
        else
        {
            // 如果没有敌人,重置计时器
            timer = attackRateTime;
        }
    }

    // 更新敌人列表
    void UpdateEnemys()
    {
        // 找到所有空的敌人,并将其索引加入空索引列表中
        List<int> emptyIndex = new List<int>();
        for (int index = 0; index < enemys.Count; index++)
        {
            if (enemys[index] == null)
            {
                emptyIndex.Add(index);
            }
        }
        // 根据空索引列表,移除空敌人
        for (int i = 0; i < emptyIndex.Count; i++)
        {
            enemys.RemoveAt(emptyIndex[i]-i);
        }
    }
}





拖动鼠标实现相机视角转换

在这里插入图片描述

把这个脚本,挂载到主相机上。

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

public class ViewController : MonoBehaviour {

    public float sizespeed = 1;    // 定义了一个名为sizespeed的公共(public)浮点型(float)变量,初始值为1
    public float mouseSpeed = 10;  // 定义了一个名为mouseSpeed的公共浮点型变量,初始值为10

    private Vector3 lastMousePosition;    // 定义了一个名为lastMousePosition的私有(private)Vector3类型变量

    // Update is called once per frame
    void Update () {    // 定义了一个名为Update的方法,在每一帧(frame)中被调用

        float mouse = -Input.GetAxis("Mouse ScrollWheel");    // 获取鼠标滚轮的输入值,并将其赋值给名为mouse的局部(local)浮点型变量

        // 鼠标中键按住拖动
        if (Input.GetMouseButton(2)) {    // 检测if语句中的条件是否为真,如果鼠标中键被按住,则执行大括号内的代码块

            Vector3 deltaMousePosition = Input.mousePosition - lastMousePosition;    // 获取当前鼠标位置和上一次鼠标位置之间的差值,并将其赋值给名为deltaMousePosition的局部Vector3类型变量

            transform.Translate(-deltaMousePosition.x * mouseSpeed * Time.deltaTime, -deltaMousePosition.y * mouseSpeed * Time.deltaTime, 0);    // 将摄像机的位置向左右和上下移动,移动的距离由鼠标的移动距离和鼠标速度决定

        }

        transform.Translate(new Vector3(0, mouse * sizespeed, 0) * Time.deltaTime, Space.World);    // 将摄像机的位置向上或向下移动,移动的距离由鼠标滚轮的输入值和大小速度决定

        lastMousePosition = Input.mousePosition;    // 将鼠标当前位置赋值给lastMousePosition变量,以便下一帧计算鼠标位置差值
    }
}

鼠标光标放在cube上变色

在这里插入图片描述

在这里插入图片描述

给cube添加脚本:

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

public class aaa : MonoBehaviour {

    private Renderer renderer; // 渲染器组件

    void Start()
    {
        renderer = GetComponent<Renderer>(); // 获取自身的渲染器组件
    }

    // 当鼠标指针进入该物体的渲染范围内时执行
    void OnMouseEnter()
    {
        // 如果鼠标不在UI元素上,则将该物体的材质颜色改为红色
        if (EventSystem.current.IsPointerOverGameObject() == false)
        {
            renderer.material.color = Color.red;
        }
    }

    // 当鼠标指针离开该物体的渲染范围时执行
    void OnMouseExit()
    {
        // 将该物体的材质颜色改回白色
        renderer.material.color = Color.white;
    }
}


文字动画

在这里插入图片描述

在这里插入图片描述


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

通过Python的pdfplumber库提取pdf中表格数据

文章目录 前言一、pdfplumber库是什么&#xff1f;二、安装pdfplumber库三、查看pdfplumber库版本四、提取pdf中表格数据1.引入库2.定义pdf文件路径3.打开pdf文件4.获取pdf文件中的页数5.遍历每一页6.获取当前页内容7.提取表格数据8.输出表格数据9.效果 总结 前言 大家好&#…

Scala学习(九)---List集合

文章目录 1.List1.1 不可变List集合1.2 可变集合ListBuffer 1.List List集合默认为不可变集合&#xff0c;List集合在实例化的时候&#xff0c;无法通过new关键字进行实例化&#xff0c;只能通过伴生apply方法来对其进行实例化 1.1 不可变List集合 创建一个不可变list集合 …

HUD(抬头显示)的方案介绍

目录 一、基于DLP3030-Q1的HUD电路设计 二、DLP3030-Q1的介绍 三、DLP3030-Q1工作原理 四、DLPC120-Q1DMD 显示控制器 五、TMS320F2802332 位 MCU 六、 HUD显示实例 HUD主板实例 七、HUD的软件环境 一、基于DLP3030-Q1的HUD电路设计 本设计采用了DLP3030-Q1 芯片组&…

设计模式 -第1部分 避免浪费- 第1章 Flyweight 模式 - 共享对象避免浪费

第1部分 避免浪费 注&#xff1a;其内容主要来自于【日】-结城浩 著《图解设计模式》20章节 极力推荐大家阅读原著 第1章 Flyweight 模式 - 共享对象避免浪费 1.1 Flyweight 模式 Flyweight 的意思"轻量级"&#xff0c;其在英文中的原意指比赛中选手体重最轻等级的一…

【C语言】实现猜数字游戏——随机数

&#x1f6a9;纸上得来终觉浅&#xff0c; 绝知此事要躬行。 &#x1f31f;主页&#xff1a;June-Frost &#x1f680;专栏&#xff1a;C语言 该篇将对 选择与循环语句 进行运用&#xff0c;实现猜数字游戏。 需求&#xff1a;游戏后可以选择再次进行游戏&#xff0c;也可以选择…

「实在RPA·烟草数字员工」助力烟草行业数字化转型加速度

烟草行业作为烟草产业链上重要一环&#xff0c;外部连接烟草工业企业、零售客户、消费者&#xff0c;内部包含营销、专卖、烟叶、物流等诸多业务&#xff0c;信息系统众多&#xff0c;企业数据量庞大。因此&#xff0c;清楚地了解自身存在的痛点&#xff0c;找到适合自身业务需…

如何在华为OD机试中获得满分?Java实现【寻找峰值】一文详解!

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: Java华为OD机试真题&#xff08;2022&2023) 文章目录 1. 题目描述2. 输入描述3. 输出描述…

gitbook在centos上安装

1&#xff09;官网下载Node.js的Linux64位的二进制包:Download | Node.js 或者在线下载&#xff1a; wget https://nodejs.org/dist/v12.16.1/node-v12.16.1-linux-x64.tar.xz ​​2)到指定目录​解压 cd /opt/gitbook tar -xJf node-v12.16.1-linux-x64.tar.xz mv node-…

STM32采集传感器数据通过排序取稳定值

一、前言 在物联网、单片机开发中,经常需要采集各种传感器的数据。比如:温度、湿度、MQ2、MQ3、MQ4等等传感器数据。这些数据采集过程中可能有波动,偶尔不稳定,为了得到稳定的值,我们可以对数据多次采集,进行排序,去掉最大和最小的值,然后取平均值返回。 二、排序算法…

运维工程师面试总结(含答案)

运维工程师面试总结 原文链接&#xff1a;https://www.cuiliangblog.cn/detail/article/2 一、linux 1. linux系统启动流程 第一步&#xff1a;开机自检&#xff0c;加载BIOS第二步&#xff1a;读取&#xff2d;&#xff22;&#xff32;第三步&#xff1a;Boot Loader grub…

华为OD机试之真正的密码(Java源码)

真正的密码 题目描述 一行中输入一个字符串数组&#xff0c;如果其中一个字符串的所有以索引0开头的子串在数组中都有&#xff0c;那么这个字符串就是潜在密码在所有潜在密码中最长的是真正的密码&#xff0c;如果有多个长度相同的真正的密码&#xff0c;那么取字典序最大的为…

【ClickHouse】

文章目录 一、表引擎1、表引擎的作用2、TinyLog3、Memory4、MergeTree二、数据库引擎1、作用--跨种类交换数据2、示例 三、MergeTree引擎1、简单使用2、分区partition by3、主键primary key4、order by&#xff08;必填&#xff09; 一、表引擎 1、表引擎的作用 CK表引擎决定…

智能运维应用之道,告别企业数字化转型危机

面临的问题及挑战 数据中心发展历程 2000 年中国数据中心始建&#xff0c;至今已经历以下 3 大阶段。早期&#xff1a;离散型数据中心 IT 因以项目建设为导向&#xff0c;故缺乏规划且无专门运维管理体系&#xff0c;此外&#xff0c;开发建设完的项目均是独立运维维护&#…

【喜闻乐见,包教包会】二分图最大匹配:匈牙利算法(洛谷P3386)

&#x1f3ad;不要管上面那玩意。。。 引入 现在&#xff0c;你&#xff0c;是一位酒店的经理。 西装笔挺&#xff0c;清瘦智慧。 金丝眼镜&#xff0c;黑色钢笔。 大理石的地板&#xff0c;黑晶石的办公桌&#xff0c;晶莹的落地玻璃。 而现在&#xff0c;有几个雍容华贵的…

智慧城市同城V4小程序V2.24独立开源版 + 全插件+VUE小程序开源前端+最新用户授权接口

智慧城市同城V4小程序V2.22开源独立版本月最新版&#xff0c;与上一版相比修复了一些小细节&#xff0c;功能本身并无大的变化。新版系统包含全插件、包括很多稀缺收费的插件都在里面如括招聘、 家政等&#xff0c;外加小程序的VUE开源前端&#xff0c;整个系统全开源&#xff…

机器学习 | 降维:PCA主成分分析

本文整理自 长路漫漫2021的原创博客&#xff1a;sklearn基础篇&#xff08;九&#xff09;-- 主成分分析&#xff08;PCA&#xff09;李春春_的原创博客&#xff1a;主成分分析&#xff08;PCA&#xff09;原理详解bilibili视频&#xff1a;用最直观的方式告诉你&#xff1a;什…

Python中模块的使用方法4

1 模块、包和库的区别 Python中&#xff0c;模块的英文是“module”&#xff0c;是一个以py为后缀名的文件&#xff1b;包的英文是“package”&#xff0c;是一个包含了多个模块的目录&#xff1b;库的英文是“library”&#xff0c;包含了具有相关功能的包和模块。 2 模块的…

web练习第二周

前言&#xff1a;&#xff08;博主个人学习笔记&#xff0c;不用看&#xff09;web练习第二周&#xff0c;仅做出前3题。相比于第一周&#xff0c;难度大幅增加&#xff0c;写题时就算看了wp还是像个无头苍蝇一样到处乱创&#xff0c;大多都是陌生知识点&#xff0c;工具的使用…

LeetCode刷题(ACM模式)-02链表

参考引用&#xff1a;代码随想录 注&#xff1a;每道 LeetCode 题目都使用 ACM 代码模式&#xff0c;可直接在本地运行&#xff0c;蓝色字体为题目超链接 0. 链表理论基础 0.1 链表定义 链表是一种通过指针串联在一起的线性结构&#xff0c;每一个节点由两部分组成&#xff1a…

矿井水除总氮工艺详解

一、项目概述 项目背景: 1、水资源浪费长期以来&#xff0c;采煤对地下水造成了严重破坏。绝大部分矿井水&#xff0c;被以直排方式&#xff0c;流入河道、田野&#xff0c;这不仅造成水资源的白白浪费&#xff0c;也污染了环境。社会对此反响强烈的同时&#xff0c;煤矿企业也…
最新文章