Python/Ursina引擎中固定UI对象随角色移动偏移问题排查
Ursina游戏中绑定相机的UI随角色移动偏移的解决方法
问题现象
为角色添加平滑相机跟随后,绑定相机坐标的血条、等级计数器等UI对象,会随角色移动方向轻微偏移,角色停止后才恢复原位,严重影响游戏观感。
问题原因
- 当前UI以世界空间实体创建,通过在
update中同步相机坐标实现“固定”效果,但相机平滑跟随(如线性插值)会导致相机位置滞后于角色,UI同步时也会跟着滞后,出现偏移。 - 世界空间UI依赖场景坐标,相机移动时必然会受其位置变化影响,无法真正固定在屏幕上。
解决方案
核心思路
将UI改为屏幕空间UI,直接固定在屏幕指定位置,完全脱离相机和场景坐标的影响;同时优化相机的平滑跟随逻辑。
关键代码修改
创建屏幕空间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))实现相机平滑跟随
在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) # 剩余原有逻辑不变...删除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调整游戏结束/胜利的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
相关产品推荐
相关产品推荐

