Zurück zur Startseite

Distanzen und Kollisionen in Ursina 3D

Leitfaden zu distance(), distance_2d(), distance_xz() und intersects() Funktionen in Ursina. Code-Beispiele für Distanzberechnung, Kollisionserkennung, KI und Item-Pickup in 3D-Spielen.

Ursina: distance und intersects für Spiele
Advertisement 728x90

Abstand und Kollisionsdetektion in Ursina für 3D-Spiele

In Ursina ermöglichen die Funktionen distance(), distance_2d() und distance_xz() die präzise Berechnung von Abständen zwischen Objekten im 3D-Raum. Die Methode intersects() erkennt Kollisionen mithilfe von Collidern. Diese Werkzeuge sind essenziell für Mechaniken wie Erkennung, Gegenstandsaufnahme und künstliche Intelligenz.

Abstandsberechnungsfunktionen

Die Funktion distance() berechnet den euklidischen Abstand im 3D-Raum über alle X-, Y- und Z-Achsen zwischen den Zentren zweier Entity-Objekte:

from ursina import *
from ursina.prefabs.first_person_controller import FirstPersonController
app = Ursina()
ground = Entity(model='plane', texture='grass', scale=20, collider='box')
player = FirstPersonController(position=(0, 2, 0))
sphere = Entity(position=(3, 4, 0), model='sphere', color=color.blue)
Sky()

def update():
    dist = distance(player, sphere)
    print(f"Abstand vom Helden zur Kugel: {dist:.2f}")

app.run()

Formel: √((x₂-x₁)² + (y₂-y₁)² + (z₂-z₁)²). Ideal für umfassende räumliche Analyse.

Google AdInline article slot

distance_2d() ignoriert die Z-Achse und arbeitet in der X-Y-Ebene:

from ursina import * 
from ursina.prefabs.first_person_controller import FirstPersonController 
app = Ursina() 
ground = Entity( 
    model='plane', 
    texture='grass', 
    scale=20, 
    collider='box' 
) 
player = FirstPersonController(position=(0, 2, 0)) 
sphere = Entity(position=(3, 4, 0), model='sphere', color=color.blue) 
Sky() 

def update(): 
    dist = distance_2d(player, sphere) 
    print(f"2D-Abstand vom Helden zur Kugel: {dist:.2f}") 

app.run()

Nützlich für UI-Elemente oder Objekte auf gleicher Höhe.

distance_xz() konzentriert sich auf die horizontale X-Z-Ebene und ignoriert die Y-Achse:

Google AdInline article slot
from ursina import * 
from ursina.prefabs.first_person_controller import FirstPersonController 
app = Ursina() 
ground = Entity( 
    model='plane', 
    texture='grass', 
    scale=20, 
    collider='box' 
) 
player = FirstPersonController(position=(0, 2, 0)) 
sphere = Entity(position=(3, 4, 0), model='sphere', color=color.blue) 
Sky() 

def update(): 
    dist = distance_xz(player, sphere) 
    print(f"Horizontaler Abstand vom Helden zur Kugel: {dist:.2f}") 

app.run()

Perfekt für AI-Systeme auf dem Boden, bei denen die vertikale Position keine Rolle spielt.

Das intersects()-Kollisionsystem

Die Methode intersects() prüft Kollisionen zwischen Collidern (Box, Sphere, Mesh) über Panda3D’s CollisionTraverser. Beide Objekte müssen einen collider und collision=True besitzen.

Gibt ein HitInfo-Objekt mit folgenden Feldern zurück:

Google AdInline article slot
  • hit: True/False
  • entity: das getroffene Objekt
  • point: Koordinaten des Aufpralls
  • distance: Abstand zum Aufprallpunkt

Beispiel 1: Wanderkennung

from ursina import *
app = Ursina()
player = Entity(model='cube', color=color.orange, collider='box')
wall = Entity(model='cube', position=(5,0,0), scale=(1,5,5), collider='box', color=color.red)

def update():
    player.x += 0.1 * time.dt
    hit_info = player.intersects()
    if hit_info.hit:
        print("Kollision!")
        print(f"Mit: {hit_info.entity}")
        print(f"Aufprallpunkt: {hit_info.point}")
        print(f"Abstand zum Aufprall: {hit_info.distance:.2f}")

app.run()

Beispiel 2: Gegenstand aufheben

from ursina import *
from ursina.prefabs.first_person_controller import FirstPersonController
app = Ursina()
ground = Entity(model='plane', texture='grass', scale=10, collider='box')
player = FirstPersonController(model='cube', origin_y=-.5)
pickup = Entity(model='sphere', position=(1,.5,3), collider='box')

def update():
    hit_info = player.intersects()
    if hit_info.hit and hit_info.entity == pickup:
        print('Gegenstand aufgehoben!')
        destroy(pickup)

app.run()

Genauer als distance() bei komplexen Formen.

Beispiel 3: Projektilesystem

import random
from ursina import *
app = Ursina()

class Bullet(Entity):
    def __init__(self, position, direction):
        super().__init__(
            model='sphere',
            scale=.1,
            speed=15,
            collider='sphere',
            position=position,
            color=color.yellow,
            name='bullet'
        )
        self.direction = direction.normalized()

    def update(self):
        self.position += self.direction * time.dt * self.speed
        hit_info = self.intersects(ignore=(self,))
        if hit_info.hit:
            print(f"Geschoss traf {hit_info.entity.name}!")
            destroy(self)

class Enemy(Entity):
    def __init__(self):
        super().__init__(
            model='cube',
            color=color.red,
            scale=1,
            collider='box',
            name='enemy'
        )
        self.x = random.choice([-4, 4])
        self.z = random.choice([-4, 4])

for i in range(5):
    Enemy()
player = Entity(model='cube', color=color.blue, position=(0, 0, 0))
shoot_timer = 0

def update():
    global shoot_timer
    shoot_timer += time.dt
    if shoot_timer >= 1:
        shoot_timer = 0
        random_direction = Vec3(
            random.uniform(-1, 1),
            random.uniform(-1, 1),
            random.uniform(-1, 1)
        ).normalized()
        bullet = Bullet(
            position=player.position,
            direction=random_direction
        )

app.run()

Empfohlen wird raycast() für präzise Treffer anstelle physischer Geschosse.

Praktische Anwendungsfälle

Aufheben per Abstand:

from ursina import *
from ursina.prefabs.first_person_controller import FirstPersonController
app = Ursina()
ground = Entity(model='plane', texture='grass', scale=10, collider='box')
player = FirstPersonController(model='cube', origin_y=-.5, color=color.orange, has_pickup=False)
pickup = Entity(model='sphere', position=(1,.5,3))

def update():
    if not player.has_pickup and distance(player, pickup) < pickup.scale_x / 2:
        print('Gegenstand aufgehoben!')
        player.has_pickup = True
        pickup.animate_scale(0, duration=.1)
        destroy(pickup, delay=.1)

app.run()

Feind-AI mit Erkennungszon:

from ursina import *
app = Ursina()
player = Entity(model='cube', position=(0, 0, 0), color=color.blue)
ground = Entity(model='plane', scale=20, color=color.dark_gray, y=-1)

class Enemy(Entity):
    def __init__(self, **kwargs):
        super().__init__(model='cube', scale_y=2, origin_y=-.5, color=color.light_gray, **kwargs)
        self.original_color = self.color
        self.detection_radius = 10

    def update(self):
        dist = distance_xz(player.position, self.position)
        if dist > self.detection_radius:
            self.color = self.original_color
            return
        self.color = color.red
        self.look_at_2d(player.position, 'y')
        if dist > 2:
            self.position += self.forward * time.dt * 3

enemies = [Enemy(x=x * 5, z=5) for x in range(-2, 3)]

def update():
    player.x += held_keys['d'] * time.dt * 5

app.run()

Wichtige Erkenntnisse

  • Verwende distance() für vollständige 3D-Analyse, distance_2d() für X-Y-Ebene, distance_xz() für horizontale X-Z-Ebene.
  • intersects() erfordert Collidern auf beiden Objekten und liefert detaillierte HitInfo-Daten.
  • Für Geschosse bevorzuge raycast() statt physischer Projektilen.
  • Kombiniere distance() mit intersects() für Interaktionszonen und präzise Kollisionsprüfungen.
  • Verwende immer ignore=(self,) in intersects(), um Selbstkollisionen zu vermeiden.

— Editorial Team

Advertisement 728x90

Weiterlesen