Python利用3D引擎写一个Pong游戏

林见深
林见深
发布时间:2026-10-16| 5人浏览过
原创

本文介绍如何使用Python的3D引擎Ursina开发一个双人对战Pong游戏。通过导入随机库、定义分数、创建窗口与场景物体(桌子、球拍、球),并利用update函数处理按键输入、小球移动、碰撞反弹及计分逻辑,仅需不到100行代码即可实现完整的3D游戏效果。

前言

此前,我们曾使用pygame制作过2D版本的Pong游戏,今天我们将利用3D技术重新实现该游戏,最终效果如图所示:

游戏支持双人模式,玩家可通过键盘上的a、d键以及左、右方向键进行控制。

本项目的核心实现依赖于Python强大的3D引擎Ursina,关于该引擎的基础用法,可参考文章:详解Python 3D引擎Ursina如何绘制立体图形。

下面开始编写具体代码。

实现方法

首先,需要导入ursina模块以及Python的随机数库。

from ursina import *
import random as rd

接着,初始化定义两名玩家的初始分数变量。

scorea=scoreb=0

随后,创建ursina应用实例app。

app=Ursina()

Ursina提供了内置的window参数用于控制窗口,以及color参数用于定义颜色,我们可以直接通过修改window.color来设置窗口背景色。

window.color=color.cyan

创建一个代表桌子的长方体实体,设置其model为cube,颜色为橙色,并通过scale参数调整大小,同时指定position位置及普通材质纹理。

table=Entity(model="cube",color=color.orange,scale=(10,0.5,14),position=(0,0,0),texture="white_cube")

参照桌子的创建方式,构建一个球拍实体。

paddle_A=Entity(parent=table,color=color.black,model="cube",scale=(0.2,0.03,0.05),position=(0,3.7,0.22),collider="box")

由于第二个球拍与第一个结构相似,可直接使用duplicate函数复制第一个球拍实体,随后调整其部分参数(如位置)以区分两者。

paddle_B=duplicate(paddle_A,z=-0.62)

接下来处理分数显示,利用Text类创建文本实体,参数设置逻辑与前述实体类似。

t=Text(text=f"Player A : Player B  {scorea} : {scoreb}", position=(-0.85, 0.45), scale=2,color=color.orange)

初始运行程序时,视角可能仅显示桌子侧面,此时需调整Ursina内置camera对象的属性,包括设置其position位置及rotation_x倾斜角度,以获取最佳观察视角。

camera.position=(0,15,-26)
camera.rotation_x=30

在场景中绘制两名玩家的名称标识。

Text(text="Player A",scale=2,position=(-0.1,0.32),color=color.orange)
Text(text="Player B",scale=2,position=(-0.1,-0.4),color=color.orange)

随后,绘制桌子中间的分割线以及游戏用球。

line=Entity(parent=table,model="quad",scale=(0.88,0.2,0.1),position=(0,3.5,-0.2))
ball=Entity(parent=table,model="sphere",color=color.gold,scale=.05,position=(0,3.7,-0.2),collider="box")

初始化小球在x轴和z轴方向的初始速度变量。

dx=rd.uniform(-0.4,0.15) if rd.randint(0,1)==0 else rd.uniform(0.15,0.4)
dz=rd.uniform(-0.5,0.2) if rd.randint(0,1)==0 else rd.uniform(0.2,0.5)

进入游戏的主循环逻辑。

app.run()

在创建app实例之前,定义update函数。需注意该函数名必须严格为update,无需手动调用,Ursina引擎会自动每帧调用此函数。

在函数内部创建逻辑并引入全局变量以访问分数及速度状态。

def update():
    global dx,dz,scorea,scoreb

判断键盘按键状态,若按键被按下,则根据held_keys中存储的信息移动对应的球拍,同时通过条件判断限制球拍在桌子范围内的移动边界。

    paddle_B.x-=held_keys["left arrow"]*time.dt
    paddle_B.x+=held_keys["right arrow"]*time.dt
    paddle_A.x-=held_keys["a"]*time.dt
    paddle_A.x+=held_keys["d"]*time.dt
    if paddle_A.x>0.35:
        paddle_A.x=0.35
    elif paddle_A.x<-0.35:
        paddle_A.x=-0.35
    if paddle_B.x>0.35:
        paddle_B.x=0.35
    elif paddle_B.x<-0.35:
        paddle_B.x=-0.35

根据预设的速度变量更新小球的位置坐标。

    ball.x+=dx*time.dt
    ball.z+=dz*time.dt

实时刷新界面上的比分显示文本。

    t.text=f"Player A : Player B  {scorea} : {scoreb}"

执行球拍与球的碰撞检测,首先调用intersects方法获取碰撞到的实体列表,若检测到与两个球拍之一碰撞,则执行反弹逻辑,并根据碰撞点调整小球速度(加速或减速)。

    hit_info=ball.intersects()
    if hit_info.hit:
        if hit_info.entity==paddle_A:
            dz=-dz
            if dz>-0.05:
                dz-=rd.uniform(0.1,0.3)
            else:
                dz-=rd.uniform(-0.05,0.2)
        if hit_info.entity==paddle_B:
            dz=-dz
            if dz<0.05:
                dz+=rd.uniform(0.1,0.3)
            else:
                dz+=rd.uniform(-0.05,0.2)

限制小球在桌子宽度范围内的移动,若小球超出边界(即一方未接到球),则对另一方进行加分处理,并重置小球位置及速度。

    if abs(ball.x)>0.4:
        dx=-dx
    if ball.z>0.25:
        scoreb+=1
        ball.x=0
        ball.z=-0.2
        dx=rd.uniform(-0.4,0.15) if rd.randint(0,1)==0 else rd.uniform(0.15,0.4)
        dz=rd.uniform(-0.5,0.2) if rd.randint(0,1)==0 else rd.uniform(0.2,0.5)
    if ball.z<-0.65:
        scorea+=1
        ball.x=0
        ball.z=-0.2
        dx=rd.uniform(-0.4,0.15) if rd.randint(0,1)==0 else rd.uniform(0.15,0.4)
        dz=rd.uniform(-0.5,0.2) if rd.randint(0,1)==0 else rd.uniform(0.2,0.5)

通过不到100行代码,我们实现了上述完整的3D游戏效果。

完整代码

以下是最终实现的完整代码清单:

from ursina import *
import random as rd
 
scorea=scoreb=0
 
def update():
    global dx,dz,scorea,scoreb
    paddle_B.x-=held_keys["left arrow"]*time.dt
    paddle_B.x+=held_keys["right arrow"]*time.dt
    paddle_A.x-=held_keys["a"]*time.dt
    paddle_A.x+=held_keys["d"]*time.dt
    if paddle_A.x>0.35:
        paddle_A.x=0.35
    elif paddle_A.x<-0.35:
        paddle_A.x=-0.35
    if paddle_B.x>0.35:
        paddle_B.x=0.35
    elif paddle_B.x<-0.35:
        paddle_B.x=-0.35
    ball.x+=dx*time.dt
    ball.z+=dz*time.dt
    t.text=f"Player A : Player B  {scorea} : {scoreb}"
    hit_info=ball.intersects()
    if hit_info.hit:
        if hit_info.entity==paddle_A:
            dz=-dz
            if dz>-0.05:
                dz-=rd.uniform(0.1,0.3)
            else:
                dz-=rd.uniform(-0.05,0.2)
        if hit_info.entity==paddle_B:
            dz=-dz
            if dz<0.05:
                dz+=rd.uniform(0.1,0.3)
            else:
                dz+=rd.uniform(-0.05,0.2)
    if abs(ball.x)>0.4:
        dx=-dx
    if ball.z>0.25:
        scoreb+=1
        ball.x=0
        ball.z=-0.2
        dx=rd.uniform(-0.4,0.15) if rd.randint(0,1)==0 else rd.uniform(0.15,0.4)
        dz=rd.uniform(-0.5,0.2) if rd.randint(0,1)==0 else rd.uniform(0.2,0.5)
    if ball.z<-0.65:
        scorea+=1
        ball.x=0
        ball.z=-0.2
        dx=rd.uniform(-0.4,0.15) if rd.randint(0,1)==0 else rd.uniform(0.15,0.4)
        dz=rd.uniform(-0.5,0.2) if rd.randint(0,1)==0 else rd.uniform(0.2,0.5)
 
app=Ursina()
 
window.color=color.cyan
 
table=Entity(model="cube",color=color.orange,scale=(10,0.5,14),position=(0,0,0),texture="white_cube")
 
paddle_A=Entity(parent=table,color=color.black,model="cube",scale=(0.2,0.03,0.05),position=(0,3.7,0.22),collider="box")
paddle_B=duplicate(paddle_A,z=-0.62)
 
t=Text(text=f"Player A : Player B  {scorea} : {scoreb}", position=(-0.85, 0.45), scale=2,color=color.orange)
 
camera.position=(0,15,-26)
camera.rotation_x=30
 
Text(text="Player A",scale=2,position=(-0.1,0.32),color=color.orange)
Text(text="Player B",scale=2,position=(-0.1,-0.4),color=color.orange)
 
line=Entity(parent=table,model="quad",scale=(0.88,0.2,0.1),position=(0,3.5,-0.2))
ball=Entity(parent=table,model="sphere",color=color.gold,scale=.05,position=(0,3.7,-0.2),collider="box")
 
dx=rd.uniform(-0.4,0.15) if rd.randint(0,1)==0 else rd.uniform(0.15,0.4)
dz=rd.uniform(-0.5,0.2) if rd.randint(0,1)==0 else rd.uniform(0.2,0.5)
 
app.run()

到此这篇关于Python利用3D引擎写一个Pong游戏的文章就介绍到这了,更多相关Python Pong游戏内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。