Cubism Demo

Cubism Demo

[toc]

需求

功能需求

  • 方块吸附放置
  • 背景放满通关
  • 方块扔出复回

表现需求

  • 背景表现,灯光,材质
  • 摆放表现

设计与实现

方块吸附放置

表现:待放方块进入背景之中会自动吸附到背景相对应的位置之上,旋转角度吸附到最贴近的90度倍数角上。

分析:实现的关键在于连续量转离散量。连续量是物体的位置和旋转;背景和方块都由很多小方块组成,位置匹配点也是小方块的轴心位置,所以位置离散量是一个小方块的位置为间隔,旋转离散量以90度为间隔。

想让待放方块位置匹配到背景之中,并且发生合理的旋转,需要找到待放方块和背景中一对一的那对子方块。

选择出合理的一对匹配点

减少候选子方块的数量有利于提高性能。所以选择已经进入背景的子方块作为待放方块的候选子方块,再从子方块里随便选一个方块作为中心,用范围检测找出最近的背景锚点子方块,再以背景锚点子方块为中心从候选子方块中找出最近的子方块作为轴心。改变待选方块轴心后,将待选方块放在背景锚点子方块的位置上,旋转角度进行“标准化”。

/// <summary>
/// 松手且进入背景范围,动态改变物体轴心,比较轴心子方块与背景子方块位置,将方块吸附到最近的位置
/// </summary>
public void CheckCubePos()
{
    //if (firstArriveCubeTrans != null && !firstArriveCube.isGrabbed)
    if (arrivedCubes.Count != 0 && !currentCube.isGrabbed)
    {
        //背景里每个子方块的位置作为锚点,由一个个带碰撞体的方块组成;
        //物体里任何进入背景的每个子方块的位置可作为新轴心;
        //确定轴心分三步,第一步确定一个临时轴心,默认为数组第一个子方块;
        Transform pivotCube = arrivedCubes[0];

        //第二步,由这个临时轴心确定一个最近的背景落脚点
        //比较新轴心与背景方块的位置
        //以比较点为中心使用重叠球形检测出最近的背景方块;
        Collider[] BKcolliders = Physics.OverlapSphere(pivotCube.position, radius, 1 << LayerMask.NameToLayer("BackgroundCube"));

        Collider nearestBKCollider = null;
        float minDis = 1;
        foreach (var collider in BKcolliders)
        {
            float temDis = Vector3.Distance(collider.transform.position, pivotCube.position);
            print(collider.name + "与" + pivotCube.name + "距离为" + temDis);

            if (temDis < minDis)
            {
                minDis = temDis;
                nearestBKCollider = collider;
            }
        }
        if (nearestBKCollider != null)
        {
            //第三步,由这个背景落脚点确定最终轴心,并设置轴心,从已经进入背景的方块找一个离最终点最近的
            Transform nearestCube = null;
            minDis = 1;
            foreach (var cube in arrivedCubes)
            {
                float temDis = Vector3.Distance(cube.position, nearestBKCollider.transform.position);
                print(cube.name + "与" + pivotCube.name + "距离为" + temDis);

                if (temDis < minDis)
                {
                    minDis = temDis;
                    nearestCube = cube;
                }
            }

            pivotCube = nearestCube;//重置最终轴心;
            SetNewPivot3(currentCube.transform, pivotCube.position);

            //设置位置+旋转

            ////赋值方块位置最近点+偏移值;
            //Vector3 finalPos = nearestCollider.transform.position - pivotCube.localPosition * pivotCube.lossyScale.x;

            //因为轴心以改变,最终位置就是最近背景方块的位置
            Vector3 finalPos = nearestBKCollider.transform.position;

            //旋转按照最近轴算
            Quaternion finalQuaternion = Quaternion.Euler(CorrectAngle(currentCube.transform.eulerAngles.x), CorrectAngle(currentCube.transform.eulerAngles.y), CorrectAngle(currentCube.transform.eulerAngles.z));


            currentCube.transform.SetPositionAndRotation(finalPos, finalQuaternion);

            currentCube.GetComponent<Rigidbody>().isKinematic = true;
            print(pivotCube.rotation.eulerAngles);


            ////检查是否通关
            //if (CheckIsFull())
            //{
            //    Debug.LogError("通关!");
            //    UIMain.Instance.ShowPanel<WinPanel>();
            //}


            StartCoroutine(CheckIsFull3());
        }

    }
}

///// <summary>
///// 检查背景是否放满了方块
///// </summary>
IEnumerator CheckIsFull3()
{
    //isFull = true;
    yield return new WaitForFixedUpdate();//等待物理帧更新
    isFull = true;
    Collider[] colliders;
    //对每个背景子方块进行重叠盒型检测,若都有东西,则返回真
    for (int i = 1; i < BKCubes.Length; i++)
    {
        colliders = Physics.OverlapBox(BKCubes[i].position, (BKCubes[i].GetComponent<BoxCollider>().size * transform.lossyScale.x) / 2.0f,
            Quaternion.identity, 1 << LayerMask.NameToLayer("GrabbableCube"));

        print("通关条件Debug" + BKCubes[i].name + "范围内检测到的方块有");
        for (int j = 0; j < colliders.Length; j++)
        {
            print("通关条件Debug" + colliders[j].name);
        }


        if (colliders.Length == 0)
        {
            isFull = false;
            break;
        }
    }
    if (isFull)
    {
        Debug.LogError("通关!");
        UIMain.Instance.ShowPanel<WinPanel>();
    }

}

改变轴心

改变轴心实际上就是改变父物体相对子物体的位置;欲改变相对位置,需要先解除父子关系,将原父物体设置到轴心位置上,然后将子物体设回父物体。

注意:

  1. 使用Transform.SetParent()方法设置父物体,似乎有一种“闪现”的逻辑在里面,具体表现在如果你在触发器内部设置父物体,触发器会连续触发,且只触发进入事件,而不触发出去事件。
  2. 之前用过不改变父子关系,只改变子物体位置方法去改变轴心。具体做法为先保存原子物体的位置,然后将父物体设置到轴心位置处,接着再把子物体位置还原为原先保存的位置。但是不知道为啥完全没用。
/// <summary>
/// 设置新轴心并且保持子物体世界坐标不变
/// </summary>
public void SetNewPivot3(Transform obj, Vector3 pivotPos)
{
    //改变轴心实际上就是改变父物体相对子物体的位置
    //改变父物体前先移除父子关系
    Transform[] subTransforms = obj.GetComponentsInChildren<Transform>();
    foreach (var item in subTransforms)
    {
        item.SetParent(environment, true);
    }

    //将准轴心物体放置到新轴心的世界坐标上
    obj.position = pivotPos;

    //父物体再设回来,此时轴心改变
    foreach (var item in subTransforms)
    {
        item.SetParent(obj, true);
    }

    print("以重置轴心点");
}

坑:

  1. 由触发器造成的BUG很多;除了上面写的,在写判定是否放满时尤为突出。
    • 最简单的放满判定方法时判定,进入背景的方块数是否到达最大数目,统计进入背景方块数是由触发器统计的,因为吸附效果需要瞬间更改位置,可能导致触发器离开事件不触发,进入背景的方块就会统计非常不准。
  2. 范围检测;第二种判定方法是范围检测,检测每个背景方块是否都有待放子方块。但是检测的结果很迷,虽然检测逻辑是在物体吸附逻辑之后,但不知道是不是具体执行时机的问题,每次结果都不太一样。
    • 经过一个小试验,发现点问题,执行下面代码
    private void FixedUpdate()
    {
        print("前" + cube.position);

        cube.SetPositionAndRotation(cube.position + Vector3.forward * 3, cube.rotation);

        print("后" + cube.position);

        Collider[] colliders = Physics.OverlapBox(cube.position, cube.GetComponent<BoxCollider>().size / 2);
        foreach (var item in colliders)
        {
            print("立刻检测" + item.name);
        }

    }

结果如下

screenshot.png

感觉可能和帧更新的执行顺序有关,射线检测的位置没错,但是此时方块还没还没真正更新过去,导致没有被检测到,等下一个物理帧更新过后,再去检测,就正常了

总结:由于物理帧和普通帧的不同步,射线检测还是尽量在物理帧中去检测,因为物理帧(internal physics update)过后游戏物理世界才真正发生了变化(比如说碰撞体更新)。所以若有需求一帧瞬移且检测,最好间隔一个物理帧后再检测。

private void FixedUpdate()
{
    print("前" + cube.position);

    cube.SetPositionAndRotation(cube.position + Vector3.forward * 3, cube.rotation);

    print("后" + cube.position);

    Collider[] colliders = Physics.OverlapBox(cube.position, cube.GetComponent<BoxCollider>().size / 2);
    foreach (var item in colliders)
    {
        print("立刻检测" + item.name);
    }

    StartCoroutine(OverlapFixed());

}

IEnumerator OverlapFixed()
{
    yield return new WaitForFixedUpdate();//若改为yield return nullj,结果还是不准确的
    Collider[] colliders = Physics.OverlapBox(cube.position, cube.GetComponent<BoxCollider>().size / 2);
    foreach (var item in colliders)
    {
        print("等一物理帧检测" + item.name);
    }
}
%){_BQBM9~N31CP13OTW4ME.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容