当前位置: 首页 > news >正文

Unity3D仿星露谷物语开发50之初始化农作物

1、目标

创建一个农作物实例化组件,这将使树木等农作物在设计时被添加到场景中,然后加载到网格的属性中。

这样农作物就不用像之前一样,由Player播散种子后等待慢慢长大。而是在进入游戏时,就有一些各个生长阶段的树木了。

2、修改EventHandler.cs脚本

添加一个新Event来实例化农作物预制体,我们会调用这个Event作为网格属性初始化的一部分。

// Instantiate crop prefabs
public static event Action InstantiateCropPrefabsEvent;public static void CallInstantiateCropPrefabsEvent()
{if(InstantiateCropPrefabsEvent != null){InstantiateCropPrefabsEvent();}
}

3、创建CropInstantiator.cs脚本

在Assets -> Scripts -> Crop下新建脚本命名为CropInstantiator.cs。

该脚本需要附件到预制体以设置网格属性字典中的值。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;/// <summary>
/// Attach to a crop prefab to set the values in the grid property dictionary
/// 主要作用:在场景中实例化农作物预制体,将相关属性数据存储到游戏的网格系统中。
/// 这个脚本的核心是将Inspector中设置的农作物属性数据传递到游戏的网格系统中,传递完成后这个临时示例就不再需要了
/// </summary>
public class CropInstantiator : MonoBehaviour
{private Grid grid;[SerializeField] private int daysSinceDug = -1;[SerializeField] private int daysSinceWatered = -1;[ItemCodeDescription][SerializeField] private int seedItemCode = 0;[SerializeField] private int growthDays = 0;private void OnEnable(){// 通过预制体挂载此脚本的方式,可以在是合理化时快速设置网格属性,而不需要复杂的手动配置过程。EventHandler.InstantiateCropPrefabsEvent += InstantiateCropPrefabs;}private void OnDisable(){EventHandler.InstantiateCropPrefabsEvent -= InstantiateCropPrefabs;}private void InstantiateCropPrefabs(){// Get grid gameobjectgrid = GameObject.FindObjectOfType<Grid>();// Get grid position for cropVector3Int cropGridPosition = grid.WorldToCell(transform.position);// Set Crop Grid PropertiesSetCropGridProperties(cropGridPosition);// Destroy this gameobject// 如果不销毁,场景中会残留大量仅用于数据传递的空壳对象Destroy(gameObject);}private void SetCropGridProperties(Vector3Int cropGridPosition){if(seedItemCode > 0){GridPropertyDetails gridPropertyDetails;gridPropertyDetails = GridPropertiesManager.Instance.GetGridPropertyDetails(cropGridPosition.x, cropGridPosition.y);if(gridPropertyDetails == null){gridPropertyDetails = new GridPropertyDetails();}gridPropertyDetails.daysSinceDug = daysSinceDug;gridPropertyDetails.daysSinceWatered = daysSinceWatered;gridPropertyDetails.seedItemCode = seedItemCode;gridPropertyDetails.growthDays = growthDays;GridPropertiesManager.Instance.SetGridPropertyDetails(cropGridPosition.x, cropGridPosition.y, gridPropertyDetails);}}}

工作流程:

  1. 开发者在 Unity 编辑器中创建农作物预制体,并设置好各项属性值
  2. 游戏运行时,当需要在场景中种植农作物时,实例化该预制体
  3. 预制体上的CropInstantiator脚本响应事件,将属性值写入网格系统
  4. 数据传递完成后,预制体自动销毁,只保留数据在系统中
  5. 游戏的农作物生长系统会从网格系统读取数据,控制农作物生长

4、修改SceneSave.cs脚本

确保只在第一次加载场景时实例化这些作物。

using System.Collections.Generic;[System.Serializable]public class SceneSave
{public Dictionary<string, bool> boolDictionary; // string key is an identifier name we choose for this list// string key is an identifier name we choose for this listpublic List<SceneItem> listSceneItem;public Dictionary<string, GridPropertyDetails> gridPropertyDetailsDictionary; // key是坐标信息,value是地面属性信息
}

5、修改GridPropertiesManager.cs脚本

1)属性变量添加如下内容:

private bool isFirstTimeSceneLoaded = true;

2)更新InitialiseGridProperties函数:

添加如下内容:

sceneSave.boolDictionary = new Dictionary<string, bool>();
sceneSave.boolDictionary.Add("isFirstTimeSceneLoaded", true);

然后我们可以在存储场景和恢复场景中使用这个来控制是否 是第一次场景加载或没有。

函数完整代码如下: 

private void InitialiseGridProperties()
{// loop through all gridproperties in the arrayforeach(SO_GridProperties so_GridProperties in so_gridPropertiesArray){// Create dictionary of grid property detailsDictionary<string, GridPropertyDetails> gridPropertyDictionary = new Dictionary<string, GridPropertyDetails>();// Populate grid property dictionary - Iterate through all the grid properties in the so gridproperties listforeach(GridProperty gridProperty in so_GridProperties.gridPropertyList){GridPropertyDetails gridPropertyDetails;gridPropertyDetails = GetGridPropertyDetails(gridProperty.gridCoordinate.x, gridProperty.gridCoordinate.y, gridPropertyDictionary);if(gridPropertyDetails == null){gridPropertyDetails = new GridPropertyDetails();}switch (gridProperty.gridBoolProperty){case GridBoolProperty.diggable:gridPropertyDetails.isDiggable = gridProperty.gridBoolValue;break;case GridBoolProperty.canDropItem:gridPropertyDetails.canDropItem = gridProperty.gridBoolValue;break;case GridBoolProperty.canPlaceFurniture:gridPropertyDetails.canPlaceFurniture = gridProperty.gridBoolValue;break;case GridBoolProperty.isPath:gridPropertyDetails.isPath = gridProperty.gridBoolValue;break;case GridBoolProperty.isNPCObstacle:gridPropertyDetails.isNPCObstacle = gridProperty.gridBoolValue;break;default:break; }SetGridPropertyDetails(gridProperty.gridCoordinate.x, gridProperty.gridCoordinate.y, gridPropertyDetails, gridPropertyDictionary);}// Create scene save for this gameobjectSceneSave sceneSave = new SceneSave();// Add grid property dictionary to scene save datasceneSave.gridPropertyDetailsDictionary = gridPropertyDictionary;// If starting scene set the griProertyDictionary member variable to the current iterationif(so_GridProperties.sceneName.ToString() == SceneControllerManager.Instance.startingSceneName.ToString()){this.gridPropertyDictionary = gridPropertyDictionary;}// Add bool dictionary and set first time scene loaded to truesceneSave.boolDictionary = new Dictionary<string, bool>();sceneSave.boolDictionary.Add("isFirstTimeSceneLoaded", true);// Add scene save to game object scene dataGameObjectSave.sceneData.Add(so_GridProperties.sceneName.ToString(), sceneSave);}
}

3)更新ISaveableRestoreScene函数

添加如下内容:

// get dictionary of bools - it exists since we created it in initialise
if(sceneSave.boolDictionary != null && sceneSave.boolDictionary.TryGetValue("isFirstTimeSceneLoaded", out bool storedIsFirstTimeSceneLoaded))
{isFirstTimeSceneLoaded = storedIsFirstTimeSceneLoaded;
}// Instantiate any crop prefabs iniitially present in the scene
if (isFirstTimeSceneLoaded)EventHandler.CallInstantiateCropPrefabsEvent();/ Update first time scene loaded bool
if(isFirstTimeSceneLoaded == true)
{isFirstTimeSceneLoaded = false;
}

 我们在第一次场景加载时使用这个存储值来触发这个农作物实例化,完整代码如下:

public void ISaveableRestoreScene(string sceneName)
{// Get sceneSave for scene - it exists since we created it in initialiseif(GameObjectSave.sceneData.TryGetValue(sceneName, out SceneSave sceneSave)){// get grid property details dictionary - it exists since we created it in initialiseif(sceneSave.gridPropertyDetailsDictionary != null){gridPropertyDictionary = sceneSave.gridPropertyDetailsDictionary;}// get dictionary of bools - it exists since we created it in initialiseif(sceneSave.boolDictionary != null && sceneSave.boolDictionary.TryGetValue("isFirstTimeSceneLoaded", out bool storedIsFirstTimeSceneLoaded)){isFirstTimeSceneLoaded = storedIsFirstTimeSceneLoaded;}// Instantiate any crop prefabs iniitially present in the sceneif (isFirstTimeSceneLoaded)EventHandler.CallInstantiateCropPrefabsEvent();// If grid properties existif(gridPropertyDictionary.Count > 0){// grid property details found for the current scene destroy existing ground decorationClearDisplayGridPropertyDetails();// Instantiate grid property details for current sceneDisplayGridPropertyDetails();}// Update first time scene loaded boolif(isFirstTimeSceneLoaded == true){isFirstTimeSceneLoaded = false;}}}

4)修改ISaveableStoreScene函数

添加如下内容:

// create && add bool dictionary for first time scene loaded
sceneSave.boolDictionary = new Dictionary<string, bool>();
sceneSave.boolDictionary.Add("isFirstTimeSceneLoaded", isFirstTimeSceneLoaded);

完整代码如下:

public void ISaveableStoreScene(string sceneName)
{// Remove sceneSave for sceneGameObjectSave.sceneData.Remove(sceneName);// Create sceneSave for sceneSceneSave sceneSave = new SceneSave();// create & add dict grid property details dictionarysceneSave.gridPropertyDetailsDictionary = gridPropertyDictionary;// create && add bool dictionary for first time scene loadedsceneSave.boolDictionary = new Dictionary<string, bool>();sceneSave.boolDictionary.Add("isFirstTimeSceneLoaded", isFirstTimeSceneLoaded);// Add scene save to game object scene dataGameObjectSave.sceneData.Add(sceneName, sceneSave);
}

6、创建Blue Spruce预制体

在Assets -> Prefabs -> Crop下创建"Instantiate In Scene"目录,这个目录下的农作物预制体会直接添加到场景中,会有砍树时飘落叶+砍完之后露出树桩等特效。

而之前的Scenary中的树只有最基本的功能。(只有Sprite Renderer + Box Collider 2D组件)

在"Instantiate In Scene"下创建子目录命名为"Instantiate Blue Spruce",我们要在该目录下为Blue Spruce的不同生长期创建一些预制体。

在Hierarchy -> PersistentScene下新建物体命名为BlueSpruce_5Days,添加相关组件如下:

在BlueSpruce_5Days下创建子物体命名为SecondSprite,添加相关组件如下:

将BlueSpruce_5Days拖到"Instantiate Blue Spruce"目录下,然后再删除之。

在Assets -> Prefabs -> Crop -> "Instantiate Blue Spruce"目录下,右击BlueSpruce_5Days,Create -> Prefab Variant并重命名为"BlueSpruce_10Days",

修改的内容如下:

右击BlueSpruce_5Days,Create -> Prefab Variant并重命名为"BlueSpruce_15Days",并进行相应的修改如下:

右击BlueSpruce_5Days,Create -> Prefab Variant并重命名为"BlueSpruce_20Days",并进行相应的修改如下:

返回到BlueSpruce_5Days双击,在SecondSprite中修改Sprite Renderer信息如下:

因为其他3个都是基于BlueSpruce_5Days的变体,所以可以看到其他3个的值同样发生了变化。

点击BlueSpruce_20Days,再点击SecondSprite,修改Sprite的值如下:

7、布置Blue Spruce

加载Scene1_Farm场景,然后把刚才创建的杉树预制体放到该场景中。所有物体均放到Scene1_Farm -> Crops目录下。

处理完之后卸载Scene1_Farm。

运行游戏后,我们发现Player经过刚创建的BlueSpruce后面会有Fade的效果,同时砍伐树木之前的特效都在。

查看Scene1_Farm -> Crops下的信息如下,我们发现都是CropStandard(Clone)和CropTreeBlueSpruce(Clone)。

而在设计阶段,我们给Crops的配置信息如下:

为什么Crops下的物体发生了变化?为什么仅仅通过CropInstantiator的配置,树的效果都有了?

答案是:设计阶段放置的物体(比如BlueSpruce_20Days)本来就在CropInstantiator的InstantiateCropPrefabs函数的最后一步Destroy了,它的作用是给Grid配置属性,任务完成后就会自动销毁。

而通过CropInstantiator的配置树的效果生效,是因为在GridPropertiesManager函数中,在ISaveableRestoreScene函数中,有如下的代码:

// Instantiate grid property details for current scene
DisplayGridPropertyDetails();

该函数的代码是:

private void DisplayGridPropertyDetails()
{// Loop throught all grid itemsforeach(KeyValuePair<string, GridPropertyDetails> item in gridPropertyDictionary){GridPropertyDetails gridPropertyDetails = item.Value;DisplayDugGround(gridPropertyDetails);DisplayWateredGround(gridPropertyDetails);DisplayPlantedCrop(gridPropertyDetails);}
}

在DisplayPlantedCrop函数中,会实例化之前自带特效的prefab。

同时,在GridPropertiesManager的AdvanceDay方法中,最后一步就是执行DisplayGridPropertyDetails方法。

植物的状态都是由GridPropertiesManager类控制的。场景被LoadScene时,或者用户播散种子之后,都会执行DisplayGridPropertyDetails方法。

8、创建Canyon Oak预制体并布置

方法同Blue Spruce。

将Instantiate Blue Spruce下的BlueSpruce_5Days拖入到PersistentScene进行编辑。

右击 -> Prefab -> Unpack Completely,解除与prefab中的绑定关系。

重命名为CanyonOak_5Days,其他配置如下:

然后把CanyonOak_5Days放到Assets -> Prefabs -> Crop -> Instantiate Canyon Oak下。

依次创建如下预制体:

CanyonOak_10Days:(需要通过create -> prefab variant的方式)

CanyonOak_15Days:

CanyonOak_20Days(分两步),第一步设置根的sprite:

第二步设置子的sprite:

最后,在Scene中创建CanyonOak树。

本节完毕,可体验游戏。

http://www.xdnf.cn/news/640999.html

相关文章:

  • day27:零基础学嵌入式之进程
  • Docker镜像存储路径迁移指南(解决磁盘空间不足问题)
  • Nginx安全防护
  • 基于Python Anaconda环境,使用CNN-LSTM模型预测碳交易价格的完整技术方案
  • 大模型与训练与微调
  • Java基础 Day20
  • 嵌入式自学第二十七天
  • ST表——算法的优化
  • TCP 和 UDP 的区别
  • 电梯调度算法详解与Python实现
  • 页表:从虚拟内存到物理内存的转换
  • C语言初阶--操作符
  • 消息队列kafka的基础概念和部署
  • C#、C++、Java、Python 选择哪个好
  • TCP 的三次握手
  • Python Day32 学习
  • 十二、【鸿蒙 NEXT】如何使用系统api实现视频压缩
  • 电子电路:电学都有哪些核心概念?
  • Oracle控制文件损坏恢复方案
  • dify_plugin数据库中的表总结
  • threejs几何体BufferGeometry顶点
  • 【报错】Error attempting to get column ‘created_time‘ from result set.解决方法
  • 手撕string类
  • 汉诺塔超级计算机堆栈区与数据区的协同
  • Docker(零):本文为 “Docker系列” 有关博文的简介和目录
  • Docker核心笔记
  • JavaWeb:SpringBoot配置优先级详解
  • 互联网大厂Java求职面试:AI应用集成中的RAG系统优化与向量数据库性能调优实战
  • 英语科研词汇的困境与汉语的崛起之光
  • 漫谈英伟达GPU架构进化史:从Celsius到Blackwell