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

Python/Ursina引擎中固定UI对象随角色移动偏移问题排查

Ursina游戏中绑定相机的UI随角色移动偏移的解决方法

问题现象

为角色添加平滑相机跟随后,绑定相机坐标的血条、等级计数器等UI对象,会随角色移动方向轻微偏移,角色停止后才恢复原位,严重影响游戏观感。

问题原因

  1. 当前UI以世界空间实体创建,通过在update中同步相机坐标实现“固定”效果,但相机平滑跟随(如线性插值)会导致相机位置滞后于角色,UI同步时也会跟着滞后,出现偏移。
  2. 世界空间UI依赖场景坐标,相机移动时必然会受其位置变化影响,无法真正固定在屏幕上。

解决方案

核心思路

将UI改为屏幕空间UI,直接固定在屏幕指定位置,完全脱离相机和场景坐标的影响;同时优化相机的平滑跟随逻辑。

关键代码修改

  1. 创建屏幕空间UI
    修改UI实体的parent为camera.ui,使用屏幕百分比坐标(范围-1到1,(0,0)为屏幕中心):

    # 替换原UI创建代码
    BulletBar = Entity(parent=camera.ui, model="quad", position=(-0.8, -0.45), scale=(0.6, 0.025), color="FFFF00", origin=(-0.5, 0))
    HealthBar = Entity(parent=camera.ui, model="quad", position=(-0.8, -0.375), scale=(0.6, 0.05), color="FF0000", origin=(-0.5, 0))
    levelgos = Entity(parent=camera.ui, model="quad", position=(-0.9, 0.45), scale=(0.615, 0.165), texture="level-1.png")
    gamewon = Entity(parent=camera.ui, model="quad", position=(0,0), texture="Bink.png", scale=(0.9, 0.6))
    
  2. 实现相机平滑跟随
    在Karakter类的update方法中,用lerp函数替换直接赋值,实现平滑跟随:

    def update(self):
        global mermiat, mertim, oyun
        mertim += time.dt
        # 平滑跟随,smoothness越小越平滑
        smoothness = 0.1
        camera.x = lerp(camera.x, self.x, smoothness)
        camera.y = lerp(camera.y, self.y, smoothness)
        # 剩余原有逻辑不变...
    
  3. 删除UI坐标同步代码
    移除全局update函数中同步UI与相机坐标的代码:

    # 删除以下行:
    # BulletBar.x = camera.x - 13
    # BulletBar.y = camera.y - 9
    # HealthBar.x = camera.x - 13
    # HealthBar.y = camera.y - 7.5
    # levelgos.x = camera.x - 15
    # levelgos.y = camera.y + 10.2
    # gamewon.x = camera.x
    # gamewon.y = camera.y - 2
    
  4. 调整游戏结束/胜利的UI显示
    由于gamewon是屏幕空间UI,直接设置属性即可:

    if can <= 0:
        gamewon.texture="game-over.png"
        gamewon.scale = (0.9, 0.6)
        oyun = False
    # ...
    elif level == 11:
        gamewon.texture="you-won.png"
        gamewon.scale = (0.9, 0.6)
        oyun = False
    

修改后的完整代码

from ursina import *
import random

app = Ursina()

Mermiler = []
back = Entity(model="quad", z = 4, scale=(70,40,1), texture="Back.png")
mermiat = True
mertim = 0
can = 5
kill = 0
level = 0
dushiz = 4

class Dusman(Entity):
    def __init__(self,x,y):
        super().__init__()
        self.model = "quad"
        self.scale = (2.08,1.04)
        self.x = x
        self.y = y
        self.collider = "box"
        self.texture = "Dusman.png"

    def update(self):
        global can, oyun, Ens, kill, dushiz

        if oyun == True:
            if self.x > Player.x and self.y > Player.y:
                self.x -= dushiz * time.dt
                self.y -= dushiz * time.dt

            elif self.x > Player.x and self.y < Player.y:
                self.x -= dushiz * time.dt
                self.y += dushiz * time.dt

            elif self.x < Player.x and self.y > Player.y:
                self.x += dushiz * time.dt
                self.y -= dushiz * time.dt

            elif self.x < Player.x and self.y < Player.y:
                self.x += dushiz * time.dt
                self.y += dushiz * time.dt

        for mermi in Mermiler[:]:
            if self.intersects(mermi).hit:
                destroy(self)
                destroy(mermi)
                Mermiler.remove(mermi)
                Ens -= 1
                kill += 1

        if self.intersects(Player).hit:
            can -= 1
            destroy(self)
            Ens -=1

class Mermi(Entity):
    def __init__(self):
        super().__init__()
        self.model = "quad"
        self.position = Player.position
        self.rotation_z = Player.rotation_z
        self.scale = (0.25,0.5)
        self.collider = "box"
        self.texture = "Mermi.png"

class particle(Entity):
    def __init__(self,x,y):
        super().__init__()
        self.model = "quad"
        self.scale = 0.293
        self.x = x
        self.y = y
        self.z = -3
        self.texture = "Duman.png"

    def update(self):
        self.scale -= 0.19 * time.dt
        ax = random.uniform(-0.5,0.5)
        ay = random.uniform(-0.5,0.5)
        self.x += ax * 3.4 * time.dt
        self.y += ay * 3.4 * time.dt
        self.rotation_z += 2 * time.dt
        if self.scale <= 0.00005:
            destroy(self)

class particle2(Entity):
    def __init__(self,x,y):
        super().__init__()
        self.model = "quad"
        self.scale = 0.293
        self.x = x
        self.y = y
        self.z = -3
        self.texture = "particle2.png"

    def update(self):
        self.scale -= 0.19 * time.dt
        ax = random.uniform(-0.5,0.5)
        ay = random.uniform(-0.5,0.5)
        self.x += ax * 3.4 * time.dt
        self.y += ay * 3.4 * time.dt
        self.rotation_z += 2 * time.dt
        if self.scale <= 0.00005:
            destroy(self)

class Karakter(Entity):
    def __init__(self):
        super().__init__()
        self.model = "quad"
        self.position = (0,0)
        self.scale = (1.5,1.5)
        self.collider = "box"
        self.texture = "player.png"

    def update(self):
        global mermiat, mertim, oyun
        mertim += time.dt
        # 平滑跟随相机
        smoothness = 0.1
        camera.x = lerp(camera.x, self.x, smoothness)
        camera.y = lerp(camera.y, self.y, smoothness)
        
        if oyun == True:
            if held_keys["w"]:
                self.y += 18 * time.dt
                self.rotation_z = 0

            elif held_keys["s"]:
                self.y -= 18 * time.dt
                self.rotation_z = 180

            elif held_keys["d"]:
                self.x += 18 * time.dt
                self.rotation_z = 90

            elif held_keys["a"]:
                self.x -= 18 * time.dt
                self.rotation_z = 270

            if held_keys["space"] and mermiat:
                mermi = Mermi()
                Mermiler.append(mermi)
                mermiat = False
                if self.rotation_z == 0:
                    for _ in range(9):
                        particle(self.x,self.y+1.2)
                    for _ in range(3):
                        particle2(self.x,self.y+1.2)

                elif self.rotation_z == 90:
                    for _ in range(9):
                        particle(self.x+1.2,self.y)
                    for _ in range(3):
                        particle2(self.x+1.2,self.y)

                elif self.rotation_z == 180:
                    for _ in range(9):
                        particle(self.x,self.y-1.2)
                    for _ in range(3):
                        particle2(self.x,self.y-1.2)

                elif self.rotation_z == 270:
                    for _ in range(9):
                        particle(self.x-1.2,self.y)
                    for _ in range(3):
                        particle2(self.x-1.2,self.y)

            if self.x > 34 and held_keys["d"]:
                self.x = 30

            elif self.x < -34 and held_keys["a"]:
                self.x = -30

            elif self.y > 19 and held_keys["w"]:
                self.y = 15

            elif self.y < -19 and held_keys["s"]:
                self.y = -15

            if mermiat == False and mertim >= 1.0:
                mermiat = True
                mertim = 0

            elif mermiat == True and mertim >= 1.0:
                mertim = 0

            if mermiat == True:
                BulletBar.scale_x = 0.6

            if mermiat == False:
                BulletBar.scale_x = mertim * 0.5

def SpawnEnemy1():
    global oyun, Ens
    if oyun == True:
        rx = random.uniform(-35.0,35.0)
        Dusman(x=rx,y=20)
        Ens += 1

def SpawnEnemy2():
    global oyun, Ens
    if oyun == True:
        rx = random.uniform(-35.0,35.0)
        Dusman(x=rx,y=-20)
        Ens += 1

def SpawnEnemy3():
    global oyun, Ens
    if oyun == True:
        ry = random.uniform(-20.0,20.0)
        Dusman(x=35,y=ry)
        Ens +=1

def SpawnEnemy4():
    global oyun, Ens
    if oyun == True:
        ry = random.uniform(-20.0,20.0)
        Dusman(x=-35,y=ry)
        Ens += 1

spwn1 = 0
spwn2 = 0
spwn3 = 0
spwn4 = 0

Player = Karakter()
# 屏幕空间UI设置
BulletBar = Entity(parent=camera.ui, model="quad", position=(-0.8, -0.45), scale=(0.6, 0.025), color="FFFF00", origin=(-0.5, 0))
HealthBar = Entity(parent=camera.ui, model="quad", position=(-0.8, -0.375), scale=(0.6, 0.05), color="FF0000", origin=(-0.5, 0))
levelgos = Entity(parent=camera.ui, model="quad", position=(-0.9, 0.45), scale=(0.615, 0.165), texture="level-1.png")
gamewon = Entity(parent=camera.ui, model="quad", position=(0,0), texture="Bink.png", scale=(0.9, 0.6))
oyun = True
Ens = 0

spawnspeed = 6

def update():
    global spwn1, spwn2, spwn3, spwn4, can, oyun, Ens, kill, level, spawnspeed, dushiz
    # 移除UI坐标同步代码
    HealthBar.scale_x = can * 0.12  # 适配屏幕空间的缩放

    spwn1 += time.dt
    spwn2 += time.dt
    spwn3 += time.dt
    spwn4 += time.dt

    level = (kill // 20) + 1

    for mermi in Mermiler[:]:
        if abs(mermi.x) > (abs(Player.x) + 5) or abs(mermi.y) > (abs(Player.y) + 5):
            Mermiler.remove(mermi)
            destroy(mermi)

        else:
            if mermi.rotation_z == 0:
                mermi.y += 4 * time.dt

            elif mermi.rotation_z == 180:
                mermi.y -= 4 * time.dt

            elif mermi.rotation_z == 90:
                mermi.x += 4 * time.dt

            elif mermi.rotation_z == 270:
                mermi.x -= 4 * time.dt

    if spwn1 >= spawnspeed:
        spwn1 = 0
        if Ens <= 36:
            SpawnEnemy1()

    if spwn2 >= spawnspeed:
        if Ens <= 36:
            SpawnEnemy2()
        spwn2 = 0

    if spwn3 >= spawnspeed:
        if Ens<= 36:
            SpawnEnemy3()
        spwn3 = 0

    if spwn4 >= spawnspeed:
        if Ens <= 36:
            SpawnEnemy4()
        spwn4 = 0

    if can <= 0:
        gamewon.texture="game-over.png"
        oyun = False

    else:
        oyun = True

    if level == 1:
        spawnspeed = 6
        dushiz = 4
        levelgos.texture = "level-1.png"

    elif level == 2:
        spawnspeed = 5.9
        dushiz = 4.1
        levelgos.texture = "level-2.png"

    elif level == 3:
        spawnspeed = 5.8
        dushiz = 4.2
        levelgos.texture = "level-3.png"

    elif level == 4:
        spawnspeed = 5.7
        dushiz = 4.3
        levelgos.texture = "level-4.png"

    elif level == 5:
        spawnspeed = 5.6
        dushiz = 4.4
        levelgos.texture = "level-5.png"

    elif level == 6:
        spawnspeed = 5.5
        dushiz = 4.5
        levelgos.texture = "level-6.png"

    elif level == 7:
        spawnspeed = 5.4
        dushiz = 4.6
        levelgos.texture = "level-7.png"

    elif level == 8:
        spawnspeed = 5.3
        dushiz = 4.7
        levelgos.texture = "level-8.png"

    elif level == 9:
        spawnspeed = 5.2
        dushiz = 4.8
        levelgos.texture = "level-9.png"

    elif level == 10:
        spawnspeed = 5
        dushiz = 5
        levelgos.texture = "level-10.png"

    elif level == 11:
        gamewon.texture="you-won.png"
        oyun = False

camera.z = -60

app.run()

说明

  • 屏幕空间UI的position和scale基于屏幕比例调整,可根据实际需求修改数值,确保UI在不同分辨率下显示正常。
  • 相机平滑跟随的smoothness值可调整,0.1是适中的平滑度,值越大相机跟随越灵敏,越小则越平缓。
  • 血条的缩放值也适配了屏幕空间,确保显示比例正确。

内容的提问来源于stack exchange,提问作者troko 2545

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 12:14:51