Unity3D仿星露谷物语开发31之设置地面属性方法探索

1、目标

设置地面的属性,比如哪些地面是可以放置Items的,哪些地面可以挖洞。根据地面的这些属性,决定角色相应的动作能否顺利执行。

2、思路

我们的解决方案是:在Tilemaps中存储Boolean值信息

有些信息在设计阶段就需要进行设置:

  • Can we drop objects there?
  • Can we dig there?
  • Can we place furniture there?

另外一些信息在运行阶段进行设置:

  • Has the ground grid square(方格) been dug?
  • Has the ground grid square been watered?
  • Has a seed been planted in a ground grid square?

我们需要创建一个组件TilemapGridProperties附加到tilemap物体上。

该组件只在编辑模式下运行,并将Boolean值“yes/no - can I drop an item here”放到scriptable物体上。

每个Boolean的tilemap都会附加该组件。

3、创建新的Enum

通过枚举类GridBoolProperty定义地面的属性值,用来识别每个网格与哪个Bool属性相关。

public enum GridBoolProperty
{
    diggable,
    canDropItem,
    canPlaceFurniture,
    isPath,
    isNPCObstacle
}

4、创建GridCoordinate脚本

它是一个可序列化的向量,用于捕获网格坐标。

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

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

[System.Serializable]
public class GridCoordinate
{
    public int x;
    public int y;

    public GridCoordinate(int p1, int p2)
    {
        x = p1;
        y = p2; 
    }

    public static explicit operator Vector2(GridCoordinate gridCoordinate) 
    {
        return new Vector2( (float)gridCoordinate.x, (float)gridCoordinate.y );
    }

    public static explicit operator Vector2Int(GridCoordinate gridCoordinate)
    {   
        return new Vector2Int(gridCoordinate.x, gridCoordinate.y);
    }

    public static explicit operator Vector3(GridCoordinate gridCoordinate)
    {
        return new Vector3((float)gridCoordinate.x, (float)gridCoordinate.y, 0f);
    }

    public static explicit operator Vector3Int(GridCoordinate gridCoordinate)
    {
        return new Vector3Int(gridCoordinate.x, gridCoordinate.y, 0);
    }

}

public static explicit operator是显式类型转换的语法。

5、创建GridProperty脚本

在Assets -> Scripts -> Map下创建新的脚本命名为GridProperty。

这个就是每个Grid方格的属性类。

属性包含:坐标,地面属性的信息。

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

[System.Serializable]
public class GridProperty 
{
    public GridCoordinate gridCoordinate;     // 坐标
    public GridBoolProperty gridBoolProperty; // 地面属性
    public bool gridBoolValue = false;        // 是否Bool属性

    public GridProperty(GridCoordinate gridCoordinate, GridBoolProperty gridBoolProperty, bool gridBoolValue)
    {
        this.gridCoordinate = gridCoordinate;
        this.gridBoolProperty = gridBoolProperty;
        this.gridBoolValue = gridBoolValue;
    }
}

6、创建SO_GridProperties脚本

该脚本存储场景下的所有方格的GridProperty数据。

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

[CreateAssetMenu(fileName ="so_GridProperties", menuName ="Scriptable Objects/Grid Properties")]
public class SO_GridProperties : ScriptableObject
{
    public SceneName sceneName;
    public int gridWidth;
    public int gridHeight;
    public int originX;
    public int originY;

    [SerializeField]
    public List<GridProperty> gridPropertyList;
}

gridWidth/gridHeight是某一个场景的Tilemap的长宽。

originX/originY是tilemap左下角的坐标。

7、创建TilemapGridProperties脚本

在Tilemap Grid中,GridProperties对象下有对应地面属性的各种对象。

我们希望在编辑模式下,当打开GridProperties对象时,会收集Grid的信息;当关闭GridProperties对象时,会将收集的信息写入SO_GridProperties组件中。

代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.Tilemaps;

[ExecuteAlways]
public class TilemapGridProperties : MonoBehaviour
{
    private Tilemap tilemap;
    [SerializeField] private SO_GridProperties gridProperties = null;
    [SerializeField] private GridBoolProperty gridBoolProperty = GridBoolProperty.diggable;

    private void OnEnable()
    {
        // Only populate in the editor
        if( !Application.IsPlaying(gameObject))
        {
            tilemap = GetComponent<Tilemap>();

            if(gridProperties != null)
            {
                // 编辑模式下开启脚本,清空所有的gridPropertyList数据
                gridProperties.gridPropertyList.Clear();
            }
        }
    }

    private void OnDisable()
    {
        // Only populate in the editor
        if (!Application.IsPlaying(gameObject)) 
        {
            // 编辑模式下关闭脚本,存储数据
            UpdateGridProperties();

            if(gridProperties != null)
            {
                // This is required to ensure that the updated gridproperties gameobject gets saved when the game is saved - otherwise they are not saved.
                EditorUtility.SetDirty(gridProperties);
            }
        }
    }


    private void UpdateGridProperties()
    {
        // Compress tilemap bounds
        tilemap.CompressBounds();

        // Only populate in the editor
        if (!Application.IsPlaying(gameObject))
        {
            if(gridProperties != null)
            {
                Vector3Int startCell = tilemap.cellBounds.min;
                Vector3Int endCell = tilemap.cellBounds.max;

                for (int x = startCell.x; x < endCell.x; x++)
                {
                    for (int y = startCell.y; y < endCell.y; y++)
                    {
                        TileBase tile = tilemap.GetTile(new Vector3Int(x, y, 0));

                        if(tile != null)
                        {
                            gridProperties.gridPropertyList.Add(new GridProperty(new GridCoordinate(x, y), gridBoolProperty, true));
                        }
                    }
                }
            }
        }

    }

    private void Update()
    {
        // Only populate in the editor
        if (!Application.IsPlaying(gameObject) )
        {
            Debug.Log("DISABLE PROPERTY TILEMAPS");
        }
    }
}

在编辑模式下,开启脚本时会删除历史gridProperties数据,方便重新进行绘制;关闭脚本后,会遍历Tilemap实例对应的grid,并且存储数据。

运行时,需要提醒一定要关闭脚本,因为只有在关闭状态下Tilemap实例才有gridProperties数据。

8、创建so_GridProperties实例

在Assets -> Scriptable Object Assets下创建Maps目录:

在Maps下创建Scriptable Object命名为:so_GridProperties_Scene1_Farm

点击so_GridProperties_Scene1_Farm,点击Inspector,可以看到它的属性信息。

在Hierarchy中打开Scene1_Farm,然后再点击Tile Palette,选择Coordinate Brush,在Scene中鼠标移到左下角,可以看到originX/Y的坐标信息。

然后鼠标再拖到右上角,可以看到Pos和Size信息。这正是so_GridProperties_Scene1_Farm中要填的信息。

然后添加信息如下:

以相同的方法创建so_GridProperties_Scene2_Field,其属性信息如下:

再创建so_GridProperties_Scene2_Cabin,在Scene中测量如下:

其属性信息如下:

9、添加TilemapGridProperties脚本

在Scene3_Cabin中,展开GridProperties,其下的BoolXXX全部选中,然后将TilemapGridProperties脚本添加进去。

点击BoolCanPlaceFurniture,将so_GridProperties_Scene3_Cabin资源拖入到Grid Properties属性中,Grid Bool Property属性选择和Hirerachy中物体一致的名称。如下图所示:

再看一个例子如下:

逐一操作Hierarchy中的5个GridProperties物体。

然后同样的方法在Scene1_Farm和Scene2_Field中添加TilemapGridProperties脚本,并且添加对应的so_GridProperties物体,以及选择对应的GridBoolProperty属性。

10、测试so_GridProperties物体的GridPropertyList

通过上述步骤,在编辑模式下,打开Hierarchy -> SceneXXX -> GridProperties物体(OnEnable),绘制瓦片贴图。那么关闭GridProperties物体时(OnDisable),就会自动更新so_GridProperties_Scenexxx下Grid Property List的信息。

下面测试下该功能。

点击GridProperties,然后在Inspector中进行勾选。

然后点击BoolCanDropItem,选择一个瓦片,在Scene中进行添加。

接着,点击GridProperties,然后在Inspector中进行反勾选。

最后点击so_GridProperties_Scene3_Cabin,就可以看到Grid Property List下有一个元素。

11、小结

以上内容记录了Grid的属性。

我们通过so_GridProperties实例记录每个Scene的地面属性。它记录了瓦片地图的长宽、起始坐标位置(左下角位置),以及所有的Grid的属性列表。

怎么写这个so_GridProperties实例的数据呢?我们创建了TilemapGridProperties脚本,这个脚本在编辑模型下运行,然后挂载到每个Scene下的GridProperties物体的子物体上。每个子物体会遍历各自的Tilemap地图,然后把数据写到gridProperties中。而gridProperties就是so_GridProperties实例。可以看他的定义:

 [SerializeField] private SO_GridProperties gridProperties = null;

那么Grid有哪些属性?定义在GridProperty脚本中。

### 关于Unity3D开发类似星露游戏的教程和资源 #### 使用ScriptableObject管理物品数据 为了实现类似于《星露谷物》中的复杂道具系统,在Unity3D中可以利用`ScriptableObject`来高效管理和扩展各类物品属性。通过编写继承自`ScriptableObject`的C#脚本,能够创建可序列化的资产文件(.asset),这些文件可用于定义游戏中不同类型的工具、作物或其他交互对象[^3]。 ```csharp using UnityEngine; [CreateAssetMenu(fileName = "NewItem", menuName = "Game/Item")] public class Item : ScriptableObject { public string itemName; public Sprite icon; public int value; } ``` 此代码片段展示了如何声明一个新的`ScriptableObject`类型——`Item`,并为其添加基本字段用于表示名称、图标以及价值等信息。借助内置编辑器功能,开发者可以在Inspector视窗内直观地设置各项参数而无需修改源码。 #### 参考完整的项目案例 对于希望深入学习整个流程的新手而言,《制作100个Unity游戏》系列提供了详尽的教学指导,特别是其中有关2D横板卷轴动作类别的章节,虽然侧重点有所不同,但许多概念和技术同样适用于模拟经营风格的作品。该系列不仅涵盖了基础理论还分享了实际操作经验,并提供完整工程下载链接供读者参考实践[^1]。 #### 设计思路与规划建议 针对想要模仿特定作品如《星露谷物》的新晋创作者来说,遵循一套系统的创作方法论至关重要。这包括但不限于:确定核心玩法机制;构建世界背景故事框架;设计角色成长路径等内容策划工作。同时也要注意平衡难度曲线,确保玩家体验流畅自然[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值