系列文章目录
麦田物语第二十二天
一、制作可以扔出来的物品
上节课我们制作了丢弃物品的功能,今天我们制作可以扔出来的物品,使其视觉效果上看起来更好。
我们首先将之前制作的预制体ItemBase拖入场景中,并解除其与预制体的联系(Unpack Competely),我们为这个物体添加方法使这个物体实现飞出去的效果(通过阴影等实现俯视角的视觉差)。
首先我们将这个物体的子物体添加图片,使其可以更好的显示,并为其更改名称为BounceItemBase,我们在其下方再添加空物体Shadow并添加Sprite Renderer组件,Shadow所显示的图片是我们子物体sprite的图片,但是我们将其颜色调成黑色并更改Alpha值为半透明,最后切记一定要将其Additional Settings的Sorting Layer设置为Instance。
我们要通过代码实现Shadow直接移动到指定区域(鼠标点按的位置),而Sprite下落到指定区域即可,那么我们现在就进行代码的编写吧!
首先我们需要的是阴影自动获取自己图片的代码,即我们丢弃物品时物品图片虽然已经选择好了,但是阴影图片我们需要重新获得,接着我们创建Scripts->Item->ItemShadow脚本并添加到Shadow上,首先将其添加到Inventory的命名空间,并且这个脚本必须需要SpriteRenderer组件,然后我们声明变量,一个是我们物体的SpriteRenderer,一个是阴影的SpriteRenderer,我们在Awake方法中获取阴影的SpriteRenderer,并在Unity中对物体的SpriteRenderer进行拖拽赋值,在游戏开始时(Start方法中)获取阴影的图片,即物体的图片,并且将其颜色更改为指定颜色(黑色半透明)。
ItemShadow脚本的代码如下:
namespace MFarm.Inventory
{
[RequireComponent(typeof(SpriteRenderer))]
public class ItemShadow : MonoBehaviour
{
public SpriteRenderer itemSprite;
private SpriteRenderer shadowSprite;
private void Awake()
{
shadowSprite = GetComponent<SpriteRenderer>();
}
private void Start()
{
shadowSprite.sprite = itemSprite.sprite;
shadowSprite.color = new Color(0, 0, 0, 0.3f);
}
}
}
接着我们创建Scripts->Item->ItemBounce脚本,这个脚本的目的是为了实现物体和阴影的不同飞行路径,将这个脚本挂载在BounceItemBase上,接着来编写这个脚本,首先我们需要声明Sprite的Transform变量spriteTrans,并在Awake方法中获取,同时游戏物体时从人物身上开始飞的,我们就要在其扔出去的过程中关闭其碰撞体防止碰撞,所以我们也要声明物体的碰撞体coll,在Awake方法中获取并关闭其碰撞体,还要进行一些变量的声明,例如我们需要声明物体下落的力gravity(自己定义的重力),物体下落到下面的标志(bool)isGround,物体运动的距离distance,物体运动的方向(Vector2)direction以及物体运动的目标位置(Vector3)targetPos,最后的这两个变量是用来存储传过来鼠标点击的位置,并在Update方法持续执行。
然后我们编写InitBounceItem方法,在这个方法中将碰撞体设置为不可用,并对direction,targetPos,distance,spriteTrans进行赋值,这些赋值方法需要重点说明一下:distance应该是两个向量之间的距离,即目标向量和当前物品向量的距离,这个物体位置也是初始化好的,因此我们在Awake方法中调用这个方法,最后我们将spriteTrans的位置进行更改,刚好放在人物头顶的位置。
接着我们在编写Bounce方法,用于丢弃的过程制作,首先我们要进行isGround方法的判断(spriteTrans的位置小于等于物体当前的位置),接着我们if判断该物体的位置是否到达目标位置,如果未到达,那么就一直进行移动,使得物品的位置在其方向上以一个基于距离的力(其实就是距离)在乘以(-重力)和Time.deltaTime,这样就实现了物品的水平的移动;接着我们要实现垂直方向的移动,如果物体未落地,类似于上面代码的使物品的位置向下,如果物品落地,那么将物品的位置和阴影的位置进行重合(此处用的是空物体的位置),并且开启其碰撞体的碰撞设置。
ItemBounce脚本的代码如下:
namespace MFarm.Inventory
{
public class ItemBounce : MonoBehaviour
{
private Transform spriteTrans;
private BoxCollider2D coll;
public float gravity = -3.5f;
private bool isGround;
private float distance;
private Vector2 direction;
private Vector3 targetPos;
private void Awake()
{
spriteTrans = transform.GetChild(0);
coll = GetComponent<BoxCollider2D>();
coll.enabled = false;
}
private void Update()
{
Bounce();
}
public void InitBounceItem(Vector3 target, Vector2 dir)
{
coll.enabled = false;
direction = dir;
targetPos = target;
distance = Vector3.Distance(target, transform.position);
spriteTrans.position += Vector3.up * 1.5f;
}
private void Bounce()
{
isGround = spriteTrans.position.y <= transform.position.y;
//物品的位置未到达目标的位置
if (Vector3.Distance(transform.position, targetPos) > 0.1f)
{
transform.position += (Vector3)direction * distance * -gravity * Time.deltaTime;
}
if (!isGround)
{
spriteTrans.position += Vector3.up * gravity * Time.deltaTime;
}
else
{
spriteTrans.position = transform.position;
coll.enabled = true;
}
}
}
}
最后我们返回ItemManager脚本,在脚本中添加变量bounceItemPrefab,返回Unity为其赋值,同时我们希望物品生成的位置在人物的脚底位置,因此我们需要声明人物的位置playerTransform并获取,然后在OnDropItem方法中更改Instantiate的生成物体为bounceItemPrefab,并且生成的位置也更改成playerTransform;接下来我们定义方向(即鼠标位置减去人物位置并进行归一化),并且调用新生成的物体身上的InitBounceItem方法,返回Unity运行即可。
ItemManager脚本的新添加的代码如下:
namespace MFarm.Inventory
{
public class ItemManager : MonoBehaviour
{
public Item bounceItemPrefab;
private Transform playerTrans => FindObjectOfType<Player>().transform;
private void OnDropItemEvent(int ID, Vector3 mousePos)
{
//TODO:扔东西的效果
var item = Instantiate(bounceItemPrefab, playerTrans.position, Quaternion.identity, itemParent);
item.itemID = ID;
var dir = (mousePos - playerTrans.position).normalized;
item.GetComponent<ItemBounce>().InitBounceItem(mousePos, dir);
}
}
}
二、实现挖坑和浇水的地图更改变化
当我们鼠标选择锄头后,我们点击可挖掘的位置,可以实现挖坑的效果,同时在挖坑之后的位置点击水壶之后可以进行浇水,那么我们这节课就来实现这个功能。
我们当时在01.Field场景中制作了可挖坑的Tilemap,现在我们希望实现下图的效果,就想我们当时绘制草地的时候一样,按照一定的规则生成,于是我们创建Tilemap->Rule Tiles->Dig Tile和Water Tile。挖坑的规则如下(浇水的瓦片与它相似,这里就不展示了):
那么我们怎么在游戏运行的情况下来切换这个瓦片嘞?我们可以使用Tilemap的SetTile方法,这个可以去查阅代码手册查看其使用方法,我们已经知道了用什么方法,现在就是需要编写代码,通过鼠标点击进行挖坑和浇水。
我们首先给01.Field场景的Dig和Water添加标签Dig和Water,然后我们返回GridMapManager脚本中,添加新的变量,例如Dig和Water的Tilemap(通过标签在OnAfterSceneLoadedEvent方法中获取)和Rule Tile(在Unity中直接赋值);接着编写浇水和挖坑的方法SetWaterGround和SetDigGround,我们以SetDigGround为例,因为我们调用SetTile方法时需要一个Vector3Int类型的变量,所有我们需要将该方法的参数转变为需要的变量,接着调用该方法,在相应的位置绘制该瓦片即可。我们编写完这个方法后,就可以在OnExecuteActionAfterAnimation中调用了,当物品类型为锄头时,我们调用SetDigGround方法,并且将该位置的瓦片的daySceneDug,canDig,canDropItem都进行修改,同理当物品类型为水壶时,我们调用SetWaterGround方法,并更改其属性daysSinceWatered。
GridMapManager脚本的新增代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.Tilemaps;
namespace MFarm.Map
{
public class GridMapManager : Singleton<GridMapManager>
{
[Header("种地瓦片切换信息")]
public RuleTile digTile;
public RuleTile waterTile;
private Tilemap digTilemap;
private Tilemap waterTilemap;
private void OnAfterSceneLoadEvent()
{
currentGrid = FindObjectOfType<Grid>();
digTilemap = GameObject.FindWithTag("Dig").GetComponent<Tilemap>();
waterTilemap = GameObject.FindWithTag("Water").GetComponent<Tilemap>();
}
/// <summary>
/// 在世界地图上执行实际工具或者物品功能
/// </summary>
/// <param name="mouseWorldPos">鼠标坐标</param>
/// <param name="itemDetails">物品信息</param>
private void OnExecuteActionAfterAnimator(Vector3 mouseWorldPos, ItemDetails itemDetails)
{
var mouseGridPos = currentGrid.WorldToCell(mouseWorldPos);
var currentTile = GetTileDetailsOnMousePosition(mouseGridPos);
if (currentTile != null)
{
//WORKFLOW:物品使用实际功能
switch (itemDetails.itemType)
{
case ItemType.Commodity:
EventHandler.CallDropItemEvent(itemDetails.itemID, mouseWorldPos);
break;
case ItemType.HoeTool:
SetDigGround(currentTile);
currentTile.daySceneDug = 0;
currentTile.canDig = false;
currentTile.canDropItem = false;
//设置音效
break;
case ItemType.WaterTool:
SetWaterGround(currentTile);
currentTile.daysSinceWatered = 0;
//设置音效
break;
}
}
}
/// <summary>
/// 显示挖坑信息
/// </summary>
/// <param name="tile"></param>
private void SetDigGround(TileDetails tile)
{
Vector3Int pos = new Vector3Int(tile.gridX, tile.gridY, 0);
if (digTilemap != null)
digTilemap.SetTile(pos, digTile);
}
/// <summary>
/// 显示浇水信息
/// </summary>
/// <param name="tile"></param>
private void SetWaterGround(TileDetails tile)
{
Vector3Int pos = new Vector3Int(tile.gridX, tile.gridY, 0);
if (waterTilemap != null)
waterTilemap.SetTile(pos, waterTile);
}
}
}
接着我们需要完善其他的代码,返回CursorManager脚本的CheckCursorValid方法,我们也需要补全这两个物品的判断类型,当当前的网格可以挖坑时,鼠标设置为有效状态,反之无效状态;浇水的判断也是同理,当前网格如果已经被挖坑,那么将鼠标设置为有效状态,反之设置为无效状态。
CursorManager脚本的CheckCursorValid方法代码如下:
private void CheckCursorValid()
{
mouseWorldPos = mainCamera.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y,-mainCamera.transform.position.z));
mouseGridPos = currentGrid.WorldToCell(mouseWorldPos);
var playerGridPos = currentGrid.WorldToCell(playerTransform.position);
//判断是否在适用范围内
if (Mathf.Abs(mouseGridPos.x - playerGridPos.x) > currentItem.itemUseRadius || Mathf.Abs(mouseGridPos.y - playerGridPos.y) > currentItem.itemUseRadius)
{
SetCursorInVaild();
return;
}
//Debug.Log(mouseGridPos);
TileDetails currentTile = GridMapManager.Instance.GetTileDetailsOnMousePosition(mouseGridPos);
if (currentTile != null)
{
switch (currentItem.itemType)
{
case ItemType.Commodity:
if (currentTile.canDropItem && currentItem.canDropped) SetCursorVaild(); else SetCursorInVaild();
break;
case ItemType.HoeTool:
if (currentTile.canDig) SetCursorVaild(); else SetCursorInVaild();
break;
case ItemType.WaterTool:
if (currentTile.daySceneDug > -1 && currentTile.daysSinceWatered == -1) SetCursorVaild(); else SetCursorInVaild();
break;
}
}
else
{
SetCursorInVaild();
}
}
返回Unity将锄头,水壶添加进入背包,并且尝试可不可以实现之前的方法嘞!!!
(我的现在还是有点问题,就是我的挖坑的位置总是偏移了一段距离)
两天后…
我终于知道为啥了,因为我刚开始的没有将Dig层的坐标归零,导致最后出现了x为-2,y为0.44的偏移,现在明白了坐标归零的重要性~~~(狗头)。