使用uGUI系统玩转标准俄罗斯方块
使用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系统玩转标准俄罗斯方块的更多相关文章
- Linux 系统应用编程——标准I/O
标准I/O的由来 标准I/O指的是ANSI C 中定义的用于I/O操作的一系列函数. 只要操作系统安装了C库,标准I/O函数就可以调用.换句话说,如果程序中使用的是标准I/O函数,那么 ...
- SAP系统玩阴的?
SAP系统玩阴的? 近日和项目上的ABAP开发顾问一起弄一个自开发的报表.其中某个栏位的取值需要从批次主数据里抓取到供应商代码,然后根据供应商代码取到供应商名称等.为此笔者需要备功能说明书.在说明书里 ...
- (系统架构)标准Web系统的架构分层
标准Web系统的架构分层 1.架构体系分层图 在上图中我们描述了Web系统架构中的组成部分.并且给出了每一层常用的技术组件/服务实现.需要注意以下几点: 系统架构是灵活的,根据需求的不同,不一定每一层 ...
- UNIX标准化及实现之UNIX标准化、UNIX系统实现、标准和实现的关系以及ISO C标准头文件
一.UNIX标准化 1.ISO C (International Organization for Standardization) 2.IEEE POSIX (Institue of Electri ...
- Linux系统层级结构标准
Linux Foundation有一套标准规范: FHS: Filesystem Hierarchy[‘haɪərɑːkɪ] Standard(文件系统层级标准)目前最新的标准是2.3版本:http: ...
- 【linux】系统编程-6-POSIX标准下的信号量与互斥锁
目录 前言 8. POSIX信号量 8.1 概念 8.2 POSIX无名信号量 8.3 POSIX有名信号量 8.4 POPSIX信号量与system V信号量的区别 9. POSIX互斥锁 9.1 ...
- 企业CRM系统选型的标准有哪些?
随着市场的发展,企业开始意识到客户的重要性.越来越多的企业形成了"以客户为核心"的理念,更加注重客户数据和管理,因此CRM客户关系管理系统成为企业的首选.选择一个适合企业的CRM系 ...
- win7系统玩游戏不能全屏的解决办法
1.修改注册表中的显示器的参数设置 Win键+R键,打开运行窗口,输入regedit回车,这样就打开了注册表编辑器,然后,定位到以下位置: HKEY_LOCAL_MACHINE\SYSTEM\ ...
- Android系统中标准Intent的使用
Android系统用于Activity的标准Intent 1.根据联系人ID显示联系人信息= Intent intent=new Intent(); intent.setAction(Intent.A ...
随机推荐
- Vue打包项目图片等静态资源的处理
项目打包,默认是打包在根目录下面的.当然我们可以通过设置,打包到任意子目录中去. 但是,当项目中引入资源的,比如:引入图片资源.js资源.或者字体图标之类的.那么可能在这个中间又会踩坑. 1.在vue ...
- c/c++的|、||、&、&&、异或、~、!运算
位运算 位运算的运算分量只能是整型或字符型数据,位运算把运算对象看作是由二进位组成的位串信息,按位完成指定的运算,得到位串信息的结果. 位运算符有: &(按位与).|(按位或) ...
- 【云计算】OpenStack Horizon DashBoard定制化,完整实现前后台交互
项目代码见GitHub:https://github.com/junneyang/openstack-customization-example 参考资料: Install and configure ...
- 使用rsync进行多服务器同步
使用rsync进行多服务器同步 @(Others) 当集群数量很大时,修改配置文件和节点之间的文件同步是一件很麻烦且浪费时间的事情. rsync是linux上实现不同机器之间文件同步.备份的工具,ce ...
- (转)dubbo design
框架设计 整体设计 图例说明: 图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口, 位于中轴线上的为双方都用到的接口. 图中从下至上分为十层,各层均为单向依赖,右边的黑 ...
- 使用Sublime经验分享
Sublime轻量级.可是绝对是神一样的编辑器. 1.代码清晰美观 2.能够选择文件夹作为文件结构文件夹显示在左側 3.以tab的形式打开多个页面在同一个窗体内 设置方法例如以下: Preferenc ...
- M.U.G.E.N Error怎么办
当运行乱舞格斗2008的时候出现以下错误. 在任务管理器中找到M.U.G.E.N.exe这个进程,右击设置相关性,然后取消勾选其中一个,点击确定. 不要关闭这个窗口,否则M.U.G.E.N这个进程也将 ...
- Android学习路线(二十一)运用Fragment构建动态UI——创建一个Fragment
你能够把fragment看成是activity的模块化部分.它拥有自己的生命周期,接受它自己的输入事件,你能够在activity执行时加入或者删除它(有点像是一个"子activity&quo ...
- ZK框架笔记2、ZK框架安装、相关类库、web及zk配置
1.先去ZK官网注册一个账号 2.在MyEclipse菜单栏中Help----Eclipse Marketplace中搜索ZK Studio,点击install安装即可 3.相关类库 ...
- lucene 建立CRUD操作
IndexSearcher indexSearcher = new IndexSearcher(LuceneUtils.getDirectory()); // 指定所用的索引库这句会引发线程安全问题, ...