Unity从0开发游戏上架微信小程序系列-1-完成小游戏逻辑

本人的小游戏“方块消了个消”已经上线啦!!!欢迎各位来体验,希望大家多多提意见哦~

微信公众号: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);
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值