使用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 ...
随机推荐
- pymongo常见的高级用法
pymongo是python中基于mongodb数据库开发出来的,比mongoengine要高级一些,也要好用一些. 基本的增删查改就不说了 insert() delete() find() upda ...
- NHibernate中几个集合的选择
NHibernate是从Hibernate移植过来的基于NET平台的一个ORM框架,同时跟这框架一起的还有一个开源库,叫做Iesi.Collections,这个库扩展了NET平台下面的几个集合,所谓集 ...
- ckeditor 触发事件(案例)
CKEDITOR.instances.positionDesc.on('blur', function() { $("#positionDescMSg").text("& ...
- 怎样删除Weblogic Domain?
转自:http://blog.csdn.net/biplusplus/article/details/7433558 旁白 由于没有现成的配置工具可以做这件事,我们需要手工来删除. 正题 以下方法适用 ...
- npm install 报错:node-pre-gyp ERR! 问题解决
npm install报错问题解决 问题: E:\CodeSpace\GitlabTest\desktop>npm install > lifeccp-desktop@1.1.9 post ...
- 解决安装完Ubuntu系统后启动项中没有Ubuntu的问题
问题出现的原因是你没有把grub安装到硬盘的起始扇区里,按理说Ubuntu在安装的时候应该能很好的处理这个问题,但有个别电脑还是会出问题.不过我们可以通用命令解决 问题. 使用U盘进入Ubuntu系统 ...
- QueryRunner
在相继学习了JDBC和数据库操作之后,我们明显感到编写JDBC代码并非一件轻松的事儿.为了帮助我们更高效的学习工作,从JDBC的繁重代码中解脱出来,xx给我们详尽介绍了一个简化JDBC操作的组件——D ...
- sqlite 小刀 初试
SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中.它是D.RichardHipp建立的公有领域项目.它的设计目标是嵌入式的,而且目前已经在很多嵌入式产 ...
- canvas学习笔记(中篇) -- canvas入门教程-- 颜色/透明度/渐变色/线宽/线条样式/虚线/文本/阴影/图片/像素处理
[中篇] -- 建议学习时间4小时 课程共(上中下)三篇 此笔记是我初次接触canvas的时候的学习笔记,这次特意整理为博客供大家入门学习,几乎涵盖了canvas所有的基础知识,并且有众多练习案例, ...
- 采集音频和摄像头视频并实时H264编码及AAC编码
转自:http://www.cnblogs.com/haibindev/archive/2011/11/10/2244442.html 0. 前言 我在前两篇文章中写了DirectShow捕获音视频然 ...