本人的小游戏“方块消了个消”已经上线啦!!!欢迎各位来体验,希望大家多多提意见哦~
微信公众号:unity学习加油站 ,
注:本系列更完会在公众号上放置源码,本系列会包括以下几种游戏
前言:本文主要内容,制作上图中第一个游戏,现在开始吧,喜欢的朋友点个赞吧!!!
第一步:准备素材,导入Unity中制作单个游戏形状预制件
形状预制件层级结构详解如下:
第二步:场景制作,BlockParent作为生成单个游戏物体的父物体,游戏物体的移动范围需要控制在这个父物体的显示范围内;
场景中新建一个Canvas,命名为PlayRoom,修改Canvas组件参数;
1,将渲染模式修改为摄像机模式,然后将主摄像机拖拽到渲染摄像机中;
2,修改Canvas Scaler的缩放模式,参考分辨率设置为720*1560,屏幕匹配模式设置为Match Width Or Height(宽度或高度适配),将匹配值修改为1(根据高度适配,因为是竖屏游戏)
第三步:实现单个游戏物体的拖拽和点击
1,创建脚本命名为RemoveBlockGamePlayRoom,将其拖拽到PlayRoom物体上
2,编写脚本实现点击和拖拽功能;
public Camera mainCamera;//用于获取屏幕坐标到世界坐标的转换。
private Vector3 downPos; //存储鼠标按下时的屏幕位置。
private bool isDragging; //标记是否正在拖拽对象。
private SingleBlockItem chooseBlockItem; //当前选中的 SingleBlockItem 对象
当鼠标左键按下时,重置 chooseBlockItem 和 isDragging。记录鼠标按下的位置 downPos。从 mainCamera 发射一条射线,检测是否有2D碰撞体被击中。如果有碰撞体被击中,尝试获取其父对象上的 SingleBlockItem 组件,并将其赋值给 chooseBlockItem。
if (Input.GetMouseButtonDown(0))
{
chooseBlockItem = null;
isDragging = false;
downPos = Input.mousePosition;
var ray = mainCamera.ScreenPointToRay(downPos);
var rayCastHit2D = Physics2D.Raycast(ray.origin, ray.direction);
if (rayCastHit2D.transform != null)
{
rayCastHit2D.transform.parent.TryGetComponent(out chooseBlockItem);
}
}
如果鼠标左键一直按着,并且 chooseBlockItem 不为空: 检查鼠标当前位置与按下位置的距离是否大于5个单位。如果距离大于5个单位,设置 isDragging 为 true,表示开始拖拽,将鼠标当前位置转换为世界坐标(需要限制物体只能在一定范围内移动),将 chooseBlockItem 的位置更新为这个新的世界坐标;
if (Input.GetMouseButton(0))
{
if (chooseBlockItem != null)
{
// 当前位置与按下位置距离大于5时视为拖拽游戏物体移动,否则视为点击
if (Vector3.Distance(downPos, Input.mousePosition) > 5)
{
// 开始移动
isDragging = true;
var tempPos = mainCamera.ScreenToWorldPoint(Input.mousePosition);
// 现在游戏物体只能在父物体范围内移动
tempPos.z = 0;
tempPos.x = Mathf.Clamp(tempPos.x, -1.73f, 1.73f);
tempPos.y = Mathf.Clamp(tempPos.y, -2.21f, 3.07f);
chooseBlockItem.transform.position = tempPos;
}
}
}
当鼠标左键释放时: 如果 chooseBlockItem 不为空且没有进行拖拽(即只是点击),调用 ChooseBlockItem 方法。重置 chooseBlockItem 和 isDragging。
if (Input.GetMouseButtonUp(0))
{
if (chooseBlockItem != null && !isDragging)
{
ChooseBlockItem(chooseBlockItem);//具体实现见下文
}
chooseBlockItem = null;
isDragging = false;
}
第四步:关卡制作,即游戏开始时在场景中随机生成游戏物体,根据要生成的组的数量(三个元素一组,三个相同的元素才能消除),每组先随机一个形状的物和颜色,然后每组生成三个,这个三个物体再随机设置位置;最后随机物体的层级显示,具体实现如下:
private int totalBlockCount; // 当前关卡生成的所有方块的数量
// 根据需要生成组的数量(一个游戏物体需要生成三个,因为消除时需要三个匹配才能消除),随机生成游戏物体在场景中
private void LoadGame(int loadGroupCount)
{
totalBlockCount = loadGroupCount * 3;
for (var i = 0; i < loadGroupCount; i++)
{
// 随机游戏物体的颜色和形状
var randomColor = RemoveBlockGameConfig.Instance.ColorConfigs[Random.Range(0, RemoveBlockGameConfig.Instance.ColorConfigs.Count)];
var randomShape = RemoveBlockGameConfig.Instance.BlockItems[Random.Range(0, RemoveBlockGameConfig.Instance.BlockItems.Count)];
for (var j = 0; j < 3; j++)
{
var blockItem = Instantiate(randomShape, blockItemParent);
// 随机游戏物体的位置,限制在父物体的范围内
var randomPos = new Vector3(Random.Range(-1.73f, 1.73f), Random.Range(-2.21f, 3.07f), 0);
blockItem.SetPos(randomPos, false);
// 随机设置旋转和颜色
blockItem.SetRotation(new Vector3(0, 0, Random.Range(0, 360f)));
blockItem.SetColor(randomColor);
}
}
// 再随机设置生成物体的覆盖层级
for (var i = 0; i < blockItemParent.childCount; i++)
{
blockItemParent.GetChild(i).transform.SetSiblingIndex(Random.Range(0, blockItemParent.childCount - 1));
}
}
游戏的配置信息采用的是ScriptableObject存储,目前使用到的配置是游戏物体的形状和颜色;具体如下:
[CreateAssetMenu(fileName = nameof(RemoveBlockGameConfig), menuName = "SO/" + nameof(RemoveBlockGameConfig))]
public class RemoveBlockGameConfig : ScriptableObject
{
private static RemoveBlockGameConfig instance;
public static RemoveBlockGameConfig Instance
{
get
{
if (instance == null)
{
instance = Resources.LoadAsync<ScriptableObject>("SO/RemoveBlockGameConfig").GetAwaiter().GetResult() as RemoveBlockGameConfig;
}
return instance;
}
}
// 配置游戏中能生成的游戏物体的形状
public List<SingleBlockItem> BlockItems;
// 配置游戏中能生成游戏物体的颜色
public List<Color> ColorConfigs;
}
效果图如下:
第五步:实现物体的收集,点击物体即放置在收集篮内,实现效果如下:
1,制作收集篮预制体,创建一个空物体命名为BasketItemParent作为所有篮子的父物体,在其下面创建六个篮子
2,单个篮子的层级如下,外层一个背景,内层一个空物体用做放置的父物体,修改其缩放为0.58,以致能够容纳最大的游戏物体;
3,编写单个篮子的脚本,添加到每一个BasketItem物体上,具体如下:
public class SingleBasketItem : MonoBehaviour
{
[SerializeField] private Transform blockParent;//放置方块的父物体
public SingleBlockItem BlockItem { get; private set; }//当前篮子放置的方块
public bool HasPutBlock => BlockItem != null;//判断当前篮子是否有篮子
// 向当前篮子放置方块
public void SetBlock(SingleBlockItem block)
{
if (block != null)
{
block.transform.SetParent(blockParent);
block.SetBlockInteractable(false);
}
BlockItem = block;
}
}
4,实现点击方块进行收集;成功点击方块后,判断是否有空篮子,如果有空篮子,则将当前点击的方块放置对应的篮子中,对篮子进行排序,将相同类型的方块放置在一起;具体代码如下:
[SerializeField] private RectTransform basketParent; // 篮子父物体
[SerializeField] private SingleBasketItem[] basketItems; // 所有的篮子
[SerializeField] private ParticleSystem destroyEffect; // 消除特效
private void ChooseBlockItem(SingleBlockItem blockItem)
{
if(blockItem == null) return;
// 找到空篮子,将当前选择的放置在其中
var tempBasket = GetEmptyBasket();
if (tempBasket != null)
{
tempBasket.SetBlock(blockItem);
// 判断插入的位置的index,将相同的元素放置在一起
var insertIndex = -1;
for (var i = basketItems.Length - 1; i >= 0 ; i--)
{
if (tempBasket != basketItems[i] && basketItems[i].HasPutBlock && basketItems[i].BlockItem.ShapeIndex == blockItem.ShapeIndex &&
basketItems[i].BlockItem.ColorIndex == blockItem.ColorIndex)
{
insertIndex = basketItems[i].transform.GetSiblingIndex();
break;
}
}
if (insertIndex >= 0)
{
tempBasket.transform.SetSiblingIndex(insertIndex + 1);
}
LayoutRebuilder.ForceRebuildLayoutImmediate(basketParent);
blockItem.MoveTo(tempBasket.transform.position, () =>
{
// 判断是否有可以合成的元素,如果有,则进行消除,如果没有,则判断是否还有空篮子,如果没有则游戏失败
CheckHasBlockCanRemove();
CheckIsGameOver();
});
}
}
CheckHasBlockCanRemove方法判断是否有满足消除条件(三个相同的可以进行消除)的篮子,如果有,则将对应的内容消除,然后对篮子进行排序,将非空篮子放置在最前面,具体代码如下:
// 检查篮子中是否有相同的元素可以进行消除
private void CheckHasBlockCanRemove()
{
// 找到连续三个相同的形状和颜色进行消除
var tempShapeIndex = -1;
var tempColorIndex = -1;
var sameBlockCount = 1;
for (var i = 0; i < basketParent.childCount; i++)
{
var item = basketParent.GetChild(i).GetComponent<SingleBasketItem>();
if (item.HasPutBlock)
{
if (tempShapeIndex == item.BlockItem.ShapeIndex && tempColorIndex == item.BlockItem.ColorIndex)
{
sameBlockCount++;
if (sameBlockCount >= 3)
{
break;
}
}
else
{
tempShapeIndex = item.BlockItem.ShapeIndex;
tempColorIndex = item.BlockItem.ColorIndex;
sameBlockCount = 1;
}
}
}
if (sameBlockCount >= 3)
{
var tempEmptyList = new List<int>();
var tempSeq = DOTween.Sequence()
.SetLink(gameObject)
.SetUpdate(true)
.OnComplete(() =>
{
destroyEffect.Play();
});
// 重新进行排序
for (var i = 0; i < basketParent.childCount; i++)
{
var item = basketParent.GetChild(i).GetComponent<SingleBasketItem>();
if (item.HasPutBlock)
{
if (item.BlockItem.ColorIndex == tempColorIndex &&
item.BlockItem.ShapeIndex == tempShapeIndex && sameBlockCount > 0)
{
tempEmptyList.Add(item.transform.GetSiblingIndex());
var tempBlock = item.BlockItem.gameObject;
tempSeq.Insert(0, item.BlockItem.transform.DOMove(new Vector3(0, -2.3f, 0), 0.45f))
.InsertCallback(0.45f, () =>
{
DestroyImmediate(tempBlock);
});
item.SetBlock(null);
sameBlockCount--;
}
else
{
if (tempEmptyList.Count > 0)
{
tempEmptyList.Add(item.transform.GetSiblingIndex());
item.transform.SetSiblingIndex(tempEmptyList[0]);
tempEmptyList.RemoveAt(0);
}
}
}
}
}
}
CheckIsGameOver方法判断当前游戏是由还有空篮子,如果没有,则游戏失败,调用GameOver方法,具体代码如下:
private void CheckIsGameOver()
{
var hasEmptyBasket = false;
foreach (var item in basketItems)
{
if (!item.HasPutBlock)
{
hasEmptyBasket = true;
break;
}
}
if (!hasEmptyBasket)
{
GameOver(false);
}
}
private void GameOver(bool isWin)
{
Debug.LogError("gameOver");
// 分别处理胜利和失败的逻辑
}
第六步:添加时间维度,让游戏在一定时间内结束,如果时间到了,场景中还有没有消除的方块,则游戏结束;
场景中增加倒计时的展现,接下来编写代码;如下:
[SerializeField] private TextMeshProUGUI timerTmp;
private IDisposable gameTimer;
private void StartTimer()
{
var totalTime = 20;
timerTmp.text = totalTime.ToString();
gameTimer?.Dispose();
gameTimer = Observable.Interval(TimeSpan.FromSeconds(1))
.Subscribe(_ =>
{
totalTime--;
timerTmp.text = totalTime.ToString();
if (totalTime <= 0)
{
GameOver(totalBlockCount <= 0);
gameTimer?.Dispose();
}
}).AddTo(this);
}
第七步:添加判断胜利的逻辑,在上面的LoadGame方法中,会使用totalBlockCount字段记录了当前游戏一共生成了多少个方块,每进行一次消除就将数量减3,然后判断方块是否全部消除,在CheckIsGameOver方法中补充胜利的判断;,代码如下:
private void CheckIsGameOver()
{
if (totalBlockCount <= 0)
{
GameOver(true);
return;
}
var hasEmptyBasket = false;
foreach (var item in basketItems)
{
if (!item.HasPutBlock)
{
hasEmptyBasket = true;
break;
}
}
if (!hasEmptyBasket)
{
GameOver(false);
}
}
标签:篮子,游戏,微信,物体,private,item,Unity,小游戏,var
From: https://blog.csdn.net/lel18570471704/article/details/143417358