当前位置:网站首页>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;
}
}
}
边栏推荐
猜你喜欢

美国旅游签证面试须知,我来敲重点啦!

关于 NFT 和版权的纠结真相

【yarn】Name contains illegal characters
![[taro] the solution of taro wechat applet input focusing the cursor incorrectly on the Apple phone](/img/67/6f46d415475d024950e2502c1f5dc2.png)
[taro] the solution of taro wechat applet input focusing the cursor incorrectly on the Apple phone

Based on asp Net development of enterprise communication source text message management platform source code

Go Web 编程入门:验证器

【taro】taro微信小程序input在苹果手机上光标聚焦不对的解决办法

Query of the range of the cotolly tree chtolly tree old driver tree

Recruitment brochure for traditional product manager international qualification certification (NPDP) in the second half of 2022

buuctf pwn ciscn_ 2019_ n_ eight
随机推荐
【愚公系列】2022年06月 通用职责分配原则(九)-受保护变量原则
vim自动命令事件大全
What do technical people pursue?
kubernetes code-generator使用
Meet webassembly again
You have a chance, here is a stage
[next] the component definition is missing display name in the next JS package
[pwn basics]pwntools learning
数学知识:约数个数—约数
数学知识:最大公约数—约数
Importance of data governance
[GXYCTF2019]SXMgdGhpcyBiYXNlPw==
汇编语言范例
ModuleNotFoundError: No module named ‘torchvision.datasets‘; ‘torchvision‘ is not a package
Lectures explanation for unsupervised graph level representation learning (usib)
【next】nextjs打包出现组件定义缺少显示名称【Component definition is missing display name】
【微信小程序】40029 invalid code解决办法集合
JVM makes wheels
珂朵莉树 范围查询 chtholly tree Old driver tree
Cloud whale took the lead in arranging door-to-door services to see how it broke through the industry "blockade" with services