【问题标题】:Detect very first collision only (Collision detection with raycasting)仅检测第一次碰撞(使用光线投射进行碰撞检测)
【发布时间】:2018-05-16 15:48:23
【问题描述】:

我正在构建一个无尽的跑步风格游戏,其中 (0,0) 处的角色只能在 X 轴上左右移动(使用箭头键或鼠标)。有两种类型的物体从z = -10000 向角色移动,一旦它们到达z = 10000(即角色后面和屏幕外),它们就会重置为z = -10000 我正在使用以下光线投射代码来检测两者之间的碰撞角色和两种类型的对象。

本质上,光线从渲染循环中的每一帧的每个角色顶点发射。如果光线与对象顶点之一相交并且距离接近角色(不确定它使用的确切距离),则检测到碰撞。

这是我的代码:

这在渲染循环中运行:

detectCollisionRaycaster(character, objectsArray);

这是碰撞检测功能:

let cooldown = false;
let objectMesh;
let collisionResults;

function detectCollisionRaycaster(originObject, collidedObjects) {

let originPoint = originObject.position.clone();

    for (var vertexIndex = 0; vertexIndex < objectMesh.geometry.vertices.length; vertexIndex++) {
        var localVertex = objectMesh.geometry.vertices[vertexIndex].clone();
        var globalVertex = localVertex.applyMatrix4( objectMesh.matrix );
        var directionVector = globalVertex.sub( objectMesh.position );

        var raycaster = new THREE.Raycaster( originPoint, directionVector.clone().normalize() );
        collisionResults = raycaster.intersectObjects( collidedObjects, true );
        if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() && !cooldown) {

            cooldown = true;

            console.info("Object Collided");

            setTimeout(function(){
                collisionResults.length = 0;
                cooldown = false;
            }, 500);

        }
    }
}

问题

碰撞代码多次触发。我很确定这是因为对象正在向角色移动,因此对象的顶点与光线碰撞超过 1 个单帧,因此只要对象是,碰撞代码就会运行敌人的“内部”。

我想要什么:

我希望碰撞检测代码显式触发一次,暂停碰撞检测一段时间直到对象不再与角色碰撞,然后继续检测碰撞

我的尝试

我尝试在碰撞检测代码中设置一个超时函数,我认为这会阻止碰撞检测在渲染循环中运行,直到该时间结束,但是它似乎只运行一次代码,暂停超时,然后在对象位于角色“内部”的所有时间内继续运行检测到碰撞的代码。

【问题讨论】:

    标签: javascript three.js


    【解决方案1】:

    由于您在发现冲突时并未终止循环,因此即使满足 if 条件,它也会继续执行其余的迭代。

    这样的方法可以帮助您更好地组织逻辑,将您赋予之前的碰撞检测方法的一些责任分离开来。通过两种方法,我们将您的逻辑分为一个简单地告诉我们是否发生碰撞 (isCollision) 的方法,另一种方法是在检测到碰撞时使用isCollision 适当地设置cooldown

    let cooldown = false;
    let objectMesh;
    let collisionResults;
    let timeout;
    
    function detectCollisionRaycaster(originObject, collidedObjects) {
        if (this.isCollision(originObject, collidedObject)) {
            cooldown = true;
            if (timeout) {
               clearTimeout(timeout);
            }
            timeout = setTimeout(function () {
                cooldown = false;
            }, 500);
        }
    }
    
    function isCollision(originObject, collidedObjects) {
        let originPoint = originObject.position.clone();
    
        for (var vertexIndex = 0; vertexIndex < objectMesh.geometry.vertices.length; vertexIndex++) {
            var localVertex = objectMesh.geometry.vertices[vertexIndex].clone();
            var globalVertex = localVertex.applyMatrix4( objectMesh.matrix );
            var directionVector = globalVertex.sub( objectMesh.position );
    
            var raycaster = new THREE.Raycaster( originPoint, directionVector.clone().normalize() );
            collisionResults = raycaster.intersectObjects( collidedObjects, true );
            if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() && !cooldown) {
                console.info("Object Collided");
                return true;
            }
        }
    
        return false;
    }
    

    编辑:这将保持冷却直到没有物体与玩家发生碰撞。如果您想设置它以便暂停直到触发碰撞的特定对象是被检查的对象,那么您需要保留对该对象的引用并对其进行检查,而不是针对全套玩家运行顶点+对象。一种方法是从isCollision 方法而不是布尔值返回对碰撞对象的引用,然后使用另一种方法从该对象显式投射到玩家顶点以确定碰撞何时结束。

    【讨论】:

    • 非常感谢,这解决了我的问题,并且完全按照我在问题中提出的要求! :) 如果我尝试删除碰撞检测代码中的碰撞对象(return true 之前的行),它会中断并返回多次运行碰撞检测代码......知道为什么吗?我正在使用此代码删除对象:scene.remove(scene.getObjectByName(collisionResults[0].object.parent.name));
    • 我觉得是因为我没有正确移除对象,只是在视觉上移除它,所以碰撞仍在发生......
    • 来自three.js 文档,Object3d#getObjectByName 指出 " 对于大多数对象,名称默认为空字符串。您必须手动设置它才能使用此方法。 " 同时,Object3d#remove 方法会很高兴地获取Object3d 引用,那么为什么不直接使用它呢? scene.remove(collisionResults[0].object) 应该移除导致碰撞的对象。如果您真的想删除父 Object3d,那么可以通过使用 scene.remove(collisionResults[0].object.parent) 来完成
    • 仍然用那个方法做同样的事情(我最初尝试过),这太奇怪了,像删除对象这样简单的事情会导致函数恢复到它的旧行为并检测到多次碰撞:/
    • 好吧,在重新阅读您的预期行为之后;如果您希望碰撞检测只触发一次,然后在冷却可用之前不再运行,那么我们应该明确地这样做。您可以将detectCollisionRaycaster 中的条件更改为:if (!cooldown &amp;&amp; this.isCollision(originObject, collidedObject)) 这会改变行为,因为现在clearTimeout 调用是多余的。此处的行为是碰撞将触发 500 毫秒的冷却时间,而不是持续延迟直到不再检测到碰撞。
    猜你喜欢
    • 2012-11-07
    • 2023-04-04
    • 1970-01-01
    • 2014-10-31
    • 1970-01-01
    • 1970-01-01
    • 2015-11-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多