1、目标

把防风草种子种在地里,并展示植物种子,防风草种子将随着时间变化而生长成植株。

2、创建Crop.cs脚本

在Assets -> Scripts下创建新的目录命名为Crop,在其下创建新的脚本命名为Crop.cs。

代码如下:

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

public class Crop : MonoBehaviour
{
    [HideInInspector]
    public Vector2Int cropGridPosition;
}

3、创建CropDetails.cs脚本

在Assets -> Scripts -> Crop下创建CropDetails.cs脚本。

代码如下:

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

[System.Serializable]
public class CropDetails 
{
    [ItemCodeDescription]
    public int seedItemCode; // this is the item code for the corresponding seed
    public int[] growthDays; // days growth for each stage
    public int totalGrowthDays; // total growth days
    public GameObject[] growthPrefab; // prefab to use when instantiating growth stages
    public Sprite[] growthSprite; // growth sprite
    public Season[] seasons; // growth seasons
    public Sprite harvestedSprite; // sprite used once harvested

    [ItemCodeDescription]
    public int harvestedTransformItemCode; // if the item transform into another item when harvested this item code will be populated
    public bool hideCropBeforeHarvestedAnimation; // if the crop should be disabled before the harvested animation
    public bool disableCropCollidersBeforeHarvestedAnimation; // if colliders on crop should be disabled to avoid the harvested animation effecting any other game objects
    public bool isHarvestedAnimation; // true if harvested animation to be played on final growth stage prefab
    public bool isHarvestActionEffect = false; // flag to determine whether there is a harvest action effect
    public bool spawnCropProducedAtPlayerPosition;
    public HarvestActionEffect harvestActionEffect; // the harvest action effect for the crop

    [ItemCodeDescription]
    public int[] harvestToolItemCode; // array of item codes for the tools that can harvest or 0 array elements if no tool required
    public int[] requiredHarvestActions; // number of harvest actions required for corresponding tool in harvest tool item code array

    [ItemCodeDescription]
    public int[] cropProducedItemCode; // array of item codes produced for the harvested crop
    public int[] cropProducedMinQuantity; // array of minimum quantities produced for the harvested crop
    public int[] cropProducedMaxQuantity; // if max quantity is > min quantity then a random number of crops between min and max are produced
    public int daysToRegrow; // days to regrow next crop or -1 if a single crop

    /// <summary>
    /// returns true if the tool item code can be used to harvest this crop, else returns false
    /// </summary>
    /// <param name="toolItemCode"></param>
    /// <returns></returns>
    public bool CanUseToolToHarvestCrop(int toolItemCode)
    {
        if(RequiredHarvestActionsForTool(toolItemCode) == -1)
        {
            return false;
        }
        else
        {
            return true;
        }
    }

    /// <summary>
    /// returns -1 if the tool can't be used to harvest this crop, else returns thhe number of harvest actions required by this tool
    /// </summary>
    /// <param name="toolItemCode"></param>
    /// <returns></returns>
    public int RequiredHarvestActionsForTool(int toolItemCode)
    {
        for(int i = 0; i < harvestToolItemCode.Length; i++)
        {
            if (harvestToolItemCode[i] == toolItemCode)
            {
                return requiredHarvestActions[i];
            }
        }
        return -1;
    }
    
}

4、创建SO_CropDetailsList.cs脚本

在Assets -> Scripts -> Crop下创建SO_CropDetailsList.cs脚本。

代码如下:

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

[CreateAssetMenu(fileName ="CropDetailsList", menuName ="Scriptable Objects/Crop/Crop Details List")]
public class SO_CropDetailList : ScriptableObject
{
    [SerializeField]
    public List<CropDetails> cropDetails;

    public CropDetails GetCropDetails(int seedItemCode)
    {
        return cropDetails.Find(x => x.seedItemCode == seedItemCode);
    }
}

5、填充防风草作物的数据

(1)制作Crop预制体

第1步,在Hierarchy -> PersistentScene下创建新的物体命名为CropStandard。

第2步,给该物体添加Box Collider 2D组件,并设置参数如下:

第3步,添加Crop组件和Item Nudge组件。

第4步,在CropStandard下创建子物体命名为CropSprite。

第5步,给CropSprite添加Sprite Renderer组件,并且设置参数如下:

如果Sorting Layer未设置未Instances,将导致seed无法正确显示(因为优先级不够)。

第6步,将Hierarchy下的CropStandard拖到Assets -> Prefabs -> Crop目录下,并且删除Hierarchy下的CropStandard。

(2)创建Crop的Scriptable资源

第1步,在Assets -> Scriptable Object Assets 下创建新的目录命名为Crop。

第2步,右击Crop目录,选择Scriptable Objects -> Crop -> Crop Details List,命名为so_CropDetailsList。

第3步,配置so_CropDetailsList的信息如下:

6、防风草生长

(1)优化Tags.cs脚本

添加一个变量:

public const string CropsParentTransform = "CropsParentTransform";

(2)添加Crops对象

首先,加载Scene1_Farm场景

然后,在Hierarchy -> Scene1_Farm下创建新的物体命名为Crops

接着,在Inspector界面下创建新的Tag:CropsParentTransform,并且指定Crops的Tag为新Tag。

最后,Unload Scene1_Farm场景。

对Scene2_Field场景进行同样的操作

(3)优化EventHandler.cs脚本

添加新的事件如下:

// Remove selected item from inventory
public static event Action RemoveSelectedItemFromInventoryEvent;

public static void CallRemoveSelectedItemFromInventoryEvent()
{
    if(RemoveSelectedItemFromInventoryEvent != null)
    {
        RemoveSelectedItemFromInventoryEvent();
    }
}

(4)优化GridPropertiesManager.cs脚本

添加变量1:

private Transform cropParentTransform;

修改AfterSceneLoaded函数,添加如下代码:

if(GameObject.FindGameObjectWithTag(Tags.CropsParentTransform) != null)
{
    cropParentTransform = GameObject.FindGameObjectWithTag(Tags.CropsParentTransform).transform;
}
else
{
    cropParentTransform = null;
}

添加变量2:

[SerializeField] private SO_CropDetailList so_CropDetailList = null;

修改ClearDisplayGridPropertyDetails函数,添加如下代码:

private void ClearDisplayAllPlantedCrops()
{
    // Destory all crops in scene
    Crop[] cropArray;
    cropArray = FindObjectsOfType<Crop>();   

    foreach(Crop crop in cropArray)
    {
        Destroy(crop.gameObject);
    }

}

private void ClearDisplayGridPropertyDetails()
{
    ClearDisplayGroundDecorations();

    ClearDisplayAllPlantedCrops();
}

修改DisplayGridPropertyDetails函数,代码如下:

private void DisplayGridPropertyDetails()
{
    // Loop throught all grid items
    foreach(KeyValuePair<string, GridPropertyDetails> item in gridPropertyDictionary)
    {
        GridPropertyDetails gridPropertyDetails = item.Value;

        DisplayDugGround(gridPropertyDetails);

        DisplayWateredGround(gridPropertyDetails);

        DisplayPlantedCrop(gridPropertyDetails);
    }
}

public void DisplayPlantedCrop(GridPropertyDetails gridPropertyDetails)
{
    if(gridPropertyDetails.seedItemCode > -1)
    {
        // get crop details
        CropDetails cropDetails = so_CropDetailList.GetCropDetails(gridPropertyDetails.seedItemCode);

        // prefab to use
        GameObject cropPrefab;

        // instantiate crop prefab at grid location
        int growthStages = cropDetails.growthDays.Length;

        int currentGrowthStage = 0;
        int daysCounter = cropDetails.totalGrowthDays;

        // 找出目前所处的成长阶段
        for(int i = growthStages - 1; i >= 0; i--)
        {
            if(gridPropertyDetails.growthDays >= daysCounter)
            {
                currentGrowthStage = i;
                break;
            }

            daysCounter = daysCounter - cropDetails.growthDays[i];
        }

        cropPrefab = cropDetails.growthPrefab[currentGrowthStage];

        Sprite growthSprite = cropDetails.growthSprite[currentGrowthStage];

        Vector3 worldPosition = groundDecoration2.CellToWorld(new Vector3Int(gridPropertyDetails.gridX, gridPropertyDetails.gridY, 0));

        worldPosition = new Vector3(worldPosition.x + Settings.gridCellSize / 2, worldPosition.y, worldPosition.z);

        GameObject cropInstance = Instantiate(cropPrefab, worldPosition, Quaternion.identity);

        cropInstance.GetComponentInChildren<SpriteRenderer>().sprite = growthSprite;
        cropInstance.transform.SetParent(cropParentTransform);
        cropInstance.GetComponent<Crop>().cropGridPosition = new Vector2Int(gridPropertyDetails.gridX, gridPropertyDetails.gridY);
    
    }
}

修改AdvanceDay函数,代码如下:

private void AdvanceDay(int gameYear, Season gameSeason, int gameDay, string gameDayyOfWeek, int gameHour, int gameMinute, int gameSecond)
{
    // Clear Display All Grid Property Details
    ClearDisplayGridPropertyDetails();

    // loop through all scenes - by looping through all gridproperty in the array
    foreach(SO_GridProperties so_GridProperties in so_gridPropertiesArray)
    {
        // Get gridpropertydetails dictionary for scene
        if(GameObjectSave.sceneData.TryGetValue(so_GridProperties.sceneName.ToString(), out SceneSave sceneSave))
        {
            if(sceneSave.gridPropertyDetailsDictionary != null)
            {
                for(int i = sceneSave.gridPropertyDetailsDictionary.Count - 1; i >= 0; i--)
                {
                    KeyValuePair<string, GridPropertyDetails> item = sceneSave.gridPropertyDetailsDictionary.ElementAt(i);

                    GridPropertyDetails gridPropertyDetails = item.Value;

                    #region Update all grid properties to reflect the advance in the day

                    // if a crop is planted
                    if(gridPropertyDetails.growthDays > -1)
                    {
                        gridPropertyDetails.growthDays += 1;
                    }

                    // if ground is watered, then clear water
                    if(gridPropertyDetails.daysSinceWatered > -1)
                    {
                        gridPropertyDetails.daysSinceWatered = -1;
                    }

                    // Set gridpropertydetails
                    SetGridPropertyDetails(gridPropertyDetails.gridX, gridPropertyDetails.gridY, gridPropertyDetails, sceneSave.gridPropertyDetailsDictionary);

                    #endregion Update all grid properties to reflect the advance in the day
                }
            }
        }
    }

    // Display grid property details to reflect changed values
    DisplayGridPropertyDetails();
}

(5)优化Player.cs脚本

优化ProcessPlayerClickInput函数,代码如下:

修改ProcessPlayerClickInputSeed函数如下:

private void ProcessPlayerClickInputSeed(GridPropertyDetails gridPropertyDetails, ItemDetails itemDetails)
{
    if(itemDetails.canBeDropped && gridCursor.CursorPositionIsValid && gridPropertyDetails.daysSinceDug > -1 && gridPropertyDetails.seedItemCode == -1)
    {
        PlantSeedAtCursor(gridPropertyDetails, itemDetails);
    }
    else if (itemDetails.canBeDropped && gridCursor.CursorPositionIsValid)
    {
        EventHandler.CallDropSelectedItemEvent();
    }
}

添加PlantSeedAtCursor函数如下:

 private void PlantSeedAtCursor(GridPropertyDetails gridPropertyDetails, ItemDetails itemDetails)
 {
     // update grid properties with seed details
     gridPropertyDetails.seedItemCode = itemDetails.itemCode;
     gridPropertyDetails.growthDays = 0;

     // Display planted crop at grid property details
     GridPropertiesManager.Instance.DisplayPlantedCrop(gridPropertyDetails);

     // Remove item from inventory
     EventHandler.CallRemoveSelectedItemFromInventoryEvent();
 }

(6)优化UIInventorySlot.cs脚本

修改OnEnable代码,

 private void OnEnable()
 {
     EventHandler.AfterSceneLoadEvent += SceneLoaded;
     EventHandler.RemoveSelectedItemFromInventoryEvent += RemoveSelectedItemFromInventory;
     EventHandler.DropSelectedItemEvent += DropSelectedItemAtMousePosition;
 }

修改OnDisable代码:

private void OnDisable()
{
    EventHandler.AfterSceneLoadEvent -= SceneLoaded;
    EventHandler.RemoveSelectedItemFromInventoryEvent -= RemoveSelectedItemFromInventory;
    EventHandler.DropSelectedItemEvent -= DropSelectedItemAtMousePosition;
}

添加RemoveSelectedItemFromInventory函数代码:

 private void RemoveSelectedItemFromInventory()
 {
     if(itemDetails != null && isSelected)
     {
         int itemCode = itemDetails.itemCode;

         // Remove item from players inventory
         InventoryManager.Instance.RemoveItem(InventoryLocation.player, itemCode);

         // If no more of iitem then clear selected
         if(InventoryManager.Instance.FindItemInInventory(InventoryLocation.player, itemCode) == -1)
         {
             ClearSelectedItem();
         }
     }
 }

(7)补充配置信息

配置GridPropertiesManager的So_Crop Detail List信息如下:

7、运行游戏

先用Hoe挖一块地

然后洒下Parsnip的种子

按下G键可以加速时间,此时就可以看到防风草生长的过程。

Logo

分享前沿Unity技术干货和开发经验,精彩的Unity活动和社区相关信息

更多推荐