麦田物语第十九天

系列文章目录

麦田物语第十九天



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

本小节我们想要解决一个问题,就是当我们跳转场景后,在返回之前场景,发现场景中被我们拾取的物品都重新出现了,这样肯定是不行的,现在我们来解决这个问题。
我们解决这个问题的思路就是我们在切换场景之前将当前场景中的物品保存,然后再次回到该场景时将场景中保存的物品重新加载(拾取之后的物品就不会被重新加载了)。
思路有了,现在我们进行代码方面的实现。
我们游戏中物品生成是在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;
    }
}
  • 12
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
麦田里的守望者》是美国作家J.D. Salinger所著的小说,该小说于1951年出版。该小说以第一人称叙述的方式,讲述了一位名叫霍尔顿·考尔菲尔德的十六岁男孩的故事,他在美国东部的一所寄宿学校度过了一个寒假。小说通过霍尔顿的视角,描绘了他的思想、情感和经历,反映了他对社会、人性和成长的认识与探索。 在近年来的研究中,学者们主要从以下几个方面对《麦田里的守望者》进行了文献综述: 1. 霍尔顿的反叛心理。许多学者探讨了霍尔顿的反叛心理,认为他的反叛是对社会价值观的质疑和对现实生活的不满。他试图保持自己的纯真和真实,而对于社会的虚伪和不公,他感到无法接受。 2. 霍尔顿的孤独与心理问题。一些学者认为,霍尔顿的孤独和心理问题是导致他反叛的原因之一。他对周围的人和事都持怀疑态度,感到难以融入社会。同时,他也存在着一些心理问题,如焦虑、抑郁等,这些问题影响着他的生活和行为。 3. 霍尔顿的成长与人性观。还有一些学者认为,霍尔顿的故事是一种成长与人性探索的过程。他在寻找自己的价值观和人生意义的过程中,经历了一系列的挫折和成长。他逐渐认识到人性的复杂性和社会的不完美性,也逐渐接受了自己的缺点和弱点。 4. 《麦田里的守望者》的文学价值。这部小说被认为是现代文学的经典之作,因为它深入地揭示了人类内心的复杂性和社会的种种问题。它的语言简洁、生动,情节紧凑,给人留下了深刻的印象。 综上所述,《麦田里的守望者》是一部充满探索与思考的小说,它通过一个十六岁男孩的视角,反映了现代社会中普遍存在的问题,也引发了人们对于成长、人性和社会的深入思考。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值