Raycasting
Usa raycasting para trazar una línea en el espacio y consultar colisiones con entities en la escena.
El raycasting es una herramienta fundamental en el desarrollo de juegos. Con raycasting, puedes trazar una línea imaginaria en el espacio y consultar si alguna entidad es intersectada por esa línea. Esto es útil para calcular líneas de visión, trayectorias de proyectiles, algoritmos de búsqueda de rutas y muchas otras aplicaciones.
Cuando un jugador pulsa el botón del puntero, o el botón primario o secundario, se traza un rayo desde la posición del jugador en la dirección en la que está mirando, ver eventos de botón para más detalles sobre esto. Este documento cubre cómo trazar un rayo invisible desde cualquier posición y dirección arbitrarias, independiente de las acciones del jugador, que puedes usar en muchos otros escenarios.
Ten en cuenta que los raycasts solo impactan en objetos con colliders. Así que si quieres detectar impactos de rayo contra un modelo 3D, o bien:
El modelo debe contener mallas de collider.
El
GLTFContainerdebe estar configurado para usar la geometría visible con máscaras de colisión.Añadir un componente MeshCollider.
También es buena práctica asignar capas de colisión a los modelos 3D, de modo que los rayos solo tengan que calcular colisiones contra las entidades relevantes, en lugar de contra todo lo que tenga un collider.
Crear un rayo
Todos los rayos tienen un punto de origen y una dirección. El punto de origen se basa en la posición de una entidad, tomando los valores del componente Transform de la entidad. La dirección de un rayo puede definirse de 4 maneras diferentes:
local: Una dirección relativa a la dirección frontal de la entidad, también afectada por la transformación de cualquier entidad padre. Esto es útil para detectar obstáculos frente a vehículos respetando su rumbo.
global: Ignora la rotación de la entidad y apunta en una dirección como si la rotación de la entidad fuera 0. Esto es útil, por ejemplo, para apuntar siempre hacia abajo.
objetivo global: Traza una línea entre la posición de la entidad y una posición global objetivo en la escena. Ignora la rotación de la entidad. Útil, por ejemplo, para crear juegos de defensa de torres; el cañón de cada torre puede apuntar a una coordenada precisa en el espacio.
entidad objetivo: Traza una línea entre la posición de la entidad y la posición de una segunda entidad objetivo. Ignora la rotación de ambas entidades.
El siguiente código crea un raycast con una dirección local:
const myEntity = engine.addEntity()
Transform.create(myEntity, {
position: Vector3.create(4, 1, 4),
})
raycastSystem.registerLocalDirectionRaycast(
{
entity: myEntity,
opts: { direction: Vector3.Forward() },
},
function (raycastResult) {
// función callback
}
)Usa las siguientes funciones para crear raycasts proporcionando la dirección de diferentes maneras:
raycastSystem.registerLocalDirectionRaycast(): crea un raycast con una local dirección. Ladireccióncampo espera unVector3que describe un vector relativo a la entidad y su rotación (por ejemploVector3.Forward()terminaría usando el vector forward del Transform de la entidad)raycastSystem.registerGlobalDirectionRaycast(): crea un raycast con una global dirección. Ladireccióncampo espera unVector3que describe la dirección global.raycastSystem.registerGlobalTargetRaycast(): crea un raycast con una dirección definida por una objetivo global posición. Elobjetivocampo espera unVector3que describe una posición global en la escena.raycastSystem.registerTargetEntityRaycast(): crea un raycast con una dirección definida hacia una entidad objetivo posición. EltargetEntitycampo espera una referencia a una entidad; la posición de esta entidad será usada como objetivo del rayo.
Los siguientes campos opcionales están disponibles al crear un rayo con cualquiera de los métodos anteriores:
maxDistance: número para establecer la longitud con la que se trazará este rayo. Si no se establece, el valor por defecto es 16 metros.queryType: RaycastQueryType valor enum, para definir si el rayo devolverá todas las entidades impactadas o solo la primera. Las siguientes opciones están disponibles:RaycastQueryType.RQT_HIT_FIRST: (por defecto) solo devuelve la primera entidad impactada, comenzando desde el punto de origen.RaycastQueryType.RQT_QUERY_ALL: devuelve todas las entidades impactadas, desde el origen hasta la distancia máxima del rayo.
originOffset: En lugar de comenzar el raycast desde la posición de origen de la entidad, añade un offset para iniciar la consulta desde una posición relativa. Por ejemplo, puedes usar un pequeño offset para evitar que el rayo colisione con el propio collider de la entidad. Si no se establece, el valor por defecto esVector3.Zero().collisionMask: Detectar solo colisiones con ciertas capas de colisión. Usa esto junto con una capa de colisión personalizada, o para detectar solo la capa de física o la capa de eventos de puntero. Ver capas de colisión. Si no se establece, la capa por defecto usada esColliderLayer.CL_PHYSICS.continuous: Si es true, seguirá ejecutando una consulta de raycast en cada frame. Si es false, el rayo solo se usará en el frame actual. Si no se establece, el valor por defecto es false.Al establecer la dirección con una dirección local o global, el
direccióncampo por defecto esVector3.Forward().Al establecer la dirección con un objetivo global, el
globalTargetcampo por defecto esVector3.Zero().Al establecer la dirección con una entidad objetivo, el
targetEntitycampo por defecto es la entidad raíz de la escena, ubicada enVector3.Zero().
📔 Nota: El continuous la propiedad debe usarse con precaución, ya que ejecutar una consulta de raycast en cada frame puede ser muy costoso para el rendimiento. Cuando sea posible, usa un sistema (o la interval función en la biblioteca Utils) para ejecutar consultas de raycast en un intervalo regular más espaciado, ver raycasting recurrente.
A continuación hay ejemplos usando cada uno de los cuatro métodos para determinar la dirección del rayo:
📔 Nota: raycastSystem, RaycastQueryType y ColliderLayer deben importarse vía
import { raycastSystem, RaycastQueryType, ColliderLayer } from "@dcl/sdk/ecs"
Ver Imports para ver cómo manejarlos fácilmente.
Resultado del raycast
La función callback que maneja el raycast recibe un objeto que contiene datos sobre el propio rayo y cualquier entidad que haya sido impactada.
globalOrigin: La posición donde se originó el rayo, relativa a la escena.dirección: La dirección global hacia la que apuntaba el rayo, como unVector3.hits: Un array con un objeto por cada entidad que fue impactada. Si no hubo entidades impactadas, este array está vacío. Si el raycast usóRaycastQueryType.RQT_HIT_FIRST, este array contendrá solo un objeto.
Cada objeto en el hits array incluye:
entityId: Número ID de la entidad que fue impactada por el rayo.meshName: String con el nombre interno de la malla específica en el modelo 3D que fue impactada. Esto es útil cuando un modelo 3D está compuesto por múltiples mallas.position: Vector3 para la posición donde el rayo se intersectó con la entidad impactada (relativa a la escena)length: Longitud del rayo desde su origen hasta la posición donde ocurrió el impacto contra la entidad.normalHit: Quaternion para el ángulo de la normal del impacto en el espacio mundial.globalOrigin: Vector3 para la posición donde se origina el rayo (relativa a la escena)dirección: La dirección global hacia la que apuntaba el rayo, como unVector3.
El siguiente ejemplo itera sobre las entidades que fueron impactadas:
📔 Nota: Puedes obtener un resultado de raycast al impactar una entidad en una escena diferente.
Manejar entidades impactadas
Cuando obtienes un resultado de raycast que impactó una entidad, puedes usar el entityId para interactuar con la entidad y sus componentes. Una entidad es nada más que un número, así que el entityId valor en sí puede interpretarse como un Entity tipo.
Collision layers
Es buena práctica comprobar colisiones solo con entidades relevantes, para hacer la escena más eficiente. El collisionMask campo permite listar solo capas de colisión específicas, que pueden incluir la capa de física (que bloquea el movimiento del jugador), la capa de puntero (que se usa para eventos de puntero) y 8 capas personalizadas que puedes asignar libremente según tus necesidades. Ver capas de colisión. Por defecto, se detectan todas las capas.
Por defecto, el collisionMask campo está configurado para responder a ambas capas ColliderLayer.CL_POINTER y ColliderLayer.CL_PHYSICSPuedes cambiar este valor para listar solo una de ellas, o para incluir capas personalizadas. Usa el | separador para listar múltiples opciones.
Raycasting recurrente
Al usar las funciones del raycastSystem, el comportamiento por defecto es crear un único rayo, que consultará colisiones una vez. Como alternativa, puedes establecer el continuous campo a true para ejecutar una consulta y la función callback en cada tick del bucle del juego.
El siguiente ejemplo seguirá ejecutando la consulta de raycast a partir de este punto
📔 Nota: El continuous la propiedad debe usarse con precaución, ya que ejecutar una consulta de raycast en cada frame puede ser muy costoso para el rendimiento.
Cuando ya no sea necesario, elimina cualquier raycast recurrente. Para hacerlo, debes usar raycastSystem.removeRaycasterEntity.
Cuando sea posible, usa un sistema (o la interval función en la biblioteca Utils) para ejecutar consultas de raycast en un intervalo regular más espaciado, como solo una vez por segundo, o cada quinto de segundo.
El ejemplo anterior ejecuta un raycast recurrente cada 0.1 segundos. Usa un componente temporizador y la propiedad dt del sistema para temporizar esto uniformemente. También incluye un cubo que oscila arriba y abajo, controlado por otro sistema, para moverse dentro y fuera de la trayectoria del rayo.
💡 Tip: Usa la interval función en la biblioteca SDK Utils para una forma más simple de ejecutar una función en un intervalo fijo.
Raycasts mediante un sistema
Otra forma de realizar raycasts recurrentes es ejecutarlos dentro de la función recurrente de un sistema. Esto te permite tener mucho más control sobre cuándo y cómo funcionan. En lugar de registrar una función callback, puedes realizar una consulta de raycast con raycastSystem.registerRaycast y luego comprobar los datos devueltos por esta operación, todo dentro de la función del sistema.
Ten en cuenta que dado que el raycast se ejecuta en un sistema, el resultado solo estará disponible en el siguiente tick, necesitando dos ejecuciones del sistema. Una para registrar el raycast para el siguiente frame, y el siguiente frame para procesar su resultado.
Colisionar con el jugador
No puedes impactar directamente el avatar del jugador ni los de otros jugadores con un rayo, pero como solución alternativa puedes posicionar una entidad invisible que ocupe el mismo espacio que un jugador usando el componente AvatarAttach, y comprobar colisiones con ese cubo.
Raycasts desde el jugador
Para trazar un rayo desde la posición del jugador en la dirección a la que apunta la cámara, puedes trazar un rayo usando la cámara o el avatar Entidades reservadas.
💡 Tip: En la mayoría de los casos, quizá te convenga más usar Eventos de puntero en lugar de raycasts.
El siguiente ejemplo traza un rayo desde la posición de la cámara del jugador hacia adelante, usando la entidad engine.CameraEntity entity.
📔 Nota: Ten en cuenta que en 3ª persona el cursor podría en el futuro no comportarse igual que en 1ª persona. Se recomienda usar esto solo si el jugador está en 1ª persona.
Raycast desde la posición del cursor
También puedes trazar un rayo desde la posición del cursor del jugador hacia el mundo 3D. Esto puede usarse para arrastrar objetos, shooters, etc.
En este ejemplo, detectamos cuando el jugador pulsa la tecla E, y luego trazamos un rayo desde la posición del cursor hacia el mundo 3D. Luego comprobamos si el rayo impactó alguna entidad y, si es así, hacemos algo con ella.
💡 Tip: En este ejemplo usamos el botón primario (E) para activar el raycast. No usamos el botón del puntero (clic izquierdo) porque hacer clic y arrastrar también cambia el ángulo de la cámara por defecto. Si quieres evitar rotar la cámara mientras arrastras, puedes usar una VirtualCamera para fijar el ángulo de la cámara.
Sintaxis avanzada
Crear un componente Raycast
Un componente Raycast describe el rayo invisible que se usa para consultar entidades intersectadas. El rayo se traza comenzando en la posición de la entidad, tal como está definida por el componente Transform y afectada por la de cualquier entidad padre. La dirección puede definirse de varias maneras,
Los rayos se definen usando los siguientes datos:
dirección: Un objeto que contiene un$casecampo para seleccionar el tipo de dirección, y un campo adicional que dependerá de este tipo, que determina dicha dirección. Los siguientes son los valores aceptados para$case:LOCAL_DIRECTION: Una dirección relativa a la dirección frontal de la entidad, también afectada por la transformación de cualquier entidad padre. Esto es útil para detectar obstáculos frente a vehículos respetando su rumbo. La rotación se define por ellocalDirectioncampo, como unVector3que describe una rotación.GLOBAL_DIRECTION: Ignora la rotación de la entidad y apunta en una dirección como si la rotación de la entidad fuera 0. Esto es útil, por ejemplo, para apuntar siempre hacia abajo. La rotación se define por elglobalDirectioncampo, como unVector3que describe una rotación.GLOBAL_TARGET: Traza una línea entre la posición de la entidad y una posición global objetivo en la escena. Ignora la rotación de la entidad. Útil para crear juegos de defensa de torres; el cañón de cada torre puede apuntar a una coordenada precisa en el espacio. El objetivo se define por elglobalTargetcampo, como unVector3que describe la posición global.TARGET_ENTITY: Traza una línea entre la posición de la entidad y la posición de una segunda entidad objetivo. Ignora la rotación de ambas entidades. El objetivo se define por eltargetEntitycampo, que contiene una referencia a la entidad.
maxDistance: número para establecer la longitud con la que se trazará este rayo.queryType: RaycastQueryType valor enum, para definir si el rayo devolverá todas las entidades impactadas o solo la primera. Las siguientes opciones están disponibles:RaycastQueryType.RQT_QUERY_ALL: solo devuelve la primera entidad impactada, comenzando desde el punto de origen.RaycastQueryType.RQT_HIT_FIRST: devuelve todas las entidades impactadas, desde el origen hasta la distancia máxima del rayo.
collisionMask: Detectar solo colisiones con ciertas capas de colisión. Usa esto junto con una capa de colisión personalizada, o para detectar solo la capa de física o la capa de eventos de puntero. Ver capas de colisión. Por defecto, se detectan todas las capas.originOffset: En lugar de comenzar el raycast desde la posición de origen de la entidad, añade un offset para iniciar la consulta desde una posición relativa. Por ejemplo, puedes usar un pequeño offset para evitar que el rayo colisione con el propio modelo 3D de la entidad.continuous: Si es true, seguirá ejecutando una consulta de raycast en cada frame. Si es false, el rayo solo se usará en el frame actual. Por defecto este valor es false.
📔 Nota: El continuous la propiedad debe usarse con precaución, ya que ejecutar una consulta de raycast en cada frame puede ser muy costoso para el rendimiento. Cuando sea posible, usa un sistema (o la interval función en la biblioteca Utils) para ejecutar consultas de raycast en un intervalo regular más espaciado, ver raycasting recurrente.
El siguiente ejemplo usa una rotación global para determinar la dirección y solo devuelve la primera entidad que es impactada en el frame en que se envía el rayo.
El ejemplo de abajo lanza un rayo en la dirección frontal de la entidad, devolviendo solo el primer elemento impactado. Lo hace de forma continua. También incluye un pequeño offset de 0.5 para evitar que el rayo golpee el propio collider de la entidad.
Este ejemplo traza un rayo entre dos entidades. Devuelve todas las entidades que son impactadas en el trayecto.
Componente de resultados de Raycast
📔 Nota: La manera más sencilla de manejar los resultados de raycast es usar raycastEventSystem, y registrar una función callback como parte de la misma declaración que crea el rayo. El componenteRaycastResult se usa internamente por esa interfaz, pero también se expone para permitir lógica personalizada más avanzada.
Después de crear un componente Raycast, la entidad a la que se añade este componente tendrá un componente RaycastResult RaycastResult. Este componente incluye información sobre cualquier impacto del rayo. Configura un sistema para comprobar estos datos.
El RaycastResult El componente contiene los siguientes datos:
globalOrigin: La posición donde se originó el rayo, relativa a la escena.dirección: La dirección global hacia la que apuntaba el rayo, como unVector3.hits: Un array con un objeto por cada entidad que fue impactada. Si no hubo entidades impactadas, este array está vacío. Si el raycast usóRaycastQueryType.RQT_HIT_FIRST, este array contendrá solo un objeto.
Cada objeto en el hits array incluye:
entityId: Número ID de la entidad que fue impactada por el rayo.meshName: String con el nombre interno de la malla específica en el modelo 3D que fue impactada. Esto es útil cuando un modelo 3D está compuesto por múltiples mallas.position: Vector3 para la posición donde el rayo se intersectó con la entidad impactada (relativa a la escena)length: Longitud del rayo desde su origen hasta la posición donde ocurrió el impacto contra la entidad.normalHit: Quaternion para el ángulo de la normal del impacto en el espacio mundial.globalOrigin: Vector3 para la posición donde se origina el rayo (relativa a la escena)dirección: La dirección global hacia la que apuntaba el rayo, como unVector3.
El ejemplo siguiente muestra cómo puedes acceder a los resultados de una entidad individual usando un sistema:
El siguiente ejemplo muestra cómo puedes acceder a RaycastResult componentes de todas las entidades en la escena, usando una consulta de componentes.
📔 Nota: Los resultados de un raycast no llegan en el mismo tick del bucle de juego en el que creaste el raycast. Los resultados pueden tardar uno o múltiples ticks en llegar.
En una escena donde uses múltiples tipos de rayos para diferentes propósitos (como para búsqueda de rutas, comprobación de línea de visión, trazado de proyectiles, etc.), puede que quieras usar diferentes capas de colisiónpara evitar calcular colisiones irrelevantes.
Última actualización