【制作100个unity游戏之27】使用unity复刻经典游戏《植物大战僵尸》,制作属于自己的植物大战僵尸随机版和杂交版9(附带项目源码)

news2024/11/15 11:50:42

最终效果

在这里插入图片描述

系列导航

文章目录

  • 最终效果
  • 系列导航
  • 前言
  • 选择植物
    • 简单绘制选择植物面板
    • 渲染卡牌数据
  • 点击选中和移除卡牌
    • 修改UI
    • 代码控制
  • 开始战斗
  • 源码
  • 结束语

前言

本节主要实现添加选择植物功能

选择植物

简单绘制选择植物面板

每个卡牌插槽和前面植物卡牌类似,并配置为预制体
在这里插入图片描述

渲染卡牌数据

新增ScriptableObject,配置植物列表卡牌数据

[CreateAssetMenu(fileName = "CardData", menuName = "CardData", order = 0)]
public class CardData : ScriptableObject
{
    // 存储卡牌数据的列表
    public List<CardItem> cardItemDataList = new List<CardItem>();
}

// 表示单个关卡的数据
[System.Serializable]
public class CardItem
{
    public string name; //名称
    public float waitTime; //等待时间
    public int useSun;//需要阳光
    public GameObject prefab;//预制体
    public Sprite sprite;//图片
}

配置
在这里插入图片描述
新增ChooseCardPanel,渲染植物列表卡牌数据

public class ChooseCardPanel : MonoBehaviour
{
    public GameObject beforeCardPrefab;
    public CardData cardData;

    private void Start() {
       for (int i = 0; i < cardData.cardItemDataList.Count; i++)
        {
            GameObject beforeCard = Instantiate(beforeCardPrefab);
            beforeCard.transform.SetParent(transform, false);
            beforeCard.name = cardData.cardItemDataList[i].name;
            beforeCard.GetComponent<Image>().sprite = cardData.cardItemDataList[i].sprite;
            beforeCard.transform.Find("黑色进度").GetComponent<Image>().fillAmount = 0;
            beforeCard.transform.Find("黑色底图").gameObject.SetActive(false);

            Card card = beforeCard.GetComponent<Card>();
            card.waitTime = cardData.cardItemDataList[i].waitTime;
            card.useSun = cardData.cardItemDataList[i].useSun;
            card.prefab = cardData.cardItemDataList[i].prefab;
        }
    }
}

配置
在这里插入图片描述
运行效果,可以看到卡牌数据都渲染上去了
在这里插入图片描述

点击选中和移除卡牌

注意:这里会使用DOTween实现动画效果,不会用的小伙伴可以查看我之前写的文章:【推荐100个unity插件之2】DoTween动画插件的安装和使用整合(最全)

修改UI

注意修改卡牌容器的顶点位置在左上
在这里插入图片描述
卡牌预制体轴心也在左上
在这里插入图片描述
选项卡牌面板顶点同样位置在左上
在这里插入图片描述

代码控制

这里改动比较大,我直接贴出代码

修改GameManager,新增字段用于判断是否开始游戏

public bool isStart;//是否开始游戏

ChooseCardPanel代码

public class ChooseCardPanel : MonoBehaviour
{
    public static ChooseCardPanel Instance;
    public GameObject cardPrefab;//卡牌模板预制体
    public CardData cardData;//所有卡牌数据
    public GameObject useCardPanel;//选中的卡牌的对象父类
    public List<GameObject> useCardList;//选中的卡牌

    private void Awake()
    {
        Instance = this;
    }
    private void Start()
    {
        //渲染卡牌列表数据
        for (int i = 0; i < cardData.cardItemDataList.Count; i++)
        {
            GameObject beforeCard = Instantiate(cardPrefab);
            beforeCard.transform.SetParent(transform, false);
            beforeCard.GetComponent<Card>().cardItem = cardData.cardItemDataList[i];
        }
    }

    //添加卡牌
    public void AddCard(GameObject go)
    {
        int curIndex = useCardList.Count;
        if (curIndex >= 8)
        {
            Debug.Log("已经选中的卡片超过最大数量");
            return;
        }
        useCardList.Add(go);
        go.transform.SetParent(transform.root);//父级修改为当前对象所在的最顶层父对象,确保卡牌UI在最顶层显示
        Card card = go.GetComponent<Card>();
        card.isMoving = true;
        card.hasUse = true;
        // DoMove移动到目标位置
        go.transform.DOMove(useCardPanel.transform.position, 0.8f).OnComplete(
            () =>
            {
                go.transform.SetParent(useCardPanel.transform, false);
                //移动到其父对象的子物体列表的最前面
                go.transform.SetAsFirstSibling();
                card.isMoving = false;
            }
        );
    }

    //移除卡牌
    public void RemoveCard(GameObject go)
    {
        useCardList.Remove(go);
        go.transform.SetParent(transform.root);//父级修改为当前对象所在的最顶层父对象,确保卡牌UI在最顶层显示
        Card card = go.GetComponent<Card>();
        card.isMoving = true;
        card.hasUse = false;
        go.transform.DOMove(transform.position, 0.8f).OnComplete(
            () =>
            {
                go.transform.SetParent(transform, false);
                //移动到其父对象的子物体列表的最前面
                go.transform.SetAsFirstSibling();
                card.isMoving = false;
            }
        );
    }
}

Card代码

public class Card : MonoBehaviour, IDragHandler, IBeginDragHandler, IEndDragHandler, IPointerClickHandler
{
    [HideInInspector] public CardItem cardItem;//卡牌信息
    [HideInInspector] public bool hasUse = false;//是否使用
    [HideInInspector] public bool isMoving = false;//是否正在移动
    private GameObject darkBg;//黑图
    private Image image;
    private GameObject progressBar;//进度对象
    private float waitTime; //等待时间
    private int useSun;//需要阳光
    private GameObject prefab;//预制体
    public LayerMask layerMask;//检测图层
    private GameObject thisObject;


    void Start()
    {
        darkBg = transform.Find("黑色底图").gameObject;
        progressBar = transform.Find("黑色进度").gameObject;
        image = progressBar.GetComponent<Image>();

        Init();
    }

    private void Init()
    {
        if (cardItem == null)
        {
            Debug.Log("找不到卡牌数据");
            return;
        }
        darkBg.SetActive(false);
        image.fillAmount = 0;
        GetComponent<Image>().sprite = cardItem.sprite;
        gameObject.name = cardItem.name;
        waitTime = cardItem.waitTime;
        useSun = cardItem.useSun;
        prefab = cardItem.prefab;
    }

    void Update()
    {
        if(!GameManager.Instance.isStart) return;
        UpdateProgress();
        UpdateDarkBg();
    }

    void UpdateProgress()
    {
        image.fillAmount -= 1 / waitTime * Time.deltaTime;
    }

    void UpdateDarkBg()
    {
        // TODO 且需要阳光数>useSun
        if (image.fillAmount == 0 && GameManager.Instance.sunSum >= useSun)
        {
            darkBg.SetActive(false);
        }
        else
        {
            darkBg.SetActive(true);
        }
    }

    //开始拖拽
    public void OnBeginDrag(PointerEventData eventData)
    {
        if(!GameManager.Instance.isStart) return;
        if (image.fillAmount != 0 || GameManager.Instance.sunSum < useSun) return;
        thisObject = Instantiate(prefab, transform.position, Quaternion.identity);

        //关闭运行
        thisObject.GetComponent<Plants>().isOpen = false;
        //关闭碰撞
        thisObject.GetComponent<Collider2D>().enabled = false;
        //关闭动画
        thisObject.GetComponent<Animator>().enabled = false;
    }

    //拖拽中
    public void OnDrag(PointerEventData eventData)
    {
        if (thisObject == null) return;
        //植物跟随鼠标
        Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        thisObject.transform.position = new Vector2(mousePosition.x, mousePosition.y); // 将物体位置设置为鼠标位置
    }

    //拖拽结束
    public void OnEndDrag(PointerEventData eventData)
    {
        if (thisObject == null) return;
        Vector3 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        Vector2 mousePosition2D = new Vector2(mousePosition.x, mousePosition.y);
        RaycastHit2D hit = Physics2D.Raycast(mousePosition2D, Vector2.zero, Mathf.Infinity, layerMask);//鼠标“点检测”射线
        if (hit.collider != null && hit.collider.transform.childCount == 0)
        {
            thisObject.transform.parent = hit.transform;
            thisObject.transform.localPosition = Vector2.one;

            //开始运行
            thisObject.GetComponent<Plants>().isOpen = true;
            //开启碰撞
            thisObject.GetComponent<Collider2D>().enabled = true;
            //开启动画
            thisObject.GetComponent<Animator>().enabled = true;

            thisObject = null;
            //重置进度
            progressBar.GetComponent<Image>().fillAmount = 1;

            //扣除阳光
            GameManager.Instance.SetSunSum(-useSun);

            AudioManager.Instance.PlaySFX("种植");
        }
        else
        {
            Destroy(thisObject);
        }

    }

    //点击事件
    public void OnPointerClick(PointerEventData eventData)
    {
        if (isMoving) return;
        if(GameManager.Instance.isStart) return;
        if (hasUse)
        {
            ChooseCardPanel.Instance.RemoveCard(gameObject);
        }
        else
        {
            ChooseCardPanel.Instance.AddCard(gameObject);
        }
    }
}

配置
在这里插入图片描述
效果
在这里插入图片描述

开始战斗

修改GameManager,定义开始游戏事件,记得去除原来生成阳光和生成僵尸的代码

//开始游戏
public void StartGame(){
    AudioManager.Instance.PlayMusic("bgm1");
    isStart = true;
    //顶部随机位置掉落阳光
    InvokeRepeating("CreateSunDown", 10, 10);

    //生成僵尸
    GenerateZombies.Instance.TableCreateZombie();
} 

配置
在这里插入图片描述
效果测试,一切正常
在这里插入图片描述

源码

源码不出意外的话我会放在最后一节

结束语

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,最近开始自学unity,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!php是工作,unity是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~

在这里插入图片描述

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

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

相关文章

3D处理关键点检测之ISS(内蕴形状特征)

定义 ISS(Intrinsic Shape Signatures)是由Yu Zhong于2009年提出的一种三维形状描述子&#xff0c;用于描述局部或者半局部区域的点云&#xff0c;局部区域可以理解为以一个点云中某点为球心&#xff0c;以一定半径构成的可以包含多个内点的球形区域&#xff0c;半局部则是半个…

linux驱动学习(十)之内存管理

一、linux内核启动过程中&#xff0c;关于内存信息 1、内核的内存的分区 [ 0.000000] Memory: 1024MB 1024MB total ---> 1G [ 0.000000] Memory: 810820k/810820k available, 237756k reserved, 272384K highmem [ 0.000000] Virtual kernel memory layout: 内…

鸿蒙元服务未来是能一“通”多端的前端形态?

2024年&#xff0c;华为鸿蒙的热度只增不减。 在2023年底就有业内人士透露&#xff0c;华为明年将推出不兼容安卓的鸿蒙版本&#xff0c;未来IOS、鸿蒙、安卓将成为三个各自独立的系统。 果不其然&#xff0c;执行力超强的华为&#xff0c;与2024年1月18日的开发者&#xff0…

Pixi.js学习 (六)数组

目录 前言 一、数组 1.1 定义数组 1.2 数组存取与删除 1.3 使用数组统一操作敌机 二、实战 例题一&#xff1a;使用数组统一操作敌机 例题一代码&#xff1a; 总结 前言 为了提高作者的代码编辑水品&#xff0c;作者在使用博客的时候使用的集成工具为 HBuilderX。 下文所有截…

LabVIEW故障预测

在LabVIEW故障预测中&#xff0c;振动信号特征提取的关键技术主要包括以下几个方面&#xff1a; 时域特征提取&#xff1a;时域特征是直接从振动信号的时间序列中提取的特征。常见的时域特征包括振动信号的均值、方差、峰值、峰-峰值、均方根、脉冲指数等。这些特征能够反映振动…

如何在Python中向Word文档插入图片

如何在Python中向Word文档插入图片 向Word文档插入图片添加前和添加后 在这篇博客文章中&#xff0c;我们使用Python向Word文档插入图片。通过本文&#xff0c;您将学习如何在文档中插入图片&#xff0c;并调整其大小和位置。 向Word文档插入图片 from docx import Document …

Redis脑裂问题详解及解决方案

Redis脑裂问题 Redis脑裂问题是指在主从集群中同时存在两个主节点&#xff0c;这会导致不同客户端往不同的主节点写入数据&#xff0c;最终导致数据不一致&#xff0c;甚至数据丢失。 哨兵主从集群脑裂 场景描述 假设有三台服务器&#xff1a;一台主服务器&#xff0c;两台…

据阿谱尔统计显示,2023年全球电动汽车充电设备市场价值为62.1亿美元

根据阿谱尔 (APO Research&#xff09;的统计及预测&#xff0c;2023 年全球电动汽车充电设备市场价值为 62.1 亿美元&#xff0c;预计到 2030 年将达到 391.1 亿美元&#xff0c;预测期内&#xff08;2024-2030 年&#xff09;复合年增长率为 31.47%。 电动汽车 (EV) 充电设备…

如何有效处理服务器后台密码暴露

服务器后台密码的暴露是信息安全领域中的严重事件&#xff0c;它可能引发未经授权的数据访问、恶意软件植入或系统功能滥用等一系列问题。本文将探讨几种处理服务器后台密码暴露的有效策略&#xff0c;包括紧急响应步骤、密码安全增强措施及长期预防机制&#xff0c;并提供实际…

Unity射击游戏开发教程:(27)创建带有百分比的状态栏

创建带有弹药数和推进器百分比的状态栏 在本文中,我将介绍如何创建带有分数和百分比文本的常规状态栏。 由于 Ammo Bar 将成为 UI 的一部分,因此我们需要向 Canvas 添加一个空的 GameObject 并将其重命名为 AmmoBar。我们需要一个文本和两个图像对象,它们是 AmmoBar 的父级。…

如何使用 Vue.js 和 Java 开发一个完整的 Web 应用

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

测试基础12:测试用例设计方法-边界值分析

课程大纲 1、定义 经验发现&#xff0c;较多的错误往往发生在输入或输出范围的边界上&#xff0c;因为边界值是代码判断语句的点&#xff0c;一般容易出问题&#xff08;数值写错、多加或丢失等号、写错不等号方向…&#xff09;。所以增加对取值范围的边界数据的测试&#xff…

使引用作为函数参数将变量i和j的值互换

C之所以增加引用机制&#xff0c;主要是把它作为函数参数&#xff0c;以扩充函数传递数据的功能。 解题思路&#xff1a; 传递变量的地址。形参是指针变量&#xff0c;实参是一个变量的地址&#xff0c;调用函数时&#xff0c;形参&#xff08;指针变量&#xff09;得到实参变…

AI大模型时代:程序员如何面对“焦虑”并抓住“风口”

前言 最近&#xff0c;我和不少程序员朋友聊起了全网都在热议的大模型&#xff0c;如ChatGPT、GPT-4、文心一言等。大家的反应出乎我意料&#xff0c;普遍感到焦虑和迷茫。 这些大模型的出现&#xff0c;确实令人兴奋&#xff0c;仿佛一夜之间&#xff0c;AI就能做很多事情&…

专家解读 | NIST网络安全框架(3):层级配置

NIST CSF在核心部分提供了六个类别的关键功能和子功能&#xff0c;并围绕CSF的使用提供了层级&#xff08;Tier&#xff09;和配置&#xff08;Profile&#xff09;两种工具&#xff0c;使不同组织和用户更方便有效地使用CSF&#xff0c;本文将深入探讨CSF层级和配置的主要内容…

【deepin 产品面对面】玲珑入门教程:从源代码开始构建玲珑格式应用

内容来源&#xff1a;deepin&#xff08;深度&#xff09;社区 请首先阅读玲珑官方文档 ll-builder 简介 | 玲珑&#xff0c;本文以构建 desktop-entry-editor 为例&#xff0c;该项目依赖较为简单&#xff0c;仅需玲珑官方文档中默认提供的基础运行环境即可成功构建运行。 第一…

web基础htTP协议

web基础 域名概述&#xff1a; 域名空间结构 网页的概念 HTML概述 DNS解析的三种方式 /etc/hosts 在Linux系统中&#xff0c;/etc/hosts 文件负责快速解析&#xff0c;它包含了IP地址与主机名的映射关系。在没有DNS服务器的情况下&#xff0c;可以使用本地/etc/hosts 文件完成…

用户管理与服务器远程管理

用户管理 服务器系统版本介绍 windows服务器系统&#xff1a;win2000 win2003 win2008 win2012 linux服务器系统&#xff1a;Redhat Centos 用户管理 用户概述 &#xff08;1&#xff09;每一个用户登录系统后&#xff0c;拥有不同的操作权限。 &#xff08;2&#xff09;…

【实例分享】访问后端服务超时,银河麒麟服务器操作系统分析及处理建议

1.服务器环境以及配置 【机型】 处理器&#xff1a; Intel 32核 内存&#xff1a; 128G 整机类型/架构&#xff1a; x86_64虚拟机 【内核版本】 4.19.90-25.22.v2101.kylin.x86_64 【OS镜像版本】 kylin server V10 SP2 【第三方软件】 开阳k8s 2.问题现象描述 …

三十二、 数据跨境传输场景下的 PIA 与数据出境风险自评估是一回事吗?

PIA 与数据出境风险自评估并不相同。PIA 是《个人信息保护法》第五十五条明确提出要求企业在向境外提供个人信息前应当开展的自评估工作&#xff0c;而数据出境风险自评估则是《评估办法》第五条提出的要求符合数据出境安全评估申报情形的企业在申报前应当开展的自评估工作。 换…