因为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;
}
}
}