#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ PANDA3D 3D空间GUI演示 展示如何在3D场景中使用GUI元素 """ from direct.showbase.ShowBase import ShowBase from direct.gui.DirectGui import * from direct.gui.OnscreenText import OnscreenText from direct.gui.OnscreenImage import OnscreenImage from panda3d.core import * from direct.task import Task class GUI3DDemo(ShowBase): def __init__(self): ShowBase.__init__(self) # 设置背景色 self.setBackgroundColor(0.1, 0.1, 0.3) # 设置相机位置 self.cam.setPos(0, -10, 0) self.cam.lookAt(0, 0, 0) # 尝试加载中文字体 self.chinese_font = self.loadChineseFont() # 创建标题(2D界面) self.title = OnscreenText( text="PANDA3D 3D空间GUI演示", pos=(0, 0.9), scale=0.08, fg=(1, 1, 1, 1), align=TextNode.ACenter, font=self.chinese_font if self.chinese_font else None ) # 创建基本的2D GUI self.create2DGUI() # 创建3D场景中的GUI元素 self.create3DGUI() # 加载一个简单的3D模型作为背景 self.createScene() print("3D GUI演示启动成功!") print(f"中文字体加载状态: {'成功' if self.chinese_font else '失败,使用默认字体'}") print("你可以看到2D界面元素和3D空间中的GUI") def loadChineseFont(self): """尝试加载中文字体""" import os font_paths = [ '/usr/share/fonts/truetype/wqy/wqy-microhei.ttc', '/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc', '/usr/share/fonts/truetype/arphic/ukai.ttc', '/usr/share/fonts/truetype/arphic/uming.ttc', ] for font_path in font_paths: if os.path.exists(font_path): try: font = self.loader.loadFont(font_path) if font: return font except: continue return None def create2DGUI(self): """创建传统的2D GUI元素""" # 控制面板标题 self.controlTitle = OnscreenText( text="控制面板", pos=(-1.2, 0.8), scale=0.06, fg=(1, 1, 0, 1), align=TextNode.ALeft, font=self.chinese_font if self.chinese_font else None ) # 旋转控制按钮 self.rotateButton = DirectButton( text="旋转场景", pos=(-1.2, 0, 0.6), scale=0.05, command=self.toggleRotation, frameColor=(0.2, 0.6, 0.8, 1), text_font=self.chinese_font if self.chinese_font else None ) # 颜色变化按钮 self.colorButton = DirectButton( text="变换颜色", pos=(-1.2, 0, 0.4), scale=0.05, command=self.changeColors, frameColor=(0.8, 0.2, 0.6, 1), text_font=self.chinese_font if self.chinese_font else None ) # 重置按钮 self.resetButton = DirectButton( text="重置", pos=(-1.2, 0, 0.2), scale=0.05, command=self.resetScene, frameColor=(0.6, 0.8, 0.2, 1), text_font=self.chinese_font if self.chinese_font else None ) # 状态显示 self.statusText = OnscreenText( text="状态: 就绪", pos=(-1.2, -0.8), scale=0.04, fg=(0, 1, 0, 1), align=TextNode.ALeft, font=self.chinese_font if self.chinese_font else None ) # 退出按钮 self.exitButton = DirectButton( text="退出", pos=(1.2, 0, -0.8), scale=0.05, command=self.exitDemo, frameColor=(0.8, 0.2, 0.2, 1), text_font=self.chinese_font if self.chinese_font else None ) def create3DGUI(self): """创建3D空间中的GUI元素""" # 创建一个在3D空间中的按钮 # 这实际上是一个2D GUI元素,但我们可以将其父级设置为3D节点 # 创建3D空间中的文本 self.text3D = TextNode('3d-text') self.text3D.setText("3D空间中的文本") self.text3D.setAlign(TextNode.ACenter) if self.chinese_font: self.text3D.setFont(self.chinese_font) self.textNodePath = self.render.attachNewNode(self.text3D) self.textNodePath.setPos(0, 5, 2) self.textNodePath.setScale(0.5) self.textNodePath.setColor(1, 1, 0, 1) self.textNodePath.setBillboardAxis() # 让文本总是面向相机 # 创建另一个3D文本,显示说明 self.info3D = TextNode('info-text') self.info3D.setText("这是3D空间中的GUI元素\n它们可以与3D场景交互") self.info3D.setAlign(TextNode.ACenter) if self.chinese_font: self.info3D.setFont(self.chinese_font) self.infoNodePath = self.render.attachNewNode(self.info3D) self.infoNodePath.setPos(0, 3, -1) self.infoNodePath.setScale(0.3) self.infoNodePath.setColor(0, 1, 1, 1) self.infoNodePath.setBillboardAxis() # 创建一个3D平面作为"虚拟屏幕" self.createVirtualScreen() def createVirtualScreen(self): """创建一个3D空间中的虚拟屏幕""" # 创建一个卡片(平面) cm = CardMaker("virtual-screen") cm.setFrame(-2, 2, -1, 1) self.virtualScreen = self.render.attachNewNode(cm.generate()) self.virtualScreen.setPos(3, 5, 0) self.virtualScreen.setColor(0.2, 0.2, 0.2, 0.8) self.virtualScreen.setTransparency(TransparencyAttrib.MAlpha) # 在虚拟屏幕上添加文本 screenText = TextNode('screen-text') screenText.setText("虚拟3D屏幕\n\n这里可以显示\n任何信息") screenText.setAlign(TextNode.ACenter) if self.chinese_font: screenText.setFont(self.chinese_font) screenTextNP = self.virtualScreen.attachNewNode(screenText) screenTextNP.setPos(0, 0.01, 0) # 稍微偏移,避免z-fighting screenTextNP.setScale(0.3) screenTextNP.setColor(0, 1, 0, 1) def createScene(self): """创建3D场景""" # 创建一个简单的立方体 from panda3d.core import CardMaker # 创建地面 cm = CardMaker("ground") cm.setFrame(-10, 10, -10, 10) ground = self.render.attachNewNode(cm.generate()) ground.setP(-90) # 旋转成水平面 ground.setColor(0.5, 0.5, 0.5, 1) # 创建一些立方体 self.cubes = [] for i in range(3): for j in range(3): # 使用内置的立方体几何 cube = self.loader.loadModel("models/environment") if not cube: # 如果找不到模型,创建一个简单的几何体 cube = self.render.attachNewNode("cube") cube.reparentTo(self.render) cube.setPos(-2 + i*2, 2 + j*2, 0.5) cube.setScale(0.5) cube.setColor( 0.2 + i*0.3, 0.2 + j*0.3, 0.5, 1 ) self.cubes.append(cube) # 添加光照 dlight = DirectionalLight('dlight') dlight.setColor((1, 1, 1, 1)) dlnp = self.render.attachNewNode(dlight) dlnp.setHpr(45, -45, 0) self.render.setLight(dlnp) alight = AmbientLight('alight') alight.setColor((0.3, 0.3, 0.3, 1)) alnp = self.render.attachNewNode(alight) self.render.setLight(alnp) # 初始化旋转状态 self.rotating = False self.colorIndex = 0 def toggleRotation(self): """切换场景旋转""" if self.rotating: self.taskMgr.remove("rotate-scene") self.rotating = False self.rotateButton['text'] = "旋转场景" self.statusText.setText("状态: 停止旋转") else: self.taskMgr.add(self.rotateSceneTask, "rotate-scene") self.rotating = True self.rotateButton['text'] = "停止旋转" self.statusText.setText("状态: 正在旋转") print(f"旋转状态: {'开启' if self.rotating else '关闭'}") def rotateSceneTask(self, task): """旋转场景的任务""" for cube in self.cubes: cube.setH(cube.getH() + 50 * globalClock.getDt()) # 旋转3D文本 self.textNodePath.setH(self.textNodePath.getH() + 30 * globalClock.getDt()) self.virtualScreen.setH(self.virtualScreen.getH() + 20 * globalClock.getDt()) return task.cont def changeColors(self): """改变场景颜色""" self.colorIndex = (self.colorIndex + 1) % 3 colors = [ [(1, 0.5, 0.5), (0.5, 1, 0.5), (0.5, 0.5, 1)], # 红绿蓝 [(1, 1, 0.5), (1, 0.5, 1), (0.5, 1, 1)], # 黄洋红青 [(0.8, 0.2, 0.8), (0.2, 0.8, 0.8), (0.8, 0.8, 0.2)] # 紫青黄 ] colorSet = colors[self.colorIndex] for i, cube in enumerate(self.cubes): color = colorSet[i % 3] cube.setColor(*color, 1) # 改变3D文本颜色 textColors = [(1, 1, 0), (0, 1, 1), (1, 0, 1)] self.textNodePath.setColor(*textColors[self.colorIndex], 1) self.statusText.setText(f"状态: 颜色方案 {self.colorIndex + 1}") print(f"切换到颜色方案 {self.colorIndex + 1}") def resetScene(self): """重置场景""" # 停止旋转 if self.rotating: self.toggleRotation() # 重置立方体位置和颜色 for i, cube in enumerate(self.cubes): row = i // 3 col = i % 3 cube.setPos(-2 + col*2, 2 + row*2, 0.5) cube.setHpr(0, 0, 0) cube.setColor(0.2 + col*0.3, 0.2 + row*0.3, 0.5, 1) # 重置3D文本 self.textNodePath.setHpr(0, 0, 0) self.textNodePath.setColor(1, 1, 0, 1) self.virtualScreen.setHpr(0, 0, 0) self.colorIndex = 0 self.statusText.setText("状态: 场景已重置") print("场景已重置") def exitDemo(self): """退出演示""" print("退出3D GUI演示") self.userExit() if __name__ == "__main__": print("启动PANDA3D 3D GUI演示...") demo = GUI3DDemo() demo.run()