scene
2D 场景、精灵节点、触摸、Action 动画、物理与 Classic 绘图。
Pythonista 风格的 2D 场景运行时:精灵节点、触摸、逐帧 update()、Classic 绘图、动作动画与 SpriteKit 物理。
边界:scene 适合小游戏、画布动画和物理交互。普通表单、设置页、列表导航请用 appui;教材式海龟绘图用 turtle;主屏小组件用 widget。坐标原点在左下角(与 Pythonista 一致)。
#模块概览
| 项 | 说明 |
|---|---|
| 导入 | import scene |
| 适合做什么 | 触摸游戏、精灵动画、粒子、碰撞、Classic 画布、手柄输入 |
| 入口 | 定义 Scene 子类,脚本末尾只调用一次 scene.run(MyScene()) |
| 生命周期 | setup() 建节点;update() 每帧逻辑;draw() Classic 绘图;触摸走 touch_* |
| 关闭方式 | scene.run() 会阻塞直到场景关闭(界面上的关闭按钮或系统下滑 dismiss) |
| MiniApp | Scene 类型 MiniApp 入口同样是 scene.run(...),不是 appui.run() |
| 文档示例 | 代码块标记 previewScene,点「预览」直接打开全屏场景,不弹控制台 sheet |
#快速开始
点击运行后会打开全屏 2D 场景;触摸屏幕可看到坐标更新。
已复制
import scene
class TapDemo(scene.Scene):
def setup(self):
self.background_color = (0.08, 0.09, 0.12)
self.label = scene.LabelNode(
"Tap anywhere",
font=("Helvetica", 22),
position=(self.size.w / 2, self.size.h / 2),
parent=self,
)
def touch_began(self, touch):
self.label.text = f"{touch.location.x:.0f}, {touch.location.y:.0f}"
scene.run(TapDemo(), show_fps=True)
#可运行示例
#精灵与 Action 动画
SpriteNode 的第一个位置参数是纹理名;纯色块请用关键字 color=。动画用 node.run_action(...) 启动。
已复制
import scene
class SpriteDemo(scene.Scene):
def setup(self):
self.background_color = "black"
self.box = scene.SpriteNode(
color="cyan",
size=(80, 80),
position=(self.size.w / 2, self.size.h / 2),
parent=self,
)
pulse = scene.Action.sequence([
scene.Action.scale_to(1.2, 0.5),
scene.Action.scale_to(0.8, 0.5),
])
self.box.run_action(scene.Action.repeat_forever(pulse))
scene.run(SpriteDemo(), show_fps=True)
#Classic 绘制(draw())
fill、rect、line、text 等只能在 draw() 里调用。
已复制
import scene
class DrawDemo(scene.Scene):
def draw(self):
scene.background(0.05, 0.06, 0.08)
scene.fill(0.2, 0.6, 1.0)
scene.rect(40, 80, 120, 80, corner_radius=12)
scene.stroke(1.0, 1.0, 1.0, 0.8)
scene.stroke_weight(3)
scene.line(40, 40, 180, 120)
scene.fill(1, 1, 1)
scene.text("Draw", x=100, y=130, font_size=20)
scene.run(DrawDemo())
#物理与碰撞
给节点设置 physics_body;静态地面设 dynamic = False。需要碰撞回调时重写 contact_began(contact)。
已复制
import scene
class PhysicsDemo(scene.Scene):
def setup(self):
self.background_color = "#101820"
w, h = self.size.w, self.size.h
floor = scene.SpriteNode(
color="#444",
size=(w, 120),
position=(w / 2, 60),
parent=self,
)
floor_body = scene.PhysicsBody.rectangle(w=w, h=120)
floor_body.dynamic = False
floor.physics_body = floor_body
self.ball = scene.SpriteNode(
color="#ff8c42",
size=(44, 44),
position=(w / 2, h - 100),
parent=self,
)
ball_body = scene.PhysicsBody.circle(r=22)
ball_body.restitution = 0.65
self.ball.physics_body = ball_body
def touch_began(self, touch):
self.ball.position = (touch.location.x, touch.location.y)
scene.run(PhysicsDemo(), show_fps=True)
#模态子场景
主场景 present_modal_scene,子场景 dismiss_modal_scene 关闭。
已复制
import scene
class SubScene(scene.Scene):
def setup(self):
self.background_color = (0.1, 0.1, 0.15, 0.92)
self.label = scene.LabelNode(
"子场景 — 点一下关闭",
font=("Helvetica", 20),
position=(self.size.w / 2, self.size.h / 2),
parent=self,
)
def touch_began(self, touch):
self.dismiss_modal_scene()
class MainScene(scene.Scene):
def setup(self):
self.label = scene.LabelNode(
"主场景 — 点一下打开子场景",
font=("Helvetica", 20),
position=(self.size.w / 2, self.size.h / 2),
parent=self,
)
def touch_began(self, touch):
self.present_modal_scene(SubScene())
scene.run(MainScene())
#心智模型
- 入口:
scene.run(SceneSubclass(), ...)阻塞当前脚本,直到用户关闭场景视图。 - 初始化:在
setup()创建节点、纹理、物理体;此时self.size已由运行时注入。 - 持续逻辑:轻量状态推进放
update();节点位移/缩放优先Action,避免每帧新建节点。 - 自绘:Classic API(
scene.rect等)只放在draw();保留式 UI 用SpriteNode/LabelNode。 - 触摸:实现
touch_began/touch_moved/touch_ended,用touch.location.x/y。 - 资源:图片、音效、控制器状态在
setup()缓存,不要在每帧重复加载。
#与 appui / turtle 怎么选
| 需求 | 首选 | 原因 |
|---|---|---|
| 小游戏、Sprite、粒子、碰撞、触摸循环 | scene | 帧循环、节点树、Action、Physics |
| 设置页、列表、图表、导航 | appui | 原生控件与状态管理 |
| Pythonista 命令式 UIKit 控件 | ui | 迁移旧 ui 脚本 |
| 教材海龟、几何作图 | turtle | API 更简单,无节点/物理 |
| 主屏/锁屏展示 | widget | WidgetKit 时间线模型 |
#API 参考
#速查
| API | 作用 |
|---|---|
run(scene, *, orientation=0, frame_interval=1, ...) | 全屏运行场景(阻塞至关闭) |
Scene | 场景基类:setup / update / draw / touch_* |
SpriteNode / LabelNode / ShapeNode | 精灵、文字、矢量形状 |
Action.move_to / sequence / repeat_forever | 节点动画 |
PhysicsBody / PhysicsWorld | 碰撞与重力 |
background / fill / rect / text | Classic 绘图(仅 draw()) |
get_screen_size() / get_safe_area_insets() | 屏幕与安全区 |
PORTRAIT / LANDSCAPE | run() 的方向常量 |
#scene.run()
已复制
scene.run(
MyScene(),
orientation=scene.PORTRAIT, # DEFAULT_ORIENTATION=0, PORTRAIT=1, LANDSCAPE=2
frame_interval=1, # 1≈60fps,2≈30fps
anti_alias=True,
show_fps=True,
multi_touch=True,
)
IDE / MiniApp 的 Scene 模式 会在内部使用主线程回调(_mode='main');文档里直接运行上述示例即可。
#常用节点构造
已复制
# 纹理精灵(texture 为资源名或 Texture)
scene.SpriteNode("Player", position=(100, 200), parent=self)
# 纯色块 — 必须用 color= 关键字,不要把颜色当第一个位置参数
scene.SpriteNode(color="cyan", size=(60, 60), position=(x, y), parent=self)
# 文字
scene.LabelNode("Hello", font=("Helvetica", 24), position=(x, y), parent=self)
# 形状
scene.ShapeNode(fill_color="yellow", stroke_color="white", parent=self)
#Action Timing
Action.move_to(x, y, duration, timing_mode=scene.TIMING_LINEAR) 常用 timing:TIMING_EASE_IN_OUT、TIMING_BOUNCE_OUT 等(见 API 参考)。
#Classic 绘图要点
| API | 说明 |
|---|---|
scene.text(txt, x=0, y=0, font_name='Helvetica', font_size=16) | 在 draw() 中绘制文字 |
scene.rect(x, y, w, h, corner_radius=0) | 矩形;先 fill / stroke 再画 |
scene.image(name, x, y, w, h) | 绘制已加载图片 |
image_quad、triangle_strip 在兼容层中尚未实现,调用会抛 NotImplementedError。
#场景属性(运行时)
| 属性 | 说明 |
|---|---|
self.size / self.size.w / self.size.h | 场景尺寸(Size,支持 / 2 等向量运算) |
self.dt / self.t | 帧间隔 / 累计时间(秒) |
self.frame_count | 帧计数 |
self.physics_world | 物理世界,可改 gravity |
self.safe_area_insets | 刘海/底栏安全区 |
完整签名见 scene API 索引 与 scene API 参考。
#常见错误
| 错误写法 | 后果 | 修正 |
|---|---|---|
SpriteNode("cyan", ...) | "cyan" 被当成纹理名 | 使用 SpriteNode(color="cyan", ...) |
在 setup() 里调用 scene.rect() | 不显示或行为异常 | 绘图放进 draw() |
只创建 Action.move_to(...) 不 run_action | 节点不动 | node.run_action(action) |
脚本里没有 scene.run(...) | 场景不出现 | 末尾调用一次 scene.run(MyScene()) |
在 update() 里做网络/大文件 I/O | 掉帧、卡顿 | 放后台线程,主线程只改状态 |
用 appui.Button 做场景 HUD | API 不存在 | 用 LabelNode + 触摸,或改用 appui |
表单/设置页整页用 scene | 难维护 | 改用 appui 的 Form / List |
#失败路径
| 情况 | 处理 |
|---|---|
| 场景不显示 | 确认调用了 scene.run(SceneSubclass()),且 setup() 未抛错 |
| 触摸无反应 | 实现 touch_began 等,坐标用 touch.location |
节点在 (0,0) | 检查 position= 是否在 setup() 里用 self.size.w/h 居中 |
| 物理不碰撞 | 设置 physics_body,静态体 dynamic=False |
| 关闭后文档里再运行提示忙 | 等上一次 scene.run 完全结束,或先停止当前 Python 运行 |
#相关文档
| 文档 | 用途 |
|---|---|
| scene API 索引 | 按类族快速查名称 |
| scene API 参考 | 生命周期、节点、Action、Physics 签名 |
| turtle | 海龟教学绘图 |
| appui | 原生表单与导航 UI |
| ui | Pythonista 风格 ui 控件 |
| motion | 加速度计/陀螺仪(可与 scene 配合) |
#先选写法
| 目标 | 写法 |
|---|---|
| 精灵 / 物理游戏 | Scene 子类 + 节点树 |
| 即时画布 | draw() + Classic API |
| 教程海龟 | turtle |
#写 scene 的心智模型
setup()建节点;update()做帧逻辑;draw()只负责即时绘制。- 动画优先
Action+run_action,不要阻塞循环。 - 脚本末尾只调用一次
scene.run(...)。
#发布前检查
- 触摸、暂停、关闭路径可验证
- 精灵颜色使用
color=关键字参数 - 未使用 AppUI 布局 API
#预期效果
运行示例后,界面应出现文档描述的目标结果;若与预期不符,先看「失败路径」并按返回值或日志排查。