You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ursina Engine线条与立方体碰撞检测不全问题求助

问题分析与解决方案

核心问题

Ursina引擎的raycast()方法默认只返回第一个碰撞到的实体,而你的需求是检测射线穿过的所有闪烁体,因此必须启用return_all=True参数,否则只会记录首个碰撞,甚至会出现明显穿过物体却未检测到碰撞的情况。

此外还有两个次要逻辑问题:

  1. create_scintillator中判断闪烁体是否存在的代码参数顺序错误:isinstance(Scintillator, x)应改为isinstance(x, Scintillator),否则永远无法检测到已存在的闪烁体。
  2. calculate_collisions中多个if语句未使用elif,会导致重复判断(比如x=4时会执行所有x<=4的if块,虽颜色会被最后一个覆盖,但逻辑不严谨)。

修正后的代码

from ursina import *
import numpy as np

# USEFUL VARIABLES
SCINTILLATOR_SIZE_X = 0.5
SCINTILLATOR_SIZE_Y = 0.5
SCINTILLATOR_SIZE_Z = 0.0254  # 1 inch

objects = []
VEC0_3D = Vec3(0, 0, 0)


# USEFUL FUNCTIONS
def draw_wireframe_cube(pos, size, color):
    cube = Entity(model='wireframe_cube', world_position=pos, scale=size, color=color, mode='line')
    return cube


def size_of_vec(vec):
    """
    Returns the size of a given vector
    :param vec: 3D vector
    :return: Size of 3D vector "vec"
    """
    return np.sqrt((vec.x ** 2 + vec.y ** 2 + vec.z ** 2))


def check_muons_collisions(muons):
    hit_entities = []
    for muon in muons:
        hit_entities.append(muon.calculate_collisions())
    return hit_entities


# SCINTILLATOR (IS A CUBOID) CLASS
class Scintillator(Entity):
    def __init__(self, level, pos, size, rotation, mesh_wire):
        self.pos = pos
        self.size = size
        self.rotation = rotation
        self.mesh_wire = mesh_wire
        Entity.__init__(self, scale=size, position=pos, model='cube', collider='box', color=color.pink)


# CREATE SCINTILLATOR METHOD
def create_scintillator(num_of_scints):
    """
    This function will create the main scintillator 
    :return: Returns the scintillator object
    """
    # 修正isinstance参数顺序
    scintillator_ref = [x for x in objects if isinstance(x, Scintillator)]
    if len(scintillator_ref) > 0:
        print("A scintillator is already defined!")
        return
    scint_size = Vec3(SCINTILLATOR_SIZE_X, SCINTILLATOR_SIZE_Y, SCINTILLATOR_SIZE_Z)  # set in settings
    scint_rot = VEC0_3D  # in degrees
    for i in range(num_of_scints):
        scint_pos = Vec3(0, 0, -num_of_scints / 2 + 0.5) * 0.1 + Vec3(0, 0, 1) * 0.1 * i  # in meters
        wireframe = draw_wireframe_cube(scint_pos, scint_size * 1.01, color=color.black)
        scint = Scintillator(i, scint_pos, scint_size, scint_rot, wireframe)
        wireframe.collider = None
        wireframe.parent = scint
        # 修正:将每个闪烁体都加入objects列表
        objects.append(scint)
    return scint


# MUON (IS A LINE) CLASS
class Muon(Entity):
    """
    Object class for muons
    """

    def __init__(self, index, start, end, energy):
        """
        Initialization function for a muon
        :param start: Starting point
        :param end: End point
        :param energy: Energy, used for calculating scintillation.
        :param mesh: Mesh reference.
        """
        self.start = start
        self.end = end
        self.energy = energy
        self.index = index
        self.hit_info = None
        Entity.__init__(self, model=Mesh(vertices=[start, end], mode='line', thickness=0.1), color=color.green)

    def calculate_collisions(self):
        # 关键修正:添加return_all=True,获取所有碰撞实体
        self.hit_info = raycast(
            self.start, 
            self.end - self.start,
            distance=size_of_vec(self.end - self.start), 
            debug=True,
            return_all=True
        )
        # 去重收集所有碰撞实体
        entities = []
        for hit in self.hit_info:
            if hit.entity not in entities:
                entities.append(hit.entity)
        
        x = len(entities)
        # 修正为elif,避免重复判断
        if x == 0:
            self.color = color.cyan
        elif x == 1:
            self.color = color.brown
        elif x == 2:
            self.color = color.red
        elif x == 3:
            self.color = color.orange
        elif x == 4:
            self.color = color.gold
        elif x == 5:
            self.color = color.black

        for entity in entities:
            if isinstance(entity, Scintillator):
                # 匹配对应实体的碰撞点
                hit_point = next(hit.point for hit in self.hit_info if hit.entity == entity)
                self.print_collision(entity, hit_point)
        return entities

    def print_collision(self, entity, position):
        print("*************************")
        print(f"Muon {self.index} hit {entity} at {position}")
        print("*************************")


def create_n_random_muons(n):
    """
    This function will create and print n muons of varying energies and angels
    :param n: Number of muons
    :return: Array of all muons
    """

    muons = []  # This hold the new n muons
    # This holds calculated start points. I calculated some random ones for you
    start_points = [Vec3(-142.749, -219.022, 999.763),
                    Vec3(-143.519, 108.823, 999.763),
                    Vec3(-393.619, -104.295, 999.763),
                    Vec3(1212.26, 570.423, 999.763),
                    Vec3(-51.1669, 35.664, 999.763),
                    Vec3(323.465, -27.7195, 999.763),
                    Vec3(245.713, 183.488, 999.763),
                    Vec3(363.892, 647.235, 999.763),
                    Vec3(201.392, 574.155, 999.763),
                    Vec3(-253.416, -743.39, 999.763),
                    Vec3(171.414, 198.996, 999.763),
                    Vec3(-11.5856, 12.0816, 999.763),
                    Vec3(-46.2548, 257.973, 999.763),
                    Vec3(761.947, -622.162, 999.763),
                    Vec3(-496.811, -295.885, 999.763)]

    # This holds calculated end points. I calculated some for you
    end_points = [Vec3(-0.00927782, -0.0904079, -0.2373),
                  Vec3(-0.149831, -0.241173, -0.2373),
                  Vec3(-0.0478061, 0.051418, -0.2373),
                  Vec3(0.17725, -0.0356445, -0.2373),
                  Vec3(0.217475, -0.225569, -0.2373),
                  Vec3(0.188968, -0.064771, -0.2373),
                  Vec3(-0.19695, -0.0399446, -0.2373),
                  Vec3(-0.154698, -0.141071, -0.2373),
                  Vec3(0.0940816, -0.133347, -0.2373),
                  Vec3(0.144424, 0.223723, -0.2373),
                  Vec3(-0.161474, 0.0230457, -0.2373),
                  Vec3(-0.0304296, -0.152948, -0.2373),
                  Vec3(-0.00892861, 0.0107271, -0.2373),
                  Vec3(0.138, -0.196602, -0.2373),
                  Vec3(-0.216395, -0.0713366, -0.2373)
                  ]  # This holds randomized end points

    # For loop to create n muons
    for i in range(0, n):  # Create n end vectors for Muons
        muon_i = Muon(i, start_points[i], end_points[i], 1)
        objects.append(muon_i)
        muons.append(muon_i)
    return muons


def main(argv):
    app = Ursina(title='example')

    scints = create_scintillator(4)  # NUMBER_OF_SCINTS = 4

    muons = create_n_random_muons(10)

    check_muons_collisions(muons)

    EditorCamera()
    app.run()


if __name__ == "__main__":
    import sys
    main(sys.argv)

关键修正点说明

  1. 射线检测返回所有碰撞:在raycast中添加return_all=True,此时返回值为包含所有碰撞信息的列表,可遍历获取所有被穿过的实体。
  2. 修复闪烁体列表添加逻辑:原代码仅将最后一个闪烁体加入objects列表,现在每个闪烁体都被添加,确保所有碰撞体都能被检测到。
  3. 修正类型判断逻辑:调整isinstance参数顺序,正确判断对象是否为Scintillator类型。
  4. 优化碰撞次数判断:将多个独立if改为elif,避免重复执行颜色赋值逻辑。

修改后,所有穿过多个闪烁体的射线都能正确检测到碰撞次数,线条颜色也会对应显示。

内容的提问来源于stack exchange,提问作者the-friendly-dude

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 21:17:34