布局
祖玛游戏就不介绍了,此类游戏是关卡游戏。所以必须的先布关卡:
如图所示,必须先填充路径的点,游戏中的珠子就根据这个路径不断的向洞口靠近。
Unity 是个很好的工具,我们就使用它的编辑器功能,把球的位置给记录下来。原来是考虑用json或者xml文件记录的,后来发现LitJson对float是不支持的,但是支持double。如果使用Litjson,需要转换Vector3的时候相对麻烦。在Unity中我们考虑直接序列化,使用的是ScriptableObject。ScriptableObject是可以直接序列化成asset供unity使用,可以拖放到Inspector,可视化修改也比较方便。由此,用MenuItem调用的方式,选择场景中的Map开启记录。
[MenuItem ("Tools/Zuma/LevelConfig2Json", false, 1001)]
public static void GameLevelLayout () {
string assetPath = @"Assets/Resources/map.asset";
if (Selection.activeGameObject == null)
throw new UnityException ("must select Map");
Transform map = Selection.activeGameObject.transform;
MapConfig info = AssetDatabase.LoadAssetAtPath (assetPath, typeof (MapConfig)) as MapConfig;
bool isExist = (info != null);
if (info == null) {
Debug.Log ("AssetDatabase not exist MapConfig");
info = ScriptableObject.CreateInstance <MapConfig>() as MapConfig;
}
info.MapInfo = new Vector3 [map.childCount];
for (int i =0, L = map.childCount; i < L; ++i) {
Transform t = map.GetChild (i);
info.MapInfo [i] = t.localPosition;
}
if ( !isExist ) {
AssetDatabase.CreateAsset (info, assetPath);
return;
}
// 这里很重要,如果没有告诉unity已经被改变。它只写入内存,没有写入磁盘
EditorUtility.SetDirty (info);
AssetDatabase.SaveAssets();
}
经过操作后,map.asset文件已经保存或更新了路径位置的信息。同样我们可以把其他的一些配置信息放到这里来。这个就是游戏的配置文件了。
游戏流程
分析一下游戏的逻辑:首先左上角的洞口不断涌出珠子,点击(触摸过程调整方向,释放)发射珠子,打在队列中就与队列判断碰撞,插入队列并标记为搜索消除。如果符合规则,相连的类型数量大于等于3,消除珠子,分裂队列,播放音效与效果,此时,如果消除发生后在消除位置的后面一个与前面一个珠子的类型相同,后面的珠子需要带领它的队列段回缩,直到与前面珠子相连。游戏结束这里不作设置,如果珠子队列碰到青蛙的口,用一段时间让它整体回缩。
// Update is called once per frame
void Update() {
if (mgameState == GameState.runningTime) {
// 消除球
CheckEliminate();
// 检测球的碰撞插入
CheckFireBallCross();
// 有回缩趋势的球的检测
CheckFallBackBall ();
// 第一个球的推进
if (mAttackState == 1) {
CheckFirstSegmentAttack();
// 检测失败点
CheckGameFailurePoint();
}
// 当达到终点时队列回缩
if (mAttackState == -1) {
CheckLastSegmentAttack();
}
// 判断队列的连接
CheckSnakeConnect();
}
// 更新球的Tick
UpdateAllBallTick ();
// 更新球的渲染
UpdateBallRender();
}
游戏分析
推力
这个游戏中,在路径中的珠子我们首先以段为单位,每段由n颗珠子串连组成。如图:
当mAttackState==1时,队列向右移动。洞口第一颗珠子有推力。看图,当它推向右边就会与右边的珠子相交,右边的珠子必须向右推动,直到第一段全部推动。此时需要注意,这种情况下需要处理推到终点的情况。
// 第一段推进
void CheckFirstSegmentAttack() {
//游戏中一段珠子都没有,先初始一段
if (mSnakeSegment.Count == 0) {
Ball b = CreateActiveBall();
mSnakeSegment.Add(b);
return;
}
//如果第一段中第一个珠子已经出了出口,补充新的珠子在洞口
Ball ptr = mSnakeSegment[0];
if (ptr.IsNotHoleExit()) {
Ball b = CreateActiveBall();
b.processIndex = ptr.processIndex - 1f;
b.SetNext(ptr);
ptr.SetPre(b);
mSnakeSegment[0] = b;
ptr = b;
}
//在出洞口第一颗珠子推动
ptr.processIndex += Time.deltaTime * mAttackSpeed;
//所有的右边的珠子都要调整
while (ptr.Next != null) {
if (ptr.Next.processIndex < ptr.processIndex + 1) {
ptr.Next.processIndex = ptr.processIndex + 1;
}
ptr = ptr.Next;
}
}
当mAttackState == -1这是最后一段的最后一个珠子往左边推,这种情况下需要处理最左边的珠子会缩回出洞口的情况。
队列的连接
void CheckSnakeConnect() {
//应该先排序下
//mSnakeSegment.Sort (SortCompare);
int i = mSnakeSegment.Count;
while (i-- > 1) {
Ball q = mSnakeSegment[i];
Ball p = mSnakeSegment[i - 1];
Ball t = p.Tail;
//前一段的尾巴碰到了后一段的头
if (t.processIndex + 1 >= q.processIndex) {
q.processIndex = t.processIndex + 1;
UpdateBallAndBallDistance(q);
t.SetNext(q);
q.SetPre(t);
mSnakeSegment.RemoveAt(i);
}
}
}
珠子的插入
珠子的消除