Unity2018.1
View层负责响应用户事件和页面显示, Controller层负责响应游戏逻辑和作为View层和Model层的介质. View层通过发送消息来获取Model层的状态信息.
项目中实现了一个事件管理器EventManager
, 负责事件的监听和派发. Controller层负责事件的监听以及响应.
EventManager
中设置了一个字典来存放事件内容
private readonly Dictionary> mEventDictionary = new Dictionary>();
两个接口分别提供监听和激发事件
public void Listen(UIEvent uiEvent, Action
枚举来存放事件类型
public enum UIEvent {
ENTER_PLAY_STATE,
GET_SCORE_INFO,
GAME_PAUSE,
GAME_OVER,
CLEAR_DATA,
SET_MUIE,
REFRESH_SCORE,
SHOW_ALERT,
SHOW_DIFFICULITY_PANEL,
SHOW_DEFINED_PANEL,
CAMERA_SHAKE
}
ui面板的切换有两种需求, 情况如下
项目中使用一个自定义栈MyStack
来存储管理面板, UICompositor
提供显示隐藏接口, 字段BasePanel.stack
来标记该面板被覆盖时, 是否需要保存在栈内, 以便新面板关闭时, 该面板被显示出来.
自定义栈中需要保证每个面板仅有一个缓存, 则在Push(targetPanel)
前先移除之前该面板的记录mPanelStack.Remove(targetPanel)
, 关键代码如下:
public BasePanel PushPanel(BasePanel targetPanel) {
if (targetPanel == mPanelStack.Peek()) {
return targetPanel;
}
mPanelStack.Remove(targetPanel);
var tempPanel = mPanelStack.Peek();
if (tempPanel != null) {
//如果新窗口关闭时, 它不需要再被显示
if (!tempPanel.stack) {
mPanelStack.Pop();
}
//不管有没有弹出栈, 都需要将它隐藏
tempPanel.Hide();
}
targetPanel.Show();
return mPanelStack.Push(targetPanel);
}
游戏界面右上角的分数为使用DOTween制作的数字滚动效果, 关键是使用Sequence
, 每次数据更新时, 将滚动动画添加到现有队列中, 保证滚动效果不会异常
关键实现方法为:
创建一个Sequence
并设置它的SetAutoKill
属性为false
, 防止它实现一次滚动动画之后就自动销毁.
//声明
private Sequence mScoreSequence;
//函数内初始化
mScoreSequence = DOTween.Sequence();
//函数内设置属性
mScoreSequence.SetAutoKill(false);
当数据更新时, 调用该界面负责处理该数据的方法, 此时用新的数据创建一个Tweener
, 并加入动画序列中
mScoreSequence.Append(DOTween.To(delegate (float value) {
//向下取整
var temp = Math.Floor(value);
//向Text组件赋值
currentScoreText.text = temp + "";
}, mOldScore, newScore, 0.4f));
//将更新后的值记录下来, 用于下一次滚动动画
mOldScore = newScore;
通过设置深度和颜色透明度来实现"闪烁"效果(见前面GIF)
2D效果为:
关键操作如下面3D截图
方块加速有两种条件, 一是点击↓
按钮实现当前方块的急速下落, 二是分数达到升级条件时, 以后的每一个方块的下落速度都会更快.
当个方块的急速采用的增大步伐, 每次下落五个单位(普通速度是一个单位), 但是要注意, 当方块快接近底部时, 需要逐渐减小步伐, 才能找到最合适的位置(不然可能会覆盖掉原先的方块)
关键代码如下:
private void Fall(int step = 1)
{
while (true)
{
var position = transform.position;
position.y -= step;
transform.position = position;
if (mControllerInstance.model.IsShapePositionValid(transform) == false)//触碰到了底部方块, 停止下落
{
position.y += step;
transform.position = position;
if (step == 1)//为一, 方块不会发生重叠
{
mIsPause = true;
//储存当前数据>>检测是否需要消除行
mControllerInstance.model.PlaceShape(transform);
//新shape或结束
GameManager.Instance.ShapeFallDown();
break;
}
step = step - 1;//如果不为1, 方块可能发生重叠
continue;
}
AudioManager.Instance.PlayDrop();//继续下落
break;
}
}
方块的普通加速下落使用的是缩小每一步的时间间隔, 在Update
函数内更新, 关键代码如下
//kMultiple为加速因子
//private const int kMultiple = 20;
void Update() {
if (mIsPause) {
return;
}
mTimer += Time.deltaTime;
if (mIsRocket) {
Fall(5);
if (!mHasRocket) {
EventManager.Instance.Fire(UIEvent.CAMERA_SHAKE);
mHasRocket = true;
}
}
else {
if (mTimer > (mIsSpeedUp ? normalStepTime / kMultiple : normalStepTime)) {
mTimer = 0;
Fall();
}
}
//input
InputControl();
}
使用的是DOTween提供的API, 注意相机Shake完需要设置回原位…不然它会跑偏, Σ(っ °Д °;)っ
//mCameraVector3 为相机原始的位置
private void CameraShake(object obj) {
Camera.main.DOShakePosition(0.05f, new Vector3(0, 0.2f, 0)).SetEase(Ease.Linear).OnComplete(() => {
Camera.main.transform.position = mCameraVector3;
});
}
地图使用的是单独的相机, 每个地图方块之间的间隔为1, 方便进行计算和方块的旋转和下落. 地图的原始大小固定, 而我们看到的地图和方块的大小由相机来决定.
参考的是腾讯游戏创意大赛
《游戏改变世界——游戏化如何让现实变得更美好》 中的对俄罗斯方块反馈性的描述:
俄罗斯方块让人欲罢不能,除了“不可能赢”这一点外,还在于它提供的反馈力度。
(1)视觉上,一排又一排的方块“噗噗”地消失;
(2)数量上,屏幕上的分数不断上涨;
(3)性质上,你感受到了持续上升的挑战性(速度越来越快)。
哲学家 James P. Carse 曾经写道,游戏分为两种:**一种是有尽头的游戏,我们为了获胜而玩;一种是无尽头的游戏,我们为了尽量长时间地玩下去而玩。**我们玩俄罗斯方块的用意很简单,就是把一个优秀的游戏不停地玩下去。
??
或 ?.
进行空值检查时, 可能会无意中绕过底层Unity引擎对象的生命周期检查,
CompareTag
而不是显式字符串比较gameObject.tag == "TagName"
, 后者会产生额外的内存与性能消耗 , 因为tag
属性返回的字符串是从Unity本机堆拷贝到C#托管堆的对象
jingangxin36/Tetris