使用uGUI系统玩转标准俄罗斯方块

笔者使用的Unity3D版本是4.6b17。由于一些工作上的一些事情导致制作的进度被严重滞后。笔者实际用于开发俄罗斯方块的时间,大概也就2-3天吧。

开始前的准备

想制作一个标准俄罗斯方块小游戏。先了解俄罗斯方块的游戏规则。它有I,L,J,T,O,S,Z,共7种基础形状。每一种形状可以根据一定规则变化。当一行排满消除这一行并获得相对应的游戏分数。

假如将俄罗斯方块的所有图形都可以理解为是一个九宫格9格方块的变形。俄罗斯方块的7种基础图形都可以根据9宫格削减某些部分而组成。

J型:

  T型:

  L型:

  实际游戏中笔者发现这三种图形可以通过根据红色的点为中心点顺时针旋转90度形成下一个形态的形状。一个周期为4次变形,即旋转360°之后的图形会和当前的图形重叠。

  S型:

  Z型:

I型:(是四格的,这里只显示3格是为了看上去像九宫格,实际游戏中,I型的上面还有一个格子)

  以上三种图形,笔者参考了大量的实际游戏后发现。有别于TJL三种图形。他们只有进行顺时针90度和逆时针90度这样两种变化形式。

  O型:

  O型图形是最特殊的一种方块,所以放在最后。O型是7种图形中唯一没有形态变换的形状。

  分析了7种基础图案之后。笔者最先实现的是7种方块的变化。

  写代码之前先根据我们上面的分析设计一下。

  总结如下:方块是一个Box对象。每个方块都有一个旋转中心点。都有一个状态。TLJ有四种状态、SZI有两种状态、O只有一种状态。

 /// <summary>
/// 方块旋转中心
/// </summary>
public Vector2 Center { get; set; } /// <summary>
/// 方块类型
/// </summary>
public BoxType Type { get; set; } /// <summary>
/// 方块状态 - 用于区别方块的形态
/// </summary>
public int State { get; set; } /// <summary>
/// 方块点描述
/// </summary>
public Vector2[] Pos = new Vector2[];

  Box工具类。有一些用于创建。操作方块等很多方法:

 /// <summary>
/// 方块坐标工厂
/// </summary>
/// <param name="center">中心点</param>
/// <param name="type">方块类型</param>
/// <param name="state">状态</param>
/// <returns>返回方块实例</returns>
public static Vector2[] BoxPositionFactory(Vector2 center, BoxType type, int state)
{
var pos = new Vector2[];
switch (type)
{
case BoxType.S:
/* *
* **
* *
*/
pos[] = new Vector2(center.x, center.y + );
pos[] = new Vector2(center.x, center.y);
pos[] = new Vector2(center.x + , center.y);
pos[] = new Vector2(center.x + , center.y - );
break;
case BoxType.Z:
/*
* **
* **
*/
pos[] = new Vector2(center.x, center.y);
pos[] = new Vector2(center.x - , center.y);
pos[] = new Vector2(center.x, center.y - );
pos[] = new Vector2(center.x + , center.y - );
break;
case BoxType.L:
pos[] = new Vector2(center.x - , center.y);
pos[] = new Vector2(center.x, center.y - );
pos[] = new Vector2(center.x + , center.y - );
pos[] = new Vector2(center.x - , center.y - );
break;
case BoxType.J:
pos[] = new Vector2(center.x + , center.y);
pos[] = new Vector2(center.x, center.y - );
pos[] = new Vector2(center.x + , center.y - );
pos[] = new Vector2(center.x - , center.y - );
break;
case BoxType.I:
pos[] = new Vector2(center.x, center.y + );
pos[] = new Vector2(center.x, center.y + );
pos[] = new Vector2(center.x, center.y);
pos[] = new Vector2(center.x, center.y - );
break;
case BoxType.O:
pos[] = new Vector2(center.x, center.y);
pos[] = new Vector2(center.x + , center.y);
pos[] = new Vector2(center.x + , center.y - );
pos[] = new Vector2(center.x, center.y - );
break;
case BoxType.T:
pos[] = new Vector2(center.x, center.y);
pos[] = new Vector2(center.x - , center.y);
pos[] = new Vector2(center.x + , center.y);
pos[] = new Vector2(center.x, center.y + );
break;
default:
throw new ArgumentOutOfRangeException();
}
switch (state)
{
case :
pos = BoxUtil.Rotate(pos, center, Math.PI / );
break;
case :
pos = BoxUtil.Rotate(pos, center, Math.PI);
break;
case :
pos = BoxUtil.Rotate(pos, center, (Math.PI * ) / );
break;
}
return pos;
}
 /// <summary>
/// 根据中心点选择
/// </summary>
/// <param name="center">中心点</param>
/// <param name="point">当前点</param>
/// <param name="angle">角度的弧度</param>
/// <returns></returns>
public static Vector2 BoxRotate(Vector2 point, Vector2 center, double angle)
{
/**
* 复数法:
* http://www.topschool.org/sx/sx/200904/1601.html
* http://www.tesoon.com/ask/htm/04/16403.htm
*/
angle = -angle; var result = default(Vector2);
#if !USE_COMPLEX
// 方式一:实现复数乘法计算
var x = (float)((point.x - center.x) * Math.Cos(angle) - (point.y - center.y) * Math.Sin(angle));
var y = (float)((point.x - center.x) * Math.Sin(angle) + (point.y - center.y) * Math.Cos(angle));
result = new Vector2(center.x + x, center.y + y);
#elif
// 方式二:利用复数类进行计算
var complex = new Complex(point.x - center.x, point.y - center.y) * new Complex(Math.Cos(angle), Math.Sin(angle)) + new Complex(center.x, center.y);
result = new Vector2((float)complex.Real, (float)complex.Image);
#endif
return result;
}

  ps:A点根据中线点B旋转某个角度,获得点C。笔者这里使用的复数法。由于复数是.net4.5版本之后才支持的。所以笔者这里默认是直接计算。没有.net 4.5框架的朋友,笔者在示例中增加了网上找的Complex类实现。在源码中BoxUtil类中定义了计算方法。有兴趣的可以在文章的末尾看到下载链接。欢迎下载

方块的运动

  在俄罗斯方块游戏中。任意图形都有下降、左移、右移、变化四种基础操作,方块根据难度自动下落的速度逐渐加快(表现为下落的时间间隔会逐渐缩短)。上面的Box对象类增加Down,Left,Right,Change四个方法。

  在实际游戏中还有快速左移、快速右移、快速下降等操作。为了满足这样的需求我们为这些操作增加了时间间隔和标示符。本例中经过一番的调整,最终决定将快速相关操作的时间间隔设置为50毫秒。游戏本身的基础自动下落的时间间隔为500毫秒,根据难度逐步缩短此间隔值,已达到难度加大的设计。

 /// <summary>
/// 最后一次左移或者右移
/// </summary>
private long _timeLastLeftOrRight; /// <summary>
/// 最后一次下降
/// </summary>
private long _timeLastAutoDown; /// <summary>
/// 是否快速下降
/// </summary>
public bool IsFastDown; /// <summary>
/// 快速左移
/// </summary>
public bool IsFastLeft; /// <summary>
/// 快速右移
/// </summary>
public bool IsFastRight; /// <summary>
/// 左移
/// </summary>
public void Left()
{
var now = DateTime.Now.Ticks;
if (now - _timeLastLeftOrRight <= BoxUtil.INTERVAL_FAST) return;
_timeLastLeftOrRight = now;
var position = new Vector2[Pos.Length];
for (var i = ; i < Pos.Length; i++)
position[i] = Pos[i] - Vector2.right;
if (IsCanChange(Blocks, position))
{
Center -= Vector2.right;
Pos = position;
}
} /// <summary>
/// 右移
/// </summary>
public void Right()
{
var now = DateTime.Now.Ticks;
if (now - _timeLastLeftOrRight <= BoxUtil.INTERVAL_FAST) return;
_timeLastLeftOrRight = now;
var position = new Vector2[Pos.Length];
for (var i = ; i < Pos.Length; i++)
position[i] = Pos[i] + Vector2.right;
if (IsCanChange(Blocks, position))
{
Center += Vector2.right;
Pos = position;
}
} /// <summary>
/// 下落
/// </summary>
public void Down()
{
var dynamicInterval = ;
var now = DateTime.Now.Ticks;
if ((now - _timeLastAutoDown <= dynamicInterval*) &&
(!IsFastDown || now - _timeLastAutoDown <= BoxUtil.INTERVAL_FAST)) return;
_timeLastAutoDown = now;
var position = new Vector2[Pos.Length];
for (var i = ; i < Pos.Length; i++)
position[i] = Pos[i] - Vector2.up;
if (IsCanDown(position))
{
Center -= Vector2.up;
Pos = position;
}
else
{
IsGameOver(Pos);
}
} /// <summary>
/// 方块变换
/// </summary>
public void Change()
{
var now = DateTime.Now.Ticks;
if (now - _timeLastLeftOrRight <= BoxUtil.INTERVAL_FAST) return;
_timeLastLeftOrRight = now;
var targetPos = new Vector2[Pos.Length];
for (var i = ; i < Pos.Length; i++)
targetPos[i] = Pos[i];
switch (Type)
{
case BoxType.L:
case BoxType.J:
case BoxType.T:
BoxUtil.Rotate(targetPos, Center);
break;
case BoxType.S:
case BoxType.Z:
case BoxType.I:
var isClockWise = State == ;
State = isClockWise ? : ;
BoxUtil.Rotate(targetPos, Center, isClockWise);
break;
case BoxType.O:
// Do nothing
break;
default:
throw new ArgumentOutOfRangeException();
}
if (IsCanChange(Blocks, targetPos))
Pos = targetPos;
}

边界和结束检测

实际游戏中,当方块下落移动到游戏界面的底部,此方块将会被固定在底部的位置。新的方块将会在顶部生成,然后下落。如此往复循环直至新生成的方块一次都不能下落则游戏结束。游戏的左移、右移、下落操作都不能超过游戏的边界。

 /// <summary>
/// 检查是否游戏结束
///
/// <br/>
/// 当不能移动时,原始方块的点超出边界则说明游戏结束
/// </summary>
/// <param name="position">点位置</param>
/// <returns>是否游戏结束</returns>
private bool IsGameOver(Vector2[] position)
{
foreach (var vector2 in position)
{
if (vector2.x < || vector2.y < || vector2.x >= BoxUtil.MAX_X || vector2.y >= BoxUtil.MAX_Y)
{
if (GameOver != null) GameOver(this, EventArgs.Empty);
return true;
}
}
return false;
} /// <summary>
/// 方块是否可以下落
/// </summary>
/// <param name="tp">方块的点集合</param>
/// <returns>返回方块是否可以下落</returns>
private bool IsCanDown(IEnumerable<Vector2> tp)
{
foreach (var vector2 in tp)
{
if (vector2.y < || (vector2.y < BoxUtil.MAX_Y && Blocks[(int) vector2.x, (int) vector2.y] == ))
{
if (MoveCompleted != null) MoveCompleted(this, EventArgs.Empty);
return false;
}
}
return true;
} /// <summary>
/// 方块是否可以变形
/// </summary>
/// <param name="blocks">阻挡信息</param>
/// <param name="positions">目标形状</param>
/// <returns>方块是否可以变形</returns>
private static bool IsCanChange(int[,] blocks, IEnumerable<Vector2> positions)
{
foreach (var position in positions)
{
if (position.x < || position.x >= BoxUtil.MAX_X || position.y < )
return false;
if ((position.y < BoxUtil.MAX_Y && blocks[(int) position.x, (int) position.y] == ))
return false;
}
return true;
}

随机方块生成算法

俄罗斯方块的方块随机生成算法。笔者最开始使用的是全部状态的全部随机。但是在测试游戏中感觉体验并不是很好。经过一番的资料查找和学习之后。将源码中的随机算法修改为—随机包方式。这种方式大概的内容就是,将每7个图形制作成一个包,游戏就按照一个包一个包的进行下去。这样的好处就是相同的两个图形出现的间隔最大不会超过12个。降低了完全随机生成的偶然性。

修改完之后。顺便把下个方块的预览和积分统计计算实现。

 /// <summary>
/// 随机方块包生成
/// </summary>
/// <returns>返回方块包</returns>
public static List<BoxProperty> BagRandomizer()
{
var array = new[] {BoxType.I, BoxType.J, BoxType.L, BoxType.S, BoxType.Z, BoxType.O, BoxType.T};
var result = new List<BoxProperty>(array.Length);
var random = new System.Random();
var indexList = new List<int>();
while (result.Count < array.Length)
{
var index = random.Next(, array.Length);
if (indexList.Contains(index))
continue;
indexList.Add(index); var state = ;
switch (array[index])
{
case BoxType.S:
case BoxType.I:
case BoxType.Z:
state = random.NextDouble() > 0.5 ? : ;
break;
case BoxType.L:
case BoxType.T:
case BoxType.J:
state = random.Next(, );
break;
case BoxType.O:
state = ;
break;
default:
throw new ArgumentOutOfRangeException();
} Vector2 center = random.NextDouble() > 0.5 ? new Vector2(, ) : new Vector2(, ); result.Add(new BoxProperty(center, array[index], state));
}
return result;
}

增加一个开始菜单

为游戏增加一个入口菜单。用于控制程序的进行和结束。

 /// <summary>
/// 开始游戏
/// </summary>
public void OnStartGame()
{
if (IsGameStart) return;
IsGameStart = true;
IsGameOver = false; var find = Root.Find("PanelMenu");
if (find != null)
find.gameObject.SetActive(false);
var game = Instantiate(PrefabPanelMain) as GameObject;
if (game != null)
{
game.transform.SetParent(Root, false);
game.transform.name = "Main";
}
Root.gameObject.AddComponent<Main>();
} /// <summary>
/// 结束游戏
/// </summary>
public void OnGameOver()
{
IsGameStart = false;
// 返回Menu界面
var game = Root.Find("Main");
if (game != null)
Destroy(game.gameObject);
Destroy(Root.GetComponent<Main>()); var find = Root.Find("PanelMenu");
if (find != null)
find.gameObject.SetActive(true);
}

总结

  至此,一个简单的单机的完全使用UGUI系统制作的俄罗斯方块小游戏就算完成了。UI系统的大致全部模块都有使用到。也算是对新系统的熟悉和学习吧。O(∩_∩)O哈哈~。

  后续看情况可能会继续完善此例,增加以下新的功能和完善一下界面,优化一下算法等等。

  本例使用到了Reference resolution来简单解决了屏幕自适应

  源码下载地址:http://pan.baidu.com/s/1o6lxWIe

后记

源码中实现了简单的屏幕自适应。但是写得比较粗糙。功能实现的也不是特别的完善。期盼有人能提供好的解决方案。共同交流进步。先谢谢各位不吝赐教。

作者TinyZ
出处:http://www.cnblogs.com/zou90512/
关于作者:从事于网络游戏服务端开发(JAVA)。喜欢接触和了解新技术。通过不断探索学习,提升自身价值。记录经验分享。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接
如有问题,可以通过 zou90512@126.com 联系我,非常感谢。
笔者网店: http://aoleitaisen.taobao.com. 欢迎围观

使用uGUI系统玩转标准俄罗斯方块的更多相关文章

  1. Linux 系统应用编程——标准I/O

    标准I/O的由来         标准I/O指的是ANSI C 中定义的用于I/O操作的一系列函数. 只要操作系统安装了C库,标准I/O函数就可以调用.换句话说,如果程序中使用的是标准I/O函数,那么 ...

  2. SAP系统玩阴的?

    SAP系统玩阴的? 近日和项目上的ABAP开发顾问一起弄一个自开发的报表.其中某个栏位的取值需要从批次主数据里抓取到供应商代码,然后根据供应商代码取到供应商名称等.为此笔者需要备功能说明书.在说明书里 ...

  3. (系统架构)标准Web系统的架构分层

    标准Web系统的架构分层 1.架构体系分层图 在上图中我们描述了Web系统架构中的组成部分.并且给出了每一层常用的技术组件/服务实现.需要注意以下几点: 系统架构是灵活的,根据需求的不同,不一定每一层 ...

  4. UNIX标准化及实现之UNIX标准化、UNIX系统实现、标准和实现的关系以及ISO C标准头文件

    一.UNIX标准化 1.ISO C (International Organization for Standardization) 2.IEEE POSIX (Institue of Electri ...

  5. Linux系统层级结构标准

    Linux Foundation有一套标准规范: FHS: Filesystem Hierarchy[‘haɪərɑːkɪ] Standard(文件系统层级标准)目前最新的标准是2.3版本:http: ...

  6. 【linux】系统编程-6-POSIX标准下的信号量与互斥锁

    目录 前言 8. POSIX信号量 8.1 概念 8.2 POSIX无名信号量 8.3 POSIX有名信号量 8.4 POPSIX信号量与system V信号量的区别 9. POSIX互斥锁 9.1 ...

  7. 企业CRM系统选型的标准有哪些?

    随着市场的发展,企业开始意识到客户的重要性.越来越多的企业形成了"以客户为核心"的理念,更加注重客户数据和管理,因此CRM客户关系管理系统成为企业的首选.选择一个适合企业的CRM系 ...

  8. win7系统玩游戏不能全屏的解决办法

    1.修改注册表中的显示器的参数设置   Win键+R键,打开运行窗口,输入regedit回车,这样就打开了注册表编辑器,然后,定位到以下位置:   HKEY_LOCAL_MACHINE\SYSTEM\ ...

  9. Android系统中标准Intent的使用

    Android系统用于Activity的标准Intent 1.根据联系人ID显示联系人信息= Intent intent=new Intent(); intent.setAction(Intent.A ...

随机推荐

  1. Unity3D新手教学,让你十二小时,从入门到掌握!(一) [转]

    http://blog.csdn.net/aries_h/article/details/47307799 版权声明:本文为Aries原创文章,转载请标明出处.如有不足之处欢迎提出意见或建议,联系QQ ...

  2. Net编程 详解DataTable用法【转】

    http://www.diybloghome.com/article/16.html DataTable表示一个与内存有关的数据表,可以使用工具栏里面的控件拖放来创建和使用,也可以在编写程序过程中根据 ...

  3. Shadow Map 原理和改进 【转】

    http://blog.csdn.net/ronintao/article/details/51649664 参考 1.Common Techniques to Improve Shadow Dept ...

  4. [GLSL]着色器周记02——火焰特效 【转】

    http://www.cnblogs.com/tkgamegroup/p/4214081.html 这周学了好多.包括伪随机数.柏林噪声.先说伪随机数.伪随机数我们用的是周期函数而不是那种由前一项乘一 ...

  5. JS函数节流和函数防抖问题分析

    问题1:如果实现了dom拖拽功能,但是在绑定拖拽事件的时候发现每当元素稍微移动一点便触发了大量的回调函数,导致浏览器直接卡死,这个时候怎么办? 问题2:如果给一个按钮绑定了表单提交的post事件,但是 ...

  6. 数据库建模软件ERStudio-表关系建模详解

    ERStudio是优秀的数据库建模软件,它不仅可以建立表.视图等模型,还可以建立多表间各种关系的模型,另外还可以根据模型生成表到数据库,下面具体讲解一下它的表关系建模. 1. 首先讲一下怎么建立表关系 ...

  7. 隐藏系统EFI分区Z盘

    找到C:\Windows\System32\cmd.exe程序, 右键单击cmd 选择以管理员身份运行, 打开命令提示符,输入以下命令(不区分大小写)DiskPart回车List空格volume回车s ...

  8. div 隐藏和显示

    转自:http://aideehorn.iteye.com/blog/417558 div的visibility可以控制div的显示和隐藏,但是隐藏后页面显示空白: style="visib ...

  9. 【转】C语言中结构体的位域(bit-fields)

    有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位.例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可.为了节省存储空间,并使处理简便,C语言又提供了一种数据结构 ...

  10. 用开源NGINX-RTMP-MODULE搭建FLASH直播环境

    用开源nginx-rtmp-module搭建flash直播环境 1.将nginx和nginx-rtmp-module的源码包解压PS:nginx-rtmp-module网址https://github ...