1小时粗读与魔改Unity优秀俄罗斯方块模板 Tetris Template

3 篇文章 0 订阅

因为unity涉及到很多的操作,因本人精力有限无法按步奏截图。所以主要操作还是制作了视频 https://www.bilibili.com/video/av35652505
本篇文章主要目的是为了记录代码

首先是创建蛇的主要控制脚本

using UnityEngine;

public class SnakeControl : MonoBehaviour
{

    public float transitionInterval = 0.8f;
    public float fastTransitionInterval;
    private float lastFall = 0;

    private bool isSnake = true;

    public GameObject[] snakeBody = new GameObject[4];//持有对蛇身体的引用,因为这里长度是固定为4,正常贪吃蛇游戏用list或者链表

    // Use this for initialization
    void Start()
    {
        if (!Managers.Grid.IsValidGridPosition(this.transform))//开始时限判断是否直接游戏结束
        {
            Managers.Game.SetState(typeof(GameOverState));
            Destroy(this.gameObject);
        }
    }

    public void SnakeUpdate()
    {
        if (isSnake)//判断作为蛇的移动还是俄罗斯方块下落
            SnakeMove();
        else
            FreeFall();
    }

    public void SnakeMove()
    {
        if (Time.time - lastFall >= transitionInterval)//按指定间隔时间移动
        {
            Managers.Audio.PlayDropSound();

            //移动
            for (int i=3;i>=1;i--)
            {
                snakeBody[i].transform.position = snakeBody[i - 1].transform.position;
            }
            snakeBody[0].transform.position += Managers.Game.inputDir;
            Managers.Game.lastDir = Managers.Game.inputDir;

            //判断吃星星
            if(snakeBody[0].transform.position==Managers.Game.currentStar.position)
            {
                EatStar();
                Managers.Grid.UpdateGrid(this.transform);
            }

            // See if valid
            if (Managers.Grid.IsValidGridPosition(this.transform))
            {
                // It's valid. Update grid.
                Managers.Grid.UpdateGrid(this.transform);
            }
            else
            {
                Managers.Game.SetState(typeof(GameOverState));
                Destroy(this.gameObject);
            }
            lastFall = Time.time;
        }
    }
    /// <summary>
    /// 吃到星星
    /// </summary>
    private void EatStar()
    {
        isSnake = false;
        Destroy(Managers.Game.currentStar.gameObject);
        Managers.Score.OnScore(1);
        Managers.Audio.PlayDropSound();
        Managers.Input.isActive = false;
    }
    /// <summary>
    /// 下落
    /// </summary>
    public void FreeFall()
    {
        if (Time.time - lastFall >= fastTransitionInterval)
        {
            // Modify position
            transform.position += Vector3.down;

            Managers.Audio.PlayDropSound();

            // See if valid
            if (Managers.Grid.IsValidGridPosition(this.transform))
            {
                // It's valid. Update grid.
                Managers.Grid.UpdateGrid(this.transform);
            }
            else
            {
                // It's not valid. revert.
                transform.position += new Vector3(0, 1, 0);

                GetComponent<SnakeControl>().enabled = false;
                Managers.Game.snakeControl= null;

                // Clear filled horizontal lines
                Managers.Grid.PlaceShape();
            }

            lastFall = Time.time;
        }
    }
}

修改Scripts/Utils/Constans.cs 再最后一行增加一个静态常量表示当前游戏有多少行

public static int CURRENT_ROW = 16;

修改修改自带的Scripts/Managers/GridManager.cs 我在要修改的地方都加了注释,例如判断是否出界要判断向上是否出界,还有检测方格的行数循环都改成了只循环到现在函数,在最后增加一个函数CanBePlaceStar来检测该行是否能放至星星(这里如果要做的通用一点可以改叫改行是否有方块)

using UnityEngine;
using System.Collections;


[System.Serializable]
public class Column
{
    public Transform[] row = new Transform[20];
}

public class GridManager : MonoBehaviour
{
    public Column[] gameGridcol = new Column[10];

    public bool InsideBorder(Vector2 pos)
    {
        //原来俄罗斯方块不会向上,但是贪吃蛇会,这里多做一个判断
        return ((int)pos.x >= 0 && (int)pos.x < 10 && (int)pos.y >= 0&&(int)pos.y<Constants.CURRENT_ROW);
    }

    public void PlaceShape()
    {
        int y = 0;

        StartCoroutine(DeleteRows(y));

    }

    IEnumerator DeleteRows(int k)
    {
        for (int y = k; y < Constants.CURRENT_ROW; ++y)//这里修改为只检测到当前行数
        {
            if (IsRowFull(y))
            {
                DeleteRow(y);
                DecreaseRowsAbove(y + 1);
                --y;
                Managers.Audio.PlayLineClearSound();
                yield return new WaitForSeconds(0.8f);
            }
        }     
        
        foreach (Transform t in Managers.Game.blockHolder)
            if (t.childCount <= 1)
            {
                Destroy(t.gameObject);
            }

        //New shape will be spawned
        Managers.Spawner.Spawn();

        yield break;
    }

    public bool IsRowFull(int y)
    {
        for (int x = 0; x < 10; ++x)
            if (gameGridcol[x].row[y] == null)
                return false;
        return true;
    }

    public void DeleteRow(int y)
    {
        Managers.Score.OnScore(1);//分数做了修改
        for (int x = 0; x < 10; ++x)
        {
            Destroy(gameGridcol[x].row[y].gameObject);
            gameGridcol[x].row[y] = null;
        }
    }

    public void DecreaseRowsAbove(int y)
    {
        for (int i = y; i < Constants.CURRENT_ROW; ++i)
            DecreaseRow(i);
    }

    public void DecreaseRow(int y)
    {
        for (int x = 0; x < 10; ++x)
        {
            if (gameGridcol[x].row[y] != null)
            {
                // Move one towards bottom
                gameGridcol[x].row[y - 1] = gameGridcol[x].row[y];
                gameGridcol[x].row[y] = null;

                // Update Block position
                gameGridcol[x].row[y - 1].position += new Vector3(0, -1, 0);
            }
        }
    }

    public bool IsValidGridPosition(Transform obj)
    {
        foreach (Transform child in obj)
        {
            if (child.gameObject.tag.Equals("Block"))
            {
                Vector2 v = Vector2Extension.roundVec2(child.position);

                if (!InsideBorder(v))
                {
                    return false;
                }

                // Block in grid cell (and not part of same group)?
                if (gameGridcol[(int)v.x].row[(int)v.y] != null &&
                    gameGridcol[(int)v.x].row[(int)v.y].parent != obj)
                    return false;
            }
        }
        return true;
    }

    public void UpdateGrid(Transform obj)
    {
        for (int y = 0; y < Constants.CURRENT_ROW; y++)//这里修改为只检测到当前行数
        {
            for (int x = 0; x < 10; x++)
            {
                if (gameGridcol[x].row[y] != null)
                {
                    if (gameGridcol[x].row[y].parent == obj)
                        gameGridcol[x].row[y] = null;
                }
            }
        }

        foreach (Transform child in obj)
        {
            if (child.gameObject.tag.Equals("Block"))
            {
                Vector2 v = Vector2Extension.roundVec2(child.position);
                gameGridcol[(int)v.x].row[(int)v.y] = child;
            }
        }
    }

    public void ClearBoard()
    {
        for (int y = 0; y < Constants.CURRENT_ROW; y++)//这里修改为只检测到当前行数
        {
            for (int x = 0; x < 10; x++)
            {
                if (gameGridcol[x].row[y] != null)
                {
                    Destroy(gameGridcol[x].row[y].gameObject);
                    gameGridcol[x].row[y] = null;
                }
            }
        }

        foreach (Transform t in Managers.Game.blockHolder)
            Destroy(t.gameObject);
    }

    public bool CanBePlaceStar(int y)
    {
        for(int x=0;x<10;x++)
        {
            if (gameGridcol[x].row[y] != null)
            {
                return false;
            }
        }
        return true;
    }
}

重写自带的Scripts/Managers/SpawnManager.cs 孵化脚本

using UnityEngine;
using System.Collections;

public class SpawnManager : MonoBehaviour {

	public GameObject snake;
    public GameObject star;

    public void Spawn()
	{
        //他们的父亲都要指定为BlockHolder,方便游戏重新开始时自动清理
        SpawnSnake();
        SpawnStar();
    }
    /// <summary>
    /// 孵化蛇
    /// </summary>
    private void SpawnSnake()
    {
        //创建蛇,并让总游戏管理持有对他的引用,之后重置初始方向、开启用户输入等
        Managers.Game.snakeControl=Instantiate(snake, Managers.Game.blockHolder).GetComponent<SnakeControl>();

        Managers.Game.inputDir = Vector3.right;
        Managers.Game.lastDir = Vector3.right;
        Managers.Input.isActive = true;
    }
    /// <summary>
    /// 孵化星星
    /// </summary>
    private void SpawnStar()
    {
        //最顶层无法放至直接游戏结束
        if(!Managers.Grid.CanBePlaceStar(15))
        {
            Managers.Game.SetState(typeof(GameOverState));
            return;
        }

        int y = Random.Range(2, Constants.CURRENT_ROW - 1);
        //第二顶层无法放至就放到最顶层,否则一直随机到一个可以放至的行
        if (!Managers.Grid.CanBePlaceStar(14))
            y = 15;
        else
        {
            while(!Managers.Grid.CanBePlaceStar(y))
                y = Random.Range(y+1, Constants.CURRENT_ROW - 1);
        }
        int x = Random.Range(0, 10);
        Managers.Game.currentStar = Instantiate(star, new Vector3(x, y), Quaternion.identity, Managers.Game.blockHolder).transform;
    }
}

重写Scripts/Managers/PlayerInputManager.cs 中的KeyboardInput()函数,改成

        //输入方向不能与上次移动的方向相反
        if (Input.GetKeyDown(KeyCode.UpArrow) && Managers.Game.lastDir != Vector3.down)
            Managers.Game.inputDir = Vector3.up;
        else if (Input.GetKeyDown(KeyCode.DownArrow) && Managers.Game.lastDir != Vector3.up)
            Managers.Game.inputDir = Vector3.down;
        else if (Input.GetKeyDown(KeyCode.LeftArrow) && Managers.Game.lastDir != Vector3.right)
            Managers.Game.inputDir = Vector3.left;
        else if (Input.GetKeyDown(KeyCode.RightArrow) && Managers.Game.lastDir != Vector3.left)
            Managers.Game.inputDir = Vector3.right;

然后修补一下BUG,在Scrpits/UI/Buttons/RestartButton.cs 的OnClickRestartButton()方法最后增加一行

 Managers.Score.ResetScore();

最后呢再谈谈视频里没有说到的东西,例如打包到windows平台时,自带的设置都是宽屏的,我们可以把自带的设置关掉(具体的百度,这里就不多讲了),然后手动设置一个竖屏的分辨率

        if (Application.platform == RuntimePlatform.WindowsPlayer)
            Screen.SetResolution(540, 960, false);

还有一个也是视频里只在最后提到的,如果打包到移动平台要做触控的呢,可以通过按下和松开两个的位置做对比,首先判断X和Y哪个差值更大,来确定是左右还是上下,然后再判断差值是正数还是负数来确定具体朝哪个方向更多一点,我这里给出我之前已经发布过安卓的代码给大家参考,不过因为变量命名不一样所以不能直接用,改一下也很简单

    Vector2 startPos = Vector2.zero;
    Vector2 curPos = Vector2.zero;
    float timer = 0f;//如果不是判断手抬起时,而是要长按的话可以设个时间变量,超过间隔开始判断
    float offsetTime = 0.1f;

    void TouchInput()
    {
        
        if (Event.current.type == EventType.MouseDown)
        {
            startPos = Event.current.mousePosition;//按下位置
        }
        if (Event.current.type == EventType.MouseUp)
        {
                curPos = Event.current.mousePosition;//松开位置和差值
                float x = curPos.x - startPos.x;
                float y = curPos.y - startPos.y;

                if (Mathf.Abs(x) > Mathf.Abs(y) && x <0 && Managers.Game.lastDirection != Vector3.right)//通过判断x和y哪个差值更大确定上下还是左右,判断正负来真正确定某一位置
                {
                    Managers.Game.currnetDirection = Vector3.left;
                }
                else if (Mathf.Abs(x) > Mathf.Abs(y) && x >= 0 && Managers.Game.lastDirection != Vector3.left)
                {
                    Managers.Game.currnetDirection = Vector3.right;
                }
                else if (Mathf.Abs(x) < Mathf.Abs(y) && y >= 0 && Managers.Game.lastDirection != Vector3.up)
                {
                    Managers.Game.currnetDirection = Vector3.down;
                }
                else if (Mathf.Abs(x) < Mathf.Abs(y) && y < 0 && Managers.Game.lastDirection != Vector3.down)
                {
                    Managers.Game.currnetDirection = Vector3.up;
                }
        }
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值