EG/demo/gui_3d_demo.py
2025-07-02 09:49:59 +08:00

317 lines
11 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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()