当前位置:网站首页>Farm Game

Farm Game

2022-06-21 23:04:00 晴夏。

环境设置

在package manager中删除无用的包可以使速度变快。

找到素材中的:

为了使得其他的素材图片也按照这种方式,可以以当前设定来创建一种预设。

然后就可以同时选中多个物体,让他们应用同样的这个预设。

切割图片的方法:

接下来对人物动画同理做切割:

切割时由于动画是8帧,选择切成8个,锚点选在脚底是为了能够实现一个正确的遮挡效果。 

接下来创建人物,

 在人物的锚点这里选择底部

创建人物:

为了防止人物的一些部位对其他物品进行遮挡,产生“分家”的现象,添加一个sorting group

在父物体添加一个sorting group可以使得父物体和子物体一起渲染

然后新建一个层,将几个子物体设定为

 

接下来我们希望实现这样的效果(因为是俯视角的游戏)(当人物走到草丛前面时会遮挡草丛(这个走到前面和后面不是通过z轴,而是通过y轴)):

但是在2d的情况下,在同样的层次下渲染前后遮挡关系是通过z轴实现的。我们希望通过y轴来实现,则修改如下:

(注意物体的锚点要设置在底部)

 人物移动的代码

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

public class Player : MonoBehaviour
{
    public float speed = 5;
    Rigidbody2D rb;
    private float inputX;
    private float inputY;
    private Vector2 movementInput;

    private void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        //用update函数接受数据,而不是改变物体的刚体
        PlayerInput();   
    }

    private void FixedUpdate()
    {
        //对于改变刚体的运动,使用fixupdate来实现
        Movement();
    }

    private void PlayerInput()
    {
        inputX = Input.GetAxisRaw("Horizontal");
        inputY = Input.GetAxisRaw("Vertical");
        if (inputX != 0 && inputY != 0)
        {
            inputX *= 0.6f;
            inputY *= 0.6f;
        }
        movementInput = new Vector2(inputX, inputY);
    }


    private void Movement()
    {
        rb.MovePosition(rb.position + movementInput * speed * Time.deltaTime);
    }
}

为了使得和环境更好交互,创建一系列的瓦片地图,其在不同的sorting layer上:

 

  • 创建规则的瓦片信息

然后根据自己想要设定的规则,可以绘制出有规律的地图

注意到瓦片地图间会有这样的缝隙:

解决方法:

创建一个Sprite Altas,这个图集会将所有的素材打包在一起,引用时忽略该图集。

将Maps以及其他需要打包的地图素材这个文件夹放入,即可实现。

添加这个相机使得 

  • 碰撞层

为了产生碰撞效果,且碰撞是个整体,添加这三个组件并设定如下:

接下来绘制空气墙:

可以修改三角形的碰撞体积,通过下面这种方式手动调整:

 此时碰撞体积就改变了​​​

​​​​

接下来根据地图来绘制碰撞体:

创建树和其对应的动画:

  • 添加摄像机的边界

创建一个边界,添加polygon coiilder并设定边界范围:

接下来我们希望通过读取不同的场景时,摄像机会读取该场景的边界并设定边界:

为上面添加的polygon collider添加tag。

然后为虚拟摄像机添加如下代码:

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

public class SwitchBounds : MonoBehaviour
{

    private void Start()
    {
        SwitchConfinerShape();
    }
    private void SwitchConfinerShape()
    {
        PolygonCollider2D confinerShape = GameObject.FindGameObjectWithTag("BoundsConfiner").GetComponent<PolygonCollider2D>();

        CinemachineConfiner confiner = GetComponent<CinemachineConfiner>();

        confiner.m_BoundingShape2D = confinerShape;

        //Call this if the bounding shape's points change at runtime
        confiner.InvalidatePathCache();
    }
}

  • 景观的半遮挡与透明

简单来说就是走到树后面,树会变成半透明。

让树渐变的方法可以当触发trigger函数时使用协程,让树的α值缓慢的从1变成0。

此处使用DOTween。

调用思路:在树的身上挂载一个脚本叫做ItemFader,这个脚本具有淡入和淡出的函数:

using UnityEngine;
using DG.Tweening;


[RequireComponent(typeof(SpriteRenderer))]
public class ItemFader : MonoBehaviour
{
    private SpriteRenderer spriteRenderer;


    private void Awake()
    {
        spriteRenderer = GetComponent<SpriteRenderer>();
    }

    public void FadeIn()
    {
        Color targetColor = new Color(1, 1, 1, 1);
        spriteRenderer.DOColor(targetColor, Settings.fadeDuration);
    }

    public void FadeOut()
    {
        Color targetColor = new Color(1, 1, 1, Settings.targetAlpha);
        spriteRenderer.DOColor(targetColor, Settings.fadeDuration);
    }

}

为了方便后续查找更改的数据,建立一个setting脚本用于储存常量:

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

public class Settings
{
    public const float fadeDuration = 0.35f;

    public const float targetAlpha = 0.45f;

}

当玩家触发了trigger函数时,获取碰撞体的所有子物体的ItemFader脚本,然后调用淡入和淡出的函数。

效果如下:

背包系统

背包数据初始化

采用MVC的模式,将控制、数据、和显示逐一分开,通过inventory manager来管理数据,并通过它来呼叫UI的显示内容。

创建一个DataCollection,我们会将所写的类都放在这个文件当中,方便集中管理,查找和修改。


using UnityEngine;

[System.Serializable]
//用来存储物品的详细信息 
public class ItemDetails
{
    public int itemID;
    public string name;

    public ItemType itemType;

    public Sprite itemIcon;
    public Sprite itemOnWorldSprite;

    public string itemDescription;
    public int itemUseRadius;

    public bool canPickedup;
    public bool canDropped;
    public bool canCarried;
    public int itemPrice;

    [Range(0, 1)]
    public float sellPercentage;
}

  • Enums 创建 ItemType 物品类型

public enum ItemType
{
    Seed,Commodity,Furniture,
    HoeTool,ChopTool,BreakTool,ReapTool,WaterTool,CollectTool,
    ReapableScenery
}

  • 生成 ItemDetailsList_SO 文件做为整个游戏的物品管理数据库

创建一个SO菜单栏,其对应的SO包含很多个物品,所以使用的是List<ItemDetails>的数据结构。

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


[CreateAssetMenu(fileName ="ItemDataList_SO",menuName ="Inventory/ItemDataList")]

public class ItemDataList_SO : ScriptableObject
{
    public List<ItemDetails> itemDetailsList;
}

创建一个文件夹用来管理数据,并创建一个示例SO。

(Editor)使用 UI Toolkit 和 UI Builder 制作物品编辑器

  • 要自己写好 ItemDataLis_SO

目标是设计成下面这个样子

UIToolKit可以在可视化的情况下来编辑Customer Editor,在runtime下也可以使用。

创建一个Editor文件夹:(Editor文件夹在打包时不会被打包进去)

创建一个这个 

则会生成三个文件:

制作好了编辑器后,添加物品如下:

创建InventoryManager

单例模式代码


using UnityEngine;

public class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
    private static T instance;

    public static T Instance
    {
        get => instance;
    }

    protected virtual void Awake()
    {
        if (instance != null)
            Destroy(gameObject);
        else instance = (T)this;
    }


    protected virtual void OnDestroy()
    {
        if (instance != this)
            instance = null;
    }

}

 为inventory manager添加命名空间,方便管理数据,避免互相乱调用的耦合情况。

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

namespace MFarm.Inventory
{
    public class InventoryManager : Singleton<InventoryManager>
    {
        public ItemDataList_SO ItemDataList_SO;
    }

}

添加命名空间后,想在其他的代码中使用inventoryManager,就得using这个命名空间。

为其添加通过ID查找的功能后的完整代码:

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

namespace MFarm.Inventory
{
    public class InventoryManager : Singleton<InventoryManager>
    {
        public ItemDataList_SO ItemDataList_SO;


        /// <summary>
        /// 
        ///通过ID返回物品信息
        /// </summary>
        /// <param name="ID"></param>
        /// <returns></returns>
        public ItemDetails GetItemDetails(int ID)
        {
            //找到某个itemDetails,它的ID等于所给ID
            return ItemDataList_SO.itemDetailsList.Find(i => i.itemID == ID);
        }

    }



}

在主场景中添加这个并绑定SO,因为是在主场景,所以场景切换时这个不需要改变。 

实现地图上显示数据库中的物品

创建一个itemBase,其作用是,当场景中会生成一些物品,比如砍了树会生成木头,以及果实开花产生种子。我们希望这些情况时,可以通过代码去inventoryManager里面获得ID对应的物品详情。

为其添加碰撞体,作用是比如当玩家触碰时,就将它添加到背包中。

接下来通过代码来根据不同的ID显示不同的物品,思路很简单,就是通过ID去数据库中获取对应的图片并展示出图片即可。而不同的图片的大小不一样,所以根据图片的大小去修改碰撞体的体积。

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

namespace MFarm.Inventory
{


    public class Item : MonoBehaviour
    {
        public int itemID;

        private SpriteRenderer spriteRenderer;
        private BoxCollider2D coll;
        private ItemDetails itemDetails;

        private void Awake()
        {
            spriteRenderer = GetComponentInChildren<SpriteRenderer>();
            coll = GetComponent<BoxCollider2D>();
        }

        private void Start()
        {
            if (itemID != 0)
            {
                Init(itemID);
            }
        }

        public void Init(int ID)
        {
            itemID = ID;

            //Inventory获得当前数据
            itemDetails = InventoryManager.Instance.GetItemDetails(itemID);

            //因为返回的结果有可能是空的,如果在非空的情况下显示图片即可
            if (itemDetails != null)
            {
                spriteRenderer.sprite = itemDetails.itemOnWorldSprite != null ? itemDetails.itemOnWorldSprite : itemDetails.itemIcon;

                //修改碰撞体尺寸,让其能和不同的图片一一对应
                Vector2 newSize = new Vector2(spriteRenderer.sprite.bounds.size.x, spriteRenderer.sprite.bounds.size.y);
                coll.size = newSize;
                coll.offset = new Vector2(0, spriteRenderer.sprite.bounds.center.y);
            }
        }

    }

}

  • 拾取物品

拾取物品的逻辑如下,为玩家添加拾取物品的脚本,当触发trigger时,获取该物体的item组件,然后如果有item组件,则调用数据库InventoryManager中的AddItem函数,添加物品进背包。

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

namespace MFarm.Inventory
{
    public class ItemPickUp : MonoBehaviour
    {
        private void OnTriggerEnter(Collider other)
        {
            Item item = other.GetComponent<Item>();

            if (item != null)
            {
                if (item.itemDetails.canPickedup)
                {
                    InventoryManager.Instance.AddItem(item, true);
                }
            }


        }
    }
}

在InventoryManager中添加如下代码:

        /// <summary>
        /// 添加物品到Player背包里
        /// </summary>
        /// <param name="item"></param>
        /// <param name="toDestory">是否要销毁物品</param>
        public void AddItem(Item item, bool toDestory)
        {
            Debug.Log(GetItemDetails(item.itemID).itemID + "Name: " + GetItemDetails(item.itemID).itemName);
            if (toDestory)
            {
                Destroy(item.gameObject);
            }
        }

背包系统

背包的SO代码:

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


[CreateAssetMenu(fileName ="InventoryBag_SO",menuName ="Inventory/InventoryBag_SO")]
public class InventoryBag_SO : ScriptableObject
{
    public List<InventoryItem> itemList;
}

这个背包适用于所有东西,除了玩家自己的背包,还可以是npc的商店。

 

对于添加一个物体前需要思考,背包是否满了,是否有该物品,这些都需要在AddItem里面实现。

在InventoryManager中添加如下代码即可实现该功能:

        /// <summary>
        /// 添加物品到Player背包里
        /// </summary>
        /// <param name="item"></param>
        /// <param name="toDestory">是否要销毁物品</param>
        public void AddItem(Item item, bool toDestory)
        {

            var index = GetItemIndexInBag(item.itemID);
            AddItemAtIndex(item.itemID,index,1);


            //对于添加一个物体前需要思考,背包是否满了,是否有该物品
            Debug.Log(GetItemDetails(item.itemID).itemID + "Name: " + GetItemDetails(item.itemID).itemName);
            if (toDestory)
            {
                Destroy(item.gameObject);
            }
        }


        /// <summary>
        /// 检查背包是否有空位
        /// </summary>
        /// <returns></returns>
        private bool CheckBagCapacity()
        {
            for(int i = 0; i < playerBag.itemList.Count;i++)
            {
                if (playerBag.itemList[i].itemID == 0)
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 根据ID查找包里是否有这个东西
        /// </summary>
        /// <param name="ID"></param>
        /// <returns></returns>
        private int GetItemIndexInBag(int ID)
        {
            for (int i = 0; i < playerBag.itemList.Count; i++)
            {
                if (playerBag.itemList[i].itemID == ID)
                {
                    return i;
                }
            }
            return -1;
        }


        private void AddItemAtIndex(int ID,int index,int amount)
        {

            if (index == -1&&CheckBagCapacity())
            {
                var item = new InventoryItem { itemID = ID, itemAmount = amount };

                for (int i = 0; i < playerBag.itemList.Count; i++)
                {
                    if (playerBag.itemList[i].itemID == 0)
                    {
                        playerBag.itemList[i] = item;
                        break;
                    }
                }
            }
            else
            {
                int currentAmount = playerBag.itemList[index].itemAmount + amount;

                var item = new InventoryItem { itemID = ID, itemAmount = currentAmount };

                playerBag.itemList[index] = item;
            }
        }

    }


 

原网站

版权声明
本文为[晴夏。]所创,转载请带上原文链接,感谢
https://blog.csdn.net/weixin_43757333/article/details/125337296