Powrót do strony głównej

Odległości i kolizje w Ursina 3D

Przewodnik po funkcjach distance(), distance_2d(), distance_xz() i intersects() w Ursina. Przykłady kodu do obliczania odległości, wykrywania kolizji, AI i podbierania przedmiotów w grach 3D.

Ursina: distance i intersects dla gier
Advertisement 728x90

Obliczanie odległości i kolizji w Ursinie dla gier 3D

Funkcja distance() oblicza odległość euklidesową w przestrzeni 3D we wszystkich kierunkach X, Y, Z między środkami obiektów typu Entity:

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'Odległość od bohatera do kuli: {dist:.2f}')

app.run()

Wzór: √((x₂-x₁)² + (y₂-y₁)² + (z₂-z₁)²). Idealny do kompleksowego analizowania przestrzeni.

distance_2d() pomija współrzędną Z i działa tylko na płaszczyźnie X-Y:

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_2d(player, sphere) 
    print(f'Odległość 2D od bohatera do kuli: {dist:.2f}') 

app.run()

Używane do elementów interfejsu lub obiektów na tej samej wysokości.

distance_xz() skupia się na poziomej płaszczyźnie X-Z, pomijając Y:

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'Odległość pozioma od bohatera do kuli: {dist:.2f}') 

app.run()

Doskonałe do AI na ziemi, gdzie wysokość nie ma znaczenia.

Google AdInline article slot

Mechanizm intersects() do kolizji

intersects() sprawdza przecięcia kolektorów (box, sphere, mesh) za pomocą Panda3D CollisionTraverser. Wymaga ustawienia collider i collision=True u obiektów.

Zwraca obiekt HitInfo z polami:

  • hit: True/False
  • entity: obiekt, z którym doszło do kolizji
  • point: współrzędne punktu uderzenia
  • distance: odległość do punktu uderzenia

Przykład 1: Wykrywanie ściany

Google AdInline article slot
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('Kolizja!')
        print(f'Z kim: {hit_info.entity}')
        print(f'Punkt uderzenia: {hit_info.point}')
        print(f'Odległość do punktu uderzenia: {hit_info.distance:.2f}')

app.run()

Przykład 2: Podnoszenie przedmiotu

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('Przedmiot podniesiony!')
        destroy(pickup)

app.run()

Dokładniejsze niż distance() dla złożonych kształtów.

Przykład 3: System pocisków

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'Pocisk trafił w {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()

Zalecane jest używanie raycast() do precyzyjnych trafień.

Praktyczne scenariusze

Podnoszenie na podstawie odległości:

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('Przedmiot podniesiony!')
        player.has_pickup = True
        pickup.animate_scale(0, duration=.1)
        destroy(pickup, delay=.1)

app.run()

AI wroga z obszarem wykrywania:

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()

Co warto wiedzieć

  • Używaj distance() dla pełnej przestrzeni 3D, distance_2d() dla płaszczyzny X-Y, distance_xz() dla poziomej płaszczyzny X-Z.
  • intersects() wymaga kolektorów u obu obiektów i zwraca szczegółowe informacje w HitInfo.
  • Dla pocisków lepiej stosować raycast() niż fizyczne projekty.
  • Łącz distance() z intersects() do tworzenia stref interakcji i dokładnych kolizji.
  • Ignoruj sam siebie w intersects(ignore=(self,)), aby uniknąć błędów.

— Editorial Team

Advertisement 728x90

Czytaj dalej