麦田物语第十九天

news2024/12/26 11:33:05

系列文章目录

麦田物语第十九天


文章目录

  • 系列文章目录
  • 一、保存和加载场景中的物品
  • 二、设置鼠标指针根据物品调整


一、保存和加载场景中的物品

本小节我们想要解决一个问题,就是当我们跳转场景后,在返回之前场景,发现场景中被我们拾取的物品都重新出现了,这样肯定是不行的,现在我们来解决这个问题。
我们解决这个问题的思路就是我们在切换场景之前将当前场景中的物品保存,然后再次回到该场景时将场景中保存的物品重新加载(拾取之后的物品就不会被重新加载了)。
思路有了,现在我们进行代码方面的实现。
我们游戏中物品生成是在ItemManager脚本中实现的,我们给这个脚本一个编号,就可以生成对应的物品,那我们来到ItemManager脚本开始编写代码。
接着我们需要考虑怎么存储这个物品信息,因为我们不是只有一个场景啊,所以我们需要将场景和其对应的物品信息存储起来,没错,我们可以使用字典来实现存储功能,键对应的就是场景名称,值对应的就是每个场景的物品信息List数组;但是我们还会想到一个问题,物品信息包括很多东西:编号,位置等,我们还要重新编写一个数据结构去作为物品信息变量。
那我们首先编写DataCollection脚本,在我们实现的class类中,我们必须要序列化物品坐标,只有这样我们才能保存物品的位置;那么这里面肯定需要的变量是float类型的x,y,z坐标,接着我们需要编写这个类的构造函数,这个构造函数的参数是一个Vector3类型的位置变量,我们通过对x,y,z坐标序列化。然后我们还需要一个函数将保存的坐标传递出去,即返回类型为Vector3的值。最后我们做的是2D游戏,使用的是Grid Cell都是整型的,我们可以使用VectorInt类型的值作为函数的返回类型将x,y的值传递出去。

DataCollection脚本的SerializableVector3类代码如下:

[System.Serializable]
public class SerializableVector3
{
    public float x, y, z;

    public SerializableVector3(Vector3 pos)
    {
        this.x = pos.x;
        this.y = pos.y;
        this.z = pos.z;
    }

    public Vector3 ToVector3()
    {
        return new Vector3(x, y, z);
    }

    public Vector2Int ToVector2Int()
    {
        return new Vector2Int((int)x, (int)y);
    }
}

还记得我们所说的物品信息都有什么吗,除了物品的位置,就是物品的编号了,我们也在编写一个新的Class类来存储物品信息。

DataCollection脚本的SceneItem类代码如下:

[System.Serializable]
public class SceneItem
{
    public int itemID;
    public SerializableVector3 position;
}

现在我们的准备工作做好了,就可以返回ItemManager脚本编写物品存储和物品读取的方法了。
首先我们先要声明字典变量,用来存储物品信息,字典变量的声明如下:

//记录场景物品,
        private Dictionary<string, List<SceneItem>> sceneItemDict = new Dictionary<string, List<SceneItem>>();

然后编写获得场景中所有物品的方法GetAllSceneItems,首先我们要声明一个变量currentSceneItems(List数组),用来存储当前场景中的所有物品信息,然后我们遍历该场景中所有挂载Item脚本的物品,将所有物品的信息存储到List数组中;最后我们需要将这个List数组在字典中进行更新,为什么是更新不是添加呢,因为我们可能进入的是新的场景,那这个场景的物品信息可能没有添加到字典中,所以我们需要先判断,如果当前字典中没有这个场景的名字,代表该场景的物品信息还没有存入字典,我们就直接在字典中增加新的键值对,如果该字典中有该场景的名字,那么我们将该键所对应的值进行重新赋值,达到所谓更新的效果。

ItemManager脚本的GetAllSceneItems方法代码如下:

/// <summary>
        /// 收集场景中的所有物品信息
        /// </summary>
        private void GetAllSceneItems()
        {
            List<SceneItem> currentSceneItems = new List<SceneItem>();

            //遍历找到场景中的每一个挂载Item脚本的物体
            foreach (var item in FindObjectsOfType<Item>())
            {
                SceneItem sceneItem = new SceneItem
                {
                    itemID = item.itemID,
                    position = new SerializableVector3(item.transform.position)
                };

                currentSceneItems.Add(sceneItem);
            }
                
            //判断是否该场景中已经存储了物品信息,如果存储了,那么更新这个存储的信息,如果为新的场景,那么将物品信息添加到字典中
            if (sceneItemDict.ContainsKey(SceneManager.GetActiveScene().name))
            {
                sceneItemDict[SceneManager.GetActiveScene().name] = currentSceneItems;
            }
            else
            {
                sceneItemDict.Add(SceneManager.GetActiveScene().name, currentSceneItems);
            }
        }

接着我们需要在跳转场景前执行这个方法就可以了,我们除了这个功能之外,还要在跳转场景之后将所有的物品都加载出来的功能。
我们编写RecreateAllItems方法,用来重新创建该场景的物品。我们首先新建一个List< SceneItem >数组,但是我们其实还有一个问题就是如果我们在字典中找不到该场景的物品信息嘞,为了防止报空,我们使用字典的TryGetValue方法,这个方法会返回一个bool值,并且如果返回值为true(字典中有当前场景的物品),那么会将物品信息数组赋值到新建的数组中,所以我们需要判断该数组是否为空,如果不为空的话,则代表我们当前保存了该场景的物品信息,所以我们要先清场,将所有挂载Item脚本的物品销毁;接着我们就根据数组中的物品信息将物品重新生成就可以了。

ItemManager脚本的RecreateAllItems方法代码如下:

/// <summary>
        /// 刷新重建当前场景
        /// </summary>
        private void RecreateAllItems()
        {
            List<SceneItem> currentSceneItems = new List<SceneItem>();

            if (sceneItemDict.TryGetValue(SceneManager.GetActiveScene().name, out currentSceneItems))
            {
                if (currentSceneItems != null)
                {
                    //清空所有的物品
                    foreach (var item in FindObjectsOfType<Item>())
                    {
                        Destroy(item.gameObject);
                    }

                    foreach (var item in currentSceneItems)
                    {
                        Item newItem = Instantiate(itemPrefab, item.position.ToVector3(), Quaternion.identity, itemParent);
                        newItem.Init(item.itemID);
                    }
                }
            }
        }

我们也是需要在加载场景后的方法中调用RecreateAllItems方法即可。

ItemManager脚本新添加的代码如下:

namespace MFarm.Inventory
{
    public class ItemManager : MonoBehaviour
    {
        public Item itemPrefab;
        private Transform itemParent;

        //记录场景物品,
        private Dictionary<string, List<SceneItem>> sceneItemDict = new Dictionary<string, List<SceneItem>>();


        private void OnEnable()
        {
            EventHandler.InstantiateItemInScene += OnInstantiateItemInScene;
            EventHandler.BeforeSceneUnloadEvent += OnBeforeSceneUnloadEvent;
            EventHandler.AfterSceneLoadedEvent += OnAfterSceneLoadedEvent;
        }

        

        private void OnDisable()
        {
            EventHandler.InstantiateItemInScene -= OnInstantiateItemInScene;
            EventHandler.BeforeSceneUnloadEvent -= OnBeforeSceneUnloadEvent;
            EventHandler.AfterSceneLoadedEvent -= OnAfterSceneLoadedEvent;
        }

        private void OnBeforeSceneUnloadEvent()
        {
            GetAllSceneItems();
        }

        private void OnAfterSceneLoadedEvent()
        {
            itemParent = GameObject.FindWithTag("ItemParent").transform;
            RecreateAllItems();
        }


        /// <summary>
        /// 收集场景中的所有物品信息
        /// </summary>
        private void GetAllSceneItems()
        {
            List<SceneItem> currentSceneItems = new List<SceneItem>();

            //遍历找到场景中的每一个挂载Item脚本的物体
            foreach (var item in FindObjectsOfType<Item>())
            {
                SceneItem sceneItem = new SceneItem
                {
                    itemID = item.itemID,
                    position = new SerializableVector3(item.transform.position)
                };

                currentSceneItems.Add(sceneItem);
            }
                
            //判断是否该场景中已经存储了物品信息,如果存储了,那么更新这个存储的信息,如果为新的场景,那么将物品信息添加到字典中
            if (sceneItemDict.ContainsKey(SceneManager.GetActiveScene().name))
            {
                sceneItemDict[SceneManager.GetActiveScene().name] = currentSceneItems;
            }
            else
            {
                sceneItemDict.Add(SceneManager.GetActiveScene().name, currentSceneItems);
            }
        }

        /// <summary>
        /// 刷新重建当前场景
        /// </summary>
        private void RecreateAllItems()
        {
            List<SceneItem> currentSceneItems = new List<SceneItem>();

            if (sceneItemDict.TryGetValue(SceneManager.GetActiveScene().name, out currentSceneItems))
            {
                if (currentSceneItems != null)
                {
                    //清空所有的物品
                    foreach (var item in FindObjectsOfType<Item>())
                    {
                        Destroy(item.gameObject);
                    }

                    foreach (var item in currentSceneItems)
                    {
                        Item newItem = Instantiate(itemPrefab, item.position.ToVector3(), Quaternion.identity, itemParent);
                        newItem.Init(item.itemID);
                    }
                }
            }
        }
    }
}

Destroy物品时记得一定要删除其物品的gameobject。

运行Unity,你会发现切换场景之后,会重新生成物品,但是这个物品还是会删一下,你应该已经想到了,是调用顺序的问题,我们可以将进入新场景的通知在加载界面消失之前调用即可。

TransitionManager脚本的更改如下:

private IEnumerator Transition(string sceneName, Vector3 targetPosition)
        {
            EventHandler.CallBeforeSceneUnloadEvent();

            yield return Fade(1);

            yield return SceneManager.UnloadSceneAsync(SceneManager.GetActiveScene());

            yield return LoadSceneSetActive(sceneName);

            //切换场景之后需要将角色的坐标移动到合适的位置啦
            EventHandler.CallMoveToPosition(targetPosition);

            EventHandler.CallAfterSceneLoadedEvent();

            yield return Fade(0);
        }

二、设置鼠标指针根据物品调整

本小节我们来设置鼠标图片的切换,当我们进行浇水,建造,种植等事情时,鼠标会有不同的图标。
首先我们需要导入鼠标图片的素材包,导入之后调整所有图片的最大尺寸,以及Pixels Per Unit等参数的大小,参数如下所示。
鼠标图片参数更改
其实我们可以更改图片的类型(Texture Type)为Cursor,可直接设置为鼠标指针,但是如果这么做的话,我们则无法改变Cursor的大小,但是我们如果希望Cursor有别的效果:缩小,切换图片,动画等,那么就还是不这么干比较好。
我们将Fade Panel的父物体改名为Cursor Canvas,并给其添加子物体Image以及给这个Canvas 添加 Cursor Canvas的标签;然后将cursor(11)作为Image的Source Image,调整Image的大小,高度及锚点位置等参数,使其刚好和鼠标指向的位置相同。

Cursor Canvas的参数构造如下:
Cursor Canvas的参数构造
Cursor Image的参数如下:
Cursor Image的参数

接着我们在PersistentScene场景中添加空物体CursorManager,并创建Scripts->Cursor->CursorManager脚本并添加到这个空物体上。
然后我们来编辑CursorManager脚本,首先声明需要使用的图片,例如正常图片,使用工具鼠标图片,使用种子鼠标图片,还需要一个变量currentSprite用来存储当前使用的鼠标图片,接着需要定义拿到Cursor Image组件的变量cursorImage,最后我们还需要声明其父物体的位置cursorCanvas,并在Start方法中获得这个组件的位置(通过标签)。记得返回Unity将变量进行赋值。

UI组件的Transfom组件都是Rect Transform。

接着我们可以通过父物体获取到子物体Cursor Image(记得看把Cursor Image放到其第一个子物体的位置)。
然后我们编写SetCursorImage方法改变鼠标的图片显示并设置其颜色完全显示即可。(当鼠标不能使用时切换为红色的半透明,这个功能之后可以实现),然后在Start方法中将鼠标设置为normal图标。接着我们也在Update方法中使Image跟随鼠标移动(Image的位置我们之前已经设置了,所以现在我们鼠标点击的位置就是鼠标图片点击的位置)

CursorManager脚本的SetCursorImage方法代码如下:

private void SetCursorImage(Sprite sprite)
    {
        cursorImage.sprite = sprite;
        cursorImage.color = new Color(1, 1, 1, 1);
    }

我们选择背包物品时鼠标就会切换成相应的图标,这是就需要使用EventHandler的事件了,我们调用选中背包UI的事件,编写OnItemSelectedEvent方法;我们使用新的语法糖来写,根据itemDetails的属性来选择当前的鼠标图片属性currentSprite,同时我们还要对isSelected变量进行判断,如果选中UI的话,那么可以进行语法糖的使用,如果为false,那么将currentSprite设置为normal图片即可。

OnItemSelectedEvent方法代码如下:

private void OnItemSelectedEvent(ItemDetails itemDetails, bool isSelected)
    {
        if (!isSelected)
        {
            currentSprite = normal;
        }
        else
        {
            //添加所有类型对应图片
            currentSprite = itemDetails.itemType switch
            {
                ItemType.Seed => seed,
                ItemType.Commodity => item,
                ItemType.ChopTool => tool,
                _ => normal
            };
        }
    }

因为我们的这个更换是UI被选择之后,所以我们需要在Update方法中实时调用SetCursorImage方法从而实现鼠标图片可以实时切换。
最后我们选中物品后,在与其他UI互动时,还是应该使用normal而不是使用其他的图标,这个我们编写InteractWithUI方法(返回值为bool类型),同时添加新的命名空间using UnityEngine.EventSystems;首先我么你需要判断EventSystem是否为空并且是否跟UI的物品有互动,如果不为空并且与UI发生互动,那么返回true,反之返回false。
我们还要在Update方法中调用和这个方法,如果为true,那么直接设置鼠标图片为normal,反之才试试切换鼠标图片。

CursorManager脚本的代码如下:

public class CursoeManager : MonoBehaviour
{
    public Sprite normal, tool, seed, item;

    //存储当前图片
    private Sprite currentSprite;
    private Image cursorImage;
    private RectTransform cursorCanvas;

    private void Start()
    {
        cursorCanvas = GameObject.FindGameObjectWithTag("CursorCanvas").GetComponent<RectTransform>();
        cursorImage = cursorCanvas.GetChild(0).GetComponent<Image>();

        currentSprite = normal;
        SetCursorImage(normal);
    }

    private void Update()
    {
        if (cursorImage == null) return;
        cursorImage.transform.position = Input.mousePosition;

        if (!InteractWithUI())
        {
            SetCursorImage(currentSprite);
        }
        else
        {
            SetCursorImage(normal);
        }
    }

    private void SetCursorImage(Sprite sprite)
    {
        cursorImage.sprite = sprite;
        cursorImage.color = new Color(1, 1, 1, 1);
    }

    private void OnEnable()
    {
        EventHandler.ItemSelectedEvent += OnItemSelectedEvent;
    }

    

    private void OnDisable()
    {
        EventHandler.ItemSelectedEvent -= OnItemSelectedEvent;
    }

    private void OnItemSelectedEvent(ItemDetails itemDetails, bool isSelected)
    {
        if (!isSelected)
        {
            currentSprite = normal;
        }
        else
        {
            //添加所有类型对应图片
            currentSprite = itemDetails.itemType switch
            {
                ItemType.Seed => seed,
                ItemType.Commodity => item,
                ItemType.ChopTool => tool,
                _ => normal
            };
        }
    }

    /// <summary>
    /// 判断是否跟UI互动
    /// </summary>
    /// <returns></returns>
    private bool InteractWithUI()
    {
        if (EventSystem.current != null && EventSystem.current.IsPointerOverGameObject())
        {
            return true;
        }
        else
            return false;
    }
}

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

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

相关文章

md文件转doc文件

目录 起因 实践 python方式安装 安装包安装 转换 后记 起因 近期需要提交一些文件出去&#xff0c;一般都是要word或pdf版的&#xff0c;但是手头只有md格式的&#xff0c;于是需要将md转为doc 实践 问了下度娘&#xff0c;pandoc是个不错的方法&#xff0c;可以通过下…

【C++】:错误处理机制 -- 异常

目录 前言一&#xff0c;C语言传统的处理错误的方式二&#xff0c;C异常的概念三&#xff0c;异常的使用3.1 异常的抛出和匹配原则3.2 在函数调用链中异常栈展开匹配原则3.3 异常的重新抛出3.4 异常规范 四&#xff0c;自定义异常体系五&#xff0c;异常的优缺点 点击跳转至文章…

经典⾯试题,循环中使⽤闭包解决 var 定义函数的问题

⾸先因为 setTimeout 是个异步函数&#xff0c;所有会先把循环全部执⾏完毕&#xff0c;这时候 i 就是 5了&#xff0c;所以会输出6个 5。 解决办法两种&#xff0c;第一种使用闭包 &#xff1a; 第⼆种就是使用 setTimeout 的第三个参数&#xff1a; 第三种就是使用 let 定义 …

Power功效分析之方差原理及案例教程

Power功效分析常用于实验研究时样本量的计算&#xff08;或功效值计算&#xff09;&#xff0c;实验研究中进行方差分析的情况较多&#xff0c;在SPSSAU中单独将方差放成一个计算Power的方法&#xff0c;其具体包括单因素方差/双因素方差和多因素方差&#xff0c;具体如下表格所…

Callable 与 Runnable:多线程编程中的两大接口对比

Callable 与 Runnable&#xff1a;多线程编程中的两大接口对比 1、主要区别1.1 返回值1.2 使用方式 2、适用场景3、示例 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Java多线程编程中&#xff0c;Callable和Runnable是两个核心接口&am…

如何打造 BeatBuddy:一款分析你的 Spotify 数据的 Web 应用

欢迎来到雲闪世界&#xff01;我将解释我是如何构建 BeatBuddy 的&#xff0c;这是一款分析您在 Spotify 上收听内容的网络应用。受 Spotify Wrapped 的启发&#xff0c;它旨在解读您当前的心情并提供您可以根据该分析进行调整的建议。 如果你不想阅读所有内容&#xff0c;只想…

未授权访问漏洞系列

环境 1.此漏洞需要靶场vulhub&#xff0c;可自行前往gethub下载 2.需要虚拟机或云服务器等linux系统&#xff0c;并在此系统安装docker和docker-compose提供环境支持 3.运行docker-compose指令为docker-compose up -d即可运行当前目录下的文件 Redis未授权访问漏洞 一、进…

流程挖掘,为光伏企业重塑确定的竞争力

2023年12月15日&#xff0c;2023中资光伏出海大会在江苏南京隆重举行。国内领先的流程挖掘服务商望繁信科技应邀出席大会&#xff0c;与来自海内外的重要领导、重磅嘉宾、行业大咖齐聚一堂&#xff0c;聚焦“数字化、全球化、本地化”&#xff0c;共同探讨中资光伏企业的出海机…

windows11 DNS手动配置过DNS,在恢复成自动获取后,无法自动获取到DNS,网卡里面DNS还是显示之前手动配置的DNS

windows11 DNS一开始手动配置过DNS,然后在恢复成自动获取后&#xff0c;网卡无法自动获取到DNS&#xff0c;并且网卡里面DNS显示还是之前手动配置的DNS。 系统版本&#xff1a;windows11 企业版、版&#xff1a;10.0.22621 版本 22621 解决办法&#xff1a; 注册表“HKEY_LO…

RHEL9网络设定及网络脚本

1. 添加一张网卡 2. 重写一个网卡配置文件 [rootlocalhost ~]# cd /etc/NetworkManager/system-connections/ [rootlocalhost system-connections]# ls ens160.nmconnection [rootlocalhost system-connections]# vim ens224.connection [rootlocalhost system-connections…

大模型应用(六)如何写好一个prompt,理论+实践

前言 设定是四个基本要素之一&#xff0c;没有一个好的prompt&#xff0c;就绝对不可能有一个好的agent。 如何写prompt的大部分是我粘的&#xff0c;实在是懒得写了。不感兴趣可以直接跳实战。 什么是prompt 大语言模型&#xff08;LLM&#xff09;的能力并不是被设计出来…

【数据结构与算法】迷宫求解------回溯法

回溯法 一.迷宫求解算法二.二维数组表示地图1.地图2.初始化地图3.地图的打印 三.进入迷宫四.栈的实现五.迷宫内探1.首先判断我们的入口2.入栈做标记3.开始探险4.出口判断5.能否下一步6.做标记7.不能下一步 六.运行结果 一.迷宫求解算法 当我们想要找到迷宫的出口,那我们在计算…

为什么网站要使用HTTPS访问

网站使用HTTPS访问的原因有很多&#xff0c;主要可以归纳为以下几个关键点&#xff1a; 1、数据安全性&#xff1a;HTTPS使用SSL/TLS协议对通信过程进行加密&#xff0c;确保信息在传输过程中不被窃取、篡改或冒充。对于涉及敏感信息&#xff08;如个人身份、信用卡号等&#x…

AnyGPT: Unified Multimodal LLM with Discrete Sequence Modeling

发表时间&#xff1a;arXiv 2024年2月26日 论文链接&#xff1a;https://arxiv.org/pdf/2402.12226 作者单位&#xff1a; Fudan University Motivation&#xff1a; LLM 在理解和生成人类语言方面表现出非凡的能力。但是&#xff0c;LLM 的能力仅限于针对文本的处理。而现实…

JVM系列 | 对象的消亡2——HotSpot的设计细节

HotSpot 的细节实现 文章目录 HotSpot 的细节实现OopMap 与 根节点枚举根节点类型及说明HotSpot中的实现 OopMap 与 安全点安全点介绍如何保证程序在安全点上&#xff1f; 安全区域记忆集与卡表记忆集卡表 写屏障并发的可达性分析&#xff08;与用户线程&#xff09;并发可达性…

Spring boot框架指南

1. Spring Boot 概述 1.1 定义与起源 Spring Boot是一种基于Spring框架的开源框架&#xff0c;旨在简化Spring应用程序的创建和开发过程。它通过提供一系列默认配置和自动配置功能&#xff0c;减少了开发者在配置上的工作量&#xff0c;使得快速搭建生产级别的Spring应用程序…

OV SSL证书优势及获取渠道

OV证书&#xff0c;即组织验证型SSL证书&#xff0c;通过严格的组织审查流程&#xff0c;为网站提供数据传输加密、身份验证和信息完整性保护。 OV证书优势 1 高信任度 OV证书通过证书颁发机构&#xff08;CA&#xff09;对企业实名认证&#xff0c;包括企业名称、注册地址、…

万能门店小程序开发平台功能源码系统 带完整的安装代码包以及安装搭建教程

互联网技术的迅猛发展和用户对于便捷性需求的不断提高&#xff0c;小程序以其轻量、快捷、无需安装的特点&#xff0c;成为了众多商家和开发者关注的焦点。为满足广大商家对于门店线上化、智能化管理的需求&#xff0c;小编给大家分享一款“万能门店小程序开发平台功能源码系统…

kernel32.dll丢失?那么kernel32.dll如何修复?教你几种修复丢失kernel32.dll错误的方法

在使用电脑时你是否遇到过kernel32.dll丢失的情况&#xff0c;那么遇到这种情况应该如何解决呢&#xff1f;遇到kernel32.dll丢失就会导致电脑无法正常运行&#xff0c;应用程序也会无法正常使用&#xff0c;今天就教大家kernel32.dll丢失的解决办法。 几种解决kernel32.dll丢失…

破解USB设备通讯协议实现自定义软件控制的步骤与方法

在设备和计算机之间通过USB进行通讯的情况下&#xff0c;厂家提供的软件可以控制设备&#xff0c;但没有提供任何其他资料和支持&#xff0c;这种情况下&#xff0c;若希望自行开发软件来实现同样的功能&#xff0c;可以通过以下步骤破解通讯协议并开发自定义程序。 1. 捕获US…