游戏内容
鼠标图标的变化
使用一个图片来跟随鼠标移动,用该方式来表示鼠标的变化
由于是跨场景赋值,Manager是主场景,而UI是另一个场景,因此不能直接用层次窗口拖拽的方式,需要通过tag去查找我们的canvas的内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class CursorManager : MonoBehaviour
{
public Sprite normal, tool, seed, item;
private Sprite _currentSprite; // 临时承接下一帧应该使用的图标
private Image _cursorImage; // 实际的图标的图片
private RectTransform _cursorCanvas; // 通过代码获得画布的内容
private void Start()
{
/*跨场景获取鼠标图片的信息*/
_cursorCanvas = GameObject.FindGameObjectWithTag("CursorCanvas").GetComponent<RectTransform>();
_cursorImage = _cursorCanvas.GetChild(0).GetComponent<Image>();
_currentSprite = normal;
}
// 当快捷栏的物品被选取时,鼠标图案会相应更改
private void OnEnable()
{
EventHandler.ItemSelectedEvent += OnItemSelectCursorImage;
}
private void OnDisable()
{
EventHandler.ItemSelectedEvent -= OnItemSelectCursorImage;
}
private void Update()
{
if (_cursorCanvas == null) return;
_cursorImage.transform.position = Input.mousePosition;
/* 只有当鼠标与非UI互动时才修改鼠标样式 */
if (!InteractWithUI())
{
SetCursorImage(_currentSprite);
}
else
{
SetCursorImage(normal);
}
}
private void SetCursorImage(Sprite cursorSprite)
{
_cursorImage.sprite = cursorSprite;
_cursorImage.color = new Color(1, 1, 1, 1); //保证原色
}
private void OnItemSelectCursorImage(ItemDetails itemDetail, bool isSelected)
{
/*将临时图标修改为当前选取的物品类型对应的图标*/
if (isSelected)
{
_currentSprite = itemDetail.itemType switch
{
ItemType.Seed => seed,
ItemType.Commodity => item,
ItemType.ChopTool => tool,
ItemType.HoeTool => tool,
ItemType.WaterTool => tool,
ItemType.BreakTool => tool,
ItemType.ReapTool => tool,
ItemType.Furniture => tool,
_ => normal
};
}
else
{
_currentSprite = normal;
}
}
/// <summary>
/// 当前鼠标是否在与UI互动
/// </summary>
/// <returns></returns>
private bool InteractWithUI()
{
/*通过当前UI的事件系统判断, 如果指针覆盖于UI的go上,鼠标为默认央视*/
if (EventSystem.current != null && EventSystem.current.IsPointerOverGameObject())
{
return true;
}
else
{
return false;
}
}
}
|
设置瓦片地图的地图事件
方案1:设置每一个瓦片继承自一个tileGO
类似ruleTile
的形式,然后这个GO有什么属性,瓦片地图就会有什么属性(缺点:每个瓦爿都需要内容,瓦片过多时影响性能)
方案2:使用Unity提供的2D extra中的grid information
组件来解决,该组件代码能够为对应位置的瓦片设置对应的属性(int, float等,没有bool)。(问题同上)
方案3:编写一个事件系统,能够让我们以绘制collision
类似的方式去绘制信息,然后通过代码自动拿到绘制的信息存储到SO文件中,然后通过专门的代码去处理这些信息。
**思路:**首先需要设置每个Tile应该有的具体信息(坐标,事件类型,以及事件是否能被执行的变量),然后制作一个SO文件,为每个地图指定SO文件应该包含的的内容。
最后设计一个代码,该代码能够在tilemap
的绘制完成后,将绘制的信息保存到SO文件中,即当我们手动关闭Grid Propertie
的子物体的时候,进行保存。
特殊函数
1
2
3
|
Application.IsActive(this); //是否在运行
tileMap.cellBounds; // 压缩地图,获得当前GO下绘制的tileMap内容
tileMap.cellBounds.min, max; // 获得压缩后地图的左下角和右上角坐标
|
1
2
3
4
5
6
7
8
9
10
11
12
|
[System.Serializable]
public class TileProperties
{
public Vector2Int tileCoordinate; /*网格的坐标*/
public GridType gridType; /*该网格的具体类型*/
public bool boolTypeValue; /*该类型是否可用*/
}
public enum GridType
{
Diggable, DropItem, PlaceFurniture, NpcObstacle
}
|
1
2
3
4
5
6
7
|
// MapData_SO.cs
[CreateAssetMenu(fileName = "MapData_SO", menuName = "Map/MapData")]
public class MapData_SO : ScriptableObject
{
[SceneName]public string sceneName;
public List<TileProperties> tileProperties;
}
|
由于该部分内容是在编辑窗口运行的代码,在实际运行中不能直接执行,因此需要在该代码前写[ExecuteInEditMode]
并且设置该部分代码需要在GO关闭的时候运行,因此需要在代码的OnEnable
和OnDisable
部分处理
在SO文件中,每次修改后,如果不直接保存,则下一次打开会直接丢失数据,因此需要在代码中设置EditorUtility.SetDirty(mapDataSo);
来保存加载好的So文件
该图片为压缩图片后的到的数据信息
注意该代码需要每一个Property的GO都挂载,并加载内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.Tilemaps;
[ExecuteInEditMode]
public class GridMap : MonoBehaviour
{
/*设置当挂载该脚本的瓦片GO关闭时,将该GO下的所有有信息的瓦片全部存储在对应的GO中*/
public MapData_SO mapDataSo; /*存储的So文件*/
public GridType gridType; /*当前GO对应的类型*/
private Tilemap _currentTilemap;
/*由于是在GO开启和关闭的时候绘制,因此需要使用OnEnable和 OnDisable*/
private void OnEnable()
{
/*只有当程序没有运行的时候,才能执行此处的代码*/
if (!Application.IsPlaying(this))
{
_currentTilemap = GetComponent<Tilemap>();
/*为了便于不断的绘制,因此设计,开启GO就重新绘制,关闭GO就存储*/
if (mapDataSo != null)
{
mapDataSo.tileProperties.Clear();
}
}
}
/*关闭后存储绘制的内容*/
private void OnDisable()
{
/*只有当程序没有运行的时候,才能执行此处的代码*/
if (!Application.IsPlaying(this))
{
_currentTilemap = GetComponent<Tilemap>();
/*从整个界面中,遍历所有以及绘制了的瓦片,然后将其内容存储进入SO文件中*/
UpdateTileProperties();
/*只有当在编辑窗口的时候,才能保存数据*/
#if UNITY_EDITOR
if (mapDataSo != null)
{
EditorUtility.SetDirty(mapDataSo);
}
#endif
}
}
/// <summary>
/// 将所有绘制内容存储进入SO中
/// </summary>
private void UpdateTileProperties()
{
_currentTilemap.CompressBounds(); /*压缩范围,获取当前绘制瓦片的左下角和右上角返回*/
if (!Application.IsPlaying(this))
{
if (mapDataSo != null)
{
/*议会制范围可能有空*/
/*已绘制范围的左下角位置*/
Vector3Int startPos = _currentTilemap.cellBounds.min;
/*已绘制范围的右上角位置*/
Vector3Int endPos = _currentTilemap.cellBounds.max;
for (int x = startPos.x; x < endPos.x; x++)
{
for (int y = startPos.y; y < endPos.y; y++)
{
/*获得当前坐标下瓦片的的内容*/
TileBase tile = _currentTilemap.GetTile(new Vector3Int(x, y, 0));
/*如果该位置是绘制了内容的*/
if (tile != null)
{
TileProperties newTile = new TileProperties
{
tileCoordinate = new Vector2Int(x, y),
gridType = this.gridType,
boolTypeValue = true
};
mapDataSo.tileProperties.Add(newTile);
}
}
}
}
}
}
}
|
生成地图信息
创建一个管理类,用于管理地图信息,方便后续的内容获得地图信息。
思路: 首先需要创建一个管理该瓦片所有信息的类,包含所有在tileMapData
中存储的某一种Property
的属性,以及未来可能会使用的信息(是否被挖掘,是否被浇水,种了什么种子,已经长了多少天,上一次收割后隔了多少天)
然后对整个mapdata
进行遍历,将里面存放的内容初始化好后,全部读取进入一个字典中。
为了鼠标能够对这些地图信息进行识别地图信息,因此需要先设置在CursorManager
中设置好地图的整体信息,然后让鼠标能够正常获取这些坐标信息,一边后续将鼠标信息和字典的信息进行比对
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
public class GridMapManager : MonoBehaviour
{
public List<MapData_SO> mapDataSos;
private Dictionary<string, TileDetails> _tileDetailsMap
= new Dictionary<string, TileDetails>(); /*获得所有瓦片的具体信息*/
/*初始化所有瓦片的信息*/
private void Start()
{
foreach (MapData_SO mapData in mapDataSos)
{
InitTileDetailsMap(mapData);
}
}
private void InitTileDetailsMap(MapData_SO mapDataSo)
{
if (mapDataSo.tileProperties != null)
{
foreach (TileProperties tileProperty in mapDataSo.tileProperties)
{
int tileX = tileProperty.tileCoordinate.x;
int tileY = tileProperty.tileCoordinate.y;
string tileDetailKey = tileX + 'x' + tileY + 'y' + mapDataSo.sceneName;
/* 对数据进行处理,并将数据存放或更新进入map */
TileDetails tileDetails = new TileDetails
{
gridPos = new Vector2Int(tileX, tileY),
};
if (GetTileDetails(tileDetailKey) != null)
{
tileDetails = _tileDetailsMap[tileDetailKey];
}
/*更新数据*/
switch (tileProperty.gridType)
{
case GridType.Diggable:
tileDetails.canDig = tileProperty.boolTypeValue;
break;
case GridType.DropItem:
tileDetails.canDrop = tileProperty.boolTypeValue;
break;
case GridType.PlaceFurniture:
tileDetails.canPlaceFurniture = tileProperty.boolTypeValue;
break;
case GridType.NpcObstacle:
tileDetails.isNpcObstacle = tileProperty.boolTypeValue;
break;
}
if (GetTileDetails(tileDetailKey) != null)
{
_tileDetailsMap[tileDetailKey] = tileDetails;
}
else
{
_tileDetailsMap.Add(tileDetailKey, tileDetails);
}
}
}
}
private TileDetails GetTileDetails(string key)
{
if (_tileDetailsMap.ContainsKey(key))
{
return _tileDetailsMap[key];
}
return null;
}
}
|
因此先要获得摄像机的坐标已经当前地图的坐标,由于地图会随着场景切换而改变,因此需要在场景切换后才能获取该坐标
同时获取世界坐标的功能应该是实时更新的,因此摄像头理论上应该实时改变
重点函数
1
2
3
4
|
Camera.main.ScreenToWorld(new Vector3(Input.mousePosition.x, Input.mousePosition.x, -_mainCamera.transform.position.z)); //鼠标坐标) z轴是因为摄像机距离屏幕有一定距离
// 通过世界坐标转化为网格坐标
Vector3Int = Grid.WorldToCell(世界坐标)
|
1
2
3
4
5
6
7
8
9
10
|
// 该函数需要在CursorManager.cs中的Update中调用
private void CheckCursorValid()
{
/* 获得世界坐标和网格坐标*/
_mouseWorldPos = _mainCamera.ScreenToWorldPoint(
new Vector3(Input.mousePosition.x, Input.mousePosition.y, -_mainCamera.transform.position.z));
_mouseGridPos = _currentGrid.WorldToCell(_mouseWorldPos);
Debug.Log("mouseWorld::" + _mouseWorldPos + " mouseGird::" + _mouseGridPos);
}
|
注意由于获得网格坐标我们是在切换场景后才启动的,但是刚刚加入场景的时候,并没有发生场景切换,即此时并没有得到网格坐标,但是在CursorManager
中,获取网格坐标是实时更新的,因此会发送报错
该处的错误需要将在加载初始场景以后,才能执行加载场景后的事件,但是由于加载场景是使用异步加载完成的,因此会导致在加载的过程中,就调用加载后事件了,因此需要通过协程来保证先后顺序,即通过yield return
先加载场景,然后再调用加载场景后的时间,因此需要将start
函数以协程执行
协程的含义就是遇到yield return
就让出时间片,先不执行后续内容,等yield return
执行完成后再执行后续内容
1
2
3
4
5
6
7
8
|
// TransitionManager.cs
private IEnumerator Start()
{
yield return LoadSceneSetActive(startSceneName);
sceneFaderCanvasGroup = FindObjectOfType<CanvasGroup>();
/*由于将start改为了协程,可以调用场景加载后事件了了*/
EventHandler.CallAfterSceneLoadEvent();
}
|
上述内容理解:由于yield return的存在,当进行到该start的时候,会先完成场景的加载,然后在场景加载完成后EventHandler.CallAfterSceneLoadEvent();
才能被被正常调用
根据网格信息处理处理鼠标图片
根据地图信息来显示鼠标是可用还是不可用
**思路:**先获得网格字典中的信息,并将网格字典中的信息和当前选取内容进行比较,从而决定当前鼠标的样式以及当前鼠标是否可使用。
因此首先需要设计鼠标可用和不可用的样式,然后从GridMapManager
中获取内容,将当前物品的信息和地图信息进行比较,然后将物品的信息和人物位置进行比较,最终确定鼠标的状态。
1
2
3
4
5
6
7
8
9
|
private void SetCursorValid()
{
_cursorImage.color = new Color(1, 1, 1, 1);
}
private void SetCursorInvalid()
{
_cursorImage.color = new Color(1, 0, 0, 0.4f);
}
|
1
2
3
4
5
6
7
8
9
10
11
|
// 获取GridMapManager的信息,此时需要将GridMapManager改为单例模式
/// <summary>
/// 通过鼠标的GRID坐标来获取当前地图的信息
/// </summary>
/// <param name="mousePos">鼠标的GRID坐标</param>
/// <returns></returns>
public TileDetails GetTileDetailsByMousePos(Vector3Int mousePos)
{
string tileMapKey = mousePos.x + "x" + mousePos.y + "y" + SceneManager.GetActiveScene().name;
return GetTileDetails(tileMapKey);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
/// <summary>
/// 实时检测鼠标的位置,并获取地图信息
/// </summary>
private void CheckCursorValid()
{
/* 获得世界坐标和网格坐标*/
_mouseWorldPos = _mainCamera.ScreenToWorldPoint(
new Vector3(Input.mousePosition.x, Input.mousePosition.y, -_mainCamera.transform.position.z));
_mouseGridPos = _currentGrid.WorldToCell(_mouseWorldPos);
/*获得人物信息,用于比较人物和鼠标位置, 如果不满足使用范围则 不允许使用*/
Vector3Int playerGridPos = _currentGrid.WorldToCell(playerTransform.position);
if (Mathf.Abs(playerGridPos.x - _mouseGridPos.x) > _currentItem.itemUseRadius ||
Mathf.Abs(playerGridPos.y - _mouseGridPos.y) > _currentItem.itemUseRadius)
{
SetCursorInvalid();
return;
}
/*检测当前选取的物品是否满足使用要求*/
TileDetails tileDetails = GridMapManager.Instance.GetTileDetailsByMousePos(_mouseGridPos);
if (tileDetails != null && _cursorEnable)
{
switch (_currentItem.itemType)
{
case ItemType.Commodity:
if (tileDetails.canDrop && _currentItem.canDropped) SetCursorValid();
else SetCursorInvalid();
break;
}
}
else
{
SetCursorInvalid();
}
|
该过程需要获得人物信息以及当前选取的物品信息,因此需要在对应的事件中获取内容,并且只有当选中某个物品的时候,图标才能被改变形态,其他时候都不能被改变。
实现鼠标选中物品后的场景点击事件
当鼠标点击后,然后将选取的物品信息和点的坐标,传递给需要的代码
因此需要一个鼠标的点击事件,然后触发人物的动画,接着再去执行对应的的代码(扔东西,或者树木摇晃)
由于很多内容涉及到要修改地图信息,因此可以将扔东西等代码放在GridMapManager
中执行
- 检测点击事件,保证当前位置是可以点击的,然后触发点击事件
1
2
3
4
5
6
7
8
|
public void CheckPlayerInput()
{
if (Input.GetMouseButtonDown(0) && _cursorPositionValid)
{
/*调用点击事件,并传递当前点击的坐标以及当前触发点击的物品类型*/
EventHandler.CallMouseClickedEvent(_mouseWorldPos, _currentItem);
}
}
|
- 此后人物的脚本应该能接受到该事件,然后完成相应的动画,并在此触发其他事件用以驱动其他代码
1
2
3
4
5
6
|
// player.cs中挂载的事件
private void OnMouseClickedEvent(Vector3 clickWorldPos, ItemDetails itemDetails)
{
/*todo 执行对应的动画*/
EventHandler.CallExecuteActionAfterAnimationEvent(clickWorldPos, itemDetails);
}
|
- 完成丢弃功能,暂时在
GridMapManager.cs
中执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// GridMapManager.cs
// 获得_currentGrid需要通过注册场景切换后事件的方法,然后直接FindObjectOfType<Gird>寻找
private void OnExecuteActionAfterAnimationEvent(Vector3 mouseWorldPos, ItemDetails itemDetails)
{
/*触发将物品丢弃事件,并需要更新InventoryManager中存储的物品信息,且更新UI*/
Vector3Int mouseGridPos = _currentGrid.WorldToCell(mouseWorldPos);
TileDetails currentTile = GetTileDetailsByMousePos(mouseGridPos);
if (currentTile != null)
{
/*Todo携带物品完成不同事件*/
switch (itemDetails.itemType)
{
case ItemType.Commodity:
EventHandler.CallDropItemInSceneEvent(itemDetails.itemID, mouseGridPos);
break;
}
}
}
|
- 在
InventoryManager.cs
中实现物品丢弃功能的数据更改,在itemManager.cs
实现物品丢弃的地面生成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
// InventoryManager.cs
private void RemoveItem(int itemID, int removeAmount)
{
/*通过Id获取当前物品在背包中的位置,并判断对应位置的数量*/
int itemIndexInBag = GetItemIndexInBag(itemID);
if (playerBag.inventoryItemList[itemIndexInBag].itemAmount > removeAmount)
{
var amount = playerBag.inventoryItemList[itemIndexInBag].itemAmount - removeAmount;
/*更新数据*/
playerBag.inventoryItemList[itemIndexInBag] =
new InventoryItem() { itemID = itemID, itemAmount = amount };
}
else if (playerBag.inventoryItemList[itemIndexInBag].itemAmount == removeAmount)
{
playerBag.inventoryItemList[itemIndexInBag] = new InventoryItem();
}
EventHandler.CallUpdateInventoryUI(InventoryLocation.Player, playerBag.inventoryItemList);
}
/// <summary>
/// 当丢弃某一个物品时候触发的时间
/// </summary>
/// <param name="itemID">丢弃物品的ID</param>
/// <param name="pos">丢弃的坐标</param>
private void OnDropItemInSceneEvent(int itemID, Vector3 pos)
{
RemoveItem(itemID,1);
}
|
由于丢弃完成所有物品后,格子高亮应该也要清除,并放手,物品不能再被选取,因此需要修改中的清空界面的代码,该段代码是在InventoryUI.cs
中调用的SlotUI.cs
的UpdateEmptySlot
方法,因此需要修改此处。
注意高亮的更新方法是是slotui
的父级GOInventoryUI
,以便于高亮一个以后,其他高亮消失
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// SlotUI.cs
public void UpdateEmptySlot()
{
if (isSelected)
{
isSelected = false;
inventoryUI.UpdateSlotHighlight(-1);
EventHandler.CallItemSelectedEvent(slotItemDetails, isSelected);
}
slotItemDetails = null;
slotImg.enabled = false;
amountText.text = string.Empty;
btn.interactable = false;
}
|
由于我们将slotItemDetails = null;
设置为了Null,那么相应的所有以slotItemDetails
的数量来判断的地方都要修改,包括slotui
以及显示物品信息的窗口
实现物品被扔出的效果
思路:利用生成物品的移动和阴影来完成视觉差的体验,即物品从人物身边过去,而物品从人物脚底直线过去
- 首先需要一个获得阴影的代码,让阴影能够获得本身物品的样子
- 然后对物体本身使用每一帧更新物体的位置和阴影
- 最后在
itemManager.cs
重写丢弃事件的触发代码
由于物体的弹跳需要利用视觉差,因此实际阴影的移动是跟随主物体本身移动的,而物体本身的sprite
则从人物头部投出,逐渐到达目标地点
阴影代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// ItemShadow.cs
[RequireComponent(typeof(SpriteRenderer))]
public class ItemShadow : MonoBehaviour
{
public SpriteRenderer baseSprite;
private SpriteRenderer _shadowSprite;
private void Awake()
{
_shadowSprite = GetComponent<SpriteRenderer>();
}
private void Start()
{
_shadowSprite.sprite = baseSprite.sprite;
_shadowSprite.color = new Color(0, 0, 0, 0.3f);
}
}
|
物体的移动代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
// ItemBounce.cs
public class ItemBounce : MonoBehaviour
{
private Transform Player => FindObjectOfType<Player>().transform;
public float gravity = -3.5f;
private Transform _spriteTransform;
private BoxCollider2D _collider2D; /*碰撞体,保证人物在丢弃过程中,人物不能捡起*/
/*移动过程需要的参数*/
private bool _inGround; /*是否到达地面*/
private Vector3 _targetPos; /*物体的目标位置*/
private Vector2 _direction; /*物体前进的方向*/
private float _distance; /*物体和目标地点的距离*/
private void Awake()
{
_spriteTransform = transform.GetChild(0);
_collider2D = GetComponent<BoxCollider2D>();
_collider2D.enabled = false;
}
private void Update()
{
Bounce();
}
/// <summary>
/// 对物体需要移动的初始变量进行赋值
/// </summary>
/// <param name="targetPos">物体的目标距离</param>
/// <param name="dir">物体的目标方向</param>
public void InitBounceItem(Vector3 targetPos, Vector2 dir)
{
_collider2D.enabled = false;
_targetPos = targetPos;
_direction = dir;
_inGround = false;
_distance = Vector3.Distance(transform.position, targetPos);
/*保证初始位置在人物的头顶*/
_spriteTransform.position += Vector3.up * 1.75f;
}
/// <summary>
/// 物品弹跳的过程(移动过程)
/// </summary>
private void Bounce()
{
_inGround = _spriteTransform.position.y <= transform.position.y;
if (Vector3.Distance(transform.position, _targetPos) > 0.1f && !_inGround)
{
/*平面移动*/
transform.position += (Vector3)_direction * _distance * -gravity * Time.deltaTime;
}
if (!_inGround)
{
_spriteTransform.position += Vector3.up * 4f * gravity * Time.deltaTime;
}
else
{
_spriteTransform.position = transform.position;
_collider2D.enabled = true;
}
}
}
|
实现挖坑效果
为了实现挖坑的图片合理,需要指定绘制瓦片地图的规则
-
创建RuleTile
的方法,类似于创建空物体的方法在Creat->2d->Tile->ruleTile
-
创建好Tile
规则以后,需要能绘制对应的挖地和浇水的瓦片,因此也需要通过标签来找到对应的瓦片地图
-
然后再GridMapManager.cs
中实现当携带工具点击可以挖地或浇水时候的功能,此时需要构建对应的瓦片,因此需要引入对应的瓦片地图的规则,并通过代码获取对应的瓦片地图。
-
当可以执行对应的功能的时候,使用TileMap
自带的函数TileMap.setTile
来设置对应的规则,
-
最后在对应的位置设置TileDetiles
的具体内容
GridMapManager的设置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
[Header("瓦片地图规则")]
public RuleTile digRuleTile;
public RuleTile waterRuleTile;
private Tilemap _digTileMap; /*获得场景的可挖掘地图信息*/
private Tilemap _waterTileMap; /*获得场景的可浇水地图信息*/
/*挖地事件*/
case ItemType.HoeTool:
SetDigTileMap(currentTile);
currentTile.canDig = false;
currentTile.canDrop = false;
currentTile.daySinceDig = 0;
// todo 音效
break;
case ItemType.WaterTool:
SetWaterTile(currentTile);
currentTile.daySinceWater = 0;
break;
}
}
}
private void SetDigTileMap(TileDetails tileDetails)
{
Vector3Int targetTilePos = new Vector3Int(tileDetails.gridPos.x, tileDetails.gridPos.y, 0);
if (_digTileMap != null)
{
_digTileMap.SetTile(targetTilePos, digRuleTile);
}
}
private void SetWaterTile(TileDetails tileDetails)
{
Vector3Int targetTilePos = new Vector3Int(tileDetails.gridPos.x, tileDetails.gridPos.y, 0);
if (_waterTileMap != null)
{
_waterTileMap.SetTile(targetTilePos, waterRuleTile);
}
}
|
设置使用工具的动画
整体思路是在原baseController
的基础上,增加新的BlendTree
,从而增加不同的状态动画,同时为新的动画添加动画动作,同时设置工具的专门的动画,以实现在不同工具下的动画动作
-
设置工具的AnimatorOverride
动画,且都基于baseController
-
为baseController
设置工作的四个方向动画,并使用专门的变量来控制这些方向
-
为所有基于baseController
的工具动画设置对应的动画片段,并修改角色动画的修改关系
-
编写人物触发点击事件时候的动画代码,设置动画的执行过程
然后设置在人物栏专门添加工具GO,并为其增加动画控制器。
在AnimatorOverride.cs
中该部分逻辑主要是获取当前所持物品的信息,然后找出该物品信息对应的的PartType
,找出该需要改变的所有人物拥有的部分(身体,手臂,工具),为这些部分的所有动画控制器更换动画。
修改人物点击事件的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
// player.cs
private void OnMouseClickedEvent(Vector3 clickWorldPos, ItemDetails itemDetails)
{
if (_useTool) return; // 在使用工具则不能点击
/*todo 执行对应的动画*/
if (itemDetails.itemType != ItemType.Seed && itemDetails.itemType != ItemType.Furniture &&
itemDetails.itemType != ItemType.Commodity)
{
_mouseX = clickWorldPos.x - transform.position.x;
_mouseY = clickWorldPos.y - transform.position.y;
/*保证不会出现斜方向的动画*/
if (Mathf.Abs(_mouseX) > Mathf.Abs(_mouseY))
{
_mouseY = 0;
}
else
{
_mouseX = 0;
}
StartCoroutine(UseToolRoutine(clickWorldPos, itemDetails));
}
else
{
EventHandler.CallExecuteActionAfterAnimationEvent(clickWorldPos, itemDetails);
}
}
/// <summary>
/// 执行使用工具的动画,由于是一边动画,一边完成事件,因此要用协程
/// </summary>
/// <param name="mouseWorldPos">鼠标点击的位置</param>
/// <param name="itemDetails">使用的物品</param>
/// <returns></returns>
private IEnumerator UseToolRoutine(Vector3 mouseWorldPos, ItemDetails itemDetails)
{
_useTool = true;
_inputDisable = true;
yield return null; // 保证此时已经不能移动和使用工具
/*执行动画, 由于在AnimatorOverride.cs中,选择物品就修改动画,因此此时直接执行即可*/
// 将身上所有的动画都按需求执行
foreach (var anim in _animations)
{
anim.SetTrigger("useTool");
anim.SetFloat("MouseX", _mouseX);
anim.SetFloat("MouseY", _mouseY);
}
// 动画执行到一定程度,触发动作的具体代码
yield return new WaitForSeconds(0.45f);
EventHandler.CallExecuteActionAfterAnimationEvent(mouseWorldPos, itemDetails);
// 等待动画完成
yield return new WaitForSeconds(0.35f);
_inputDisable = false;
_useTool = false;
}
|
实现地图信息随事件变化
由于每次切换场景瓦片地图数据会消失,但字典中的地图信息保留,因此就需要读取字典信息,并根据内部的内容,将瓦片地图重新绘制
-
保存瓦片地图数据(通过每次执行完事件后更新字典信息完成)
-
对地图数据进行refresh
(每次切换场景,情况所有地图信息,并从字典中重新加载地图信息)
-
增加时间对地图信息的影响,每过一天,更新地图信息,并重新绘制地图
保存瓦片地图的字典信息
1
2
3
4
5
6
7
8
|
private void UpdateGridMap(TileDetails tileDetails)
{
string key = tileDetails.gridPos.x + "x" + tileDetails.gridPos.y + "y" + SceneManager.GetActiveScene();
if (_tileDetailsMap.ContainsKey(key))
{
_tileDetailsMap[key] = tileDetails;
}
}
|
重绘网格信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
/// <summary>
/// 删除地图的瓦片信息,并重新生成画面
/// </summary>
private void RefreshGridMap()
{
if (_digTileMap != null)
_digTileMap.ClearAllTiles();
if (_waterTileMap != null)
_waterTileMap.ClearAllTiles();
DisplayMap(SceneManager.GetActiveScene().name);
}
/// <summary>
/// 重新生成当前地图的所有绘制内容
/// </summary>
/// <param name="sceneName"></param>
private void DisplayMap(string sceneName)
{
foreach (var tileMapDic in _tileDetailsMap)
{
// workflow 显示当前地图的后期绘制的瓦片
/*找到当前场景的所有瓦片数据*/
string key = tileMapDic.Key;
if (key.Contains(sceneName))
{
if (tileMapDic.Value.daySinceDig > -1)
SetDigTileMap(tileMapDic.Value);
if (tileMapDic.Value.daySinceWater > -1)
SetWaterTile(tileMapDic.Value);
// todo 种子的瓦片操作
}
}
}
|
设置每日更新事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/// <summary>
/// 每天更新的事件
/// </summary>
/// <param name="day">当前的天数</param>
/// <param name="season">当期的季节</param>
private void OnGameDayEvent(int day, Season season)
{
/*将所有场景的瓦片信息中,有日期更新的数据都进行更新*/
foreach (var tileDic in _tileDetailsMap)
{
if (tileDic.Value.daySinceDig > -1)
tileDic.Value.daySinceDig++;
if (tileDic.Value.daySinceWater > -1)
tileDic.Value.daySinceWater = -1;
/*如果坑太长时间没有种植,则消失*/
if (tileDic.Value.daySinceDig > 4)
{
RestoreNotHoe(tileDic.Value);
}
}
RefreshGridMap();
}
|