
本文详解如何使用 Kivy 的 PushMatrix/PopMatrix 与 Rotate 指令,结合鼠标坐标实时计算旋转角度,让一个矩形始终以其短边垂直指向鼠标位置,实现动态瞄准效果。
本文详解如何使用 kivy 的 `pushmatrix`/`popmatrix` 与 `rotate` 指令,结合鼠标坐标实时计算旋转角度,让一个矩形始终以其短边垂直指向鼠标位置,实现动态瞄准效果。
在 Kivy 中实现“鼠标跟随旋转”的核心逻辑并非直接修改 Widget 的 rotation 属性(该属性不支持局部坐标系下的精确锚点旋转),而是借助 OpenGL 绘图指令,在画布(canvas)中动态重绘:每次鼠标移动时,清除旧图形,重新应用旋转变换,并以指定锚点(如矩形中心或某角点)为原点执行 Rotate。
以下是一个完整、可运行的实现方案:
✅ 关键步骤解析
- 定义旋转锚点(origin):为使矩形“指向”鼠标,需选择一个固定参考点(如矩形中心或一端中点)。示例中采用 fixed_point = (x, y + height/2) —— 即矩形底边中点,模拟“炮管基座”,确保旋转后顶部自然朝向鼠标。
- 实时计算角度:使用 math.atan2(dy, dx) 计算从锚点到鼠标的极角(弧度),再转为角度(math.degrees())。注意:Kivy 的 Rotate.angle 是逆时针为正,且 Y 轴向上,与屏幕坐标系一致,无需额外翻转。
- 安全重绘机制:每次调用 self.canvas.clear() 清除上一帧,再在 with self.canvas: 块中重建图形。务必包裹 PushMatrix 和 PopMatrix,避免变换污染后续绘图。
? 完整代码(精简优化版)
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.graphics import Color, Rectangle, PushMatrix, PopMatrix, Rotate
import math
class MenuScreen(Screen):
pass
class Game(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# 矩形尺寸与初始位置
self.cannon_size = (150, 50)
self.cannon_pos = (300, 200) # 初始左下角坐标
# 旋转锚点:取矩形底边中点(模拟炮座)
self.fixed_point = (
self.cannon_pos[0] + self.cannon_size[0] / 2,
self.cannon_pos[1]
)
# 绑定鼠标移动事件
Window.bind(mouse_pos=self.update_rotation)
def update_rotation(self, *args):
mouse_x, mouse_y = Window.mouse_pos
# 计算相对位移
dx = mouse_x - self.fixed_point[0]
dy = mouse_y - self.fixed_point[1]
# 计算角度(atan2 自动处理象限)
angle = math.degrees(math.atan2(dy, dx))
# 清空并重绘
self.canvas.clear()
with self.canvas:
PushMatrix()
# 以 fixed_point 为原点旋转
Rotate(angle=angle, origin=self.fixed_point)
Color(0.2, 0.6, 0.8, 1) # 蓝色矩形
Rectangle(pos=self.cannon_pos, size=self.cannon_size)
PopMatrix()
class Myapp(ScreenManager):
pass
class MyApp(App):
def build(self):
return Myapp()
if __name__ == '__main__':
MyApp().run()
? 对应 my.kv 文件(最小化配置)
<myapp>:
MenuScreen:
name: "home"
Game:
name: "game"
<menuscreen>:
BoxLayout:
Button:
text: "I'M READY."
font_size: 48
on_release: root.current = 'game'
<game>:
# 可选:添加背景色便于观察
canvas.before:
Color:
rgba: 0.1, 0.1, 0.1, 1
Rectangle:
pos: self.pos
size: self.size</game></menuscreen></myapp>
⚠️ 注意事项与进阶建议
- 性能提示:高频 canvas.clear() + 重绘适用于简单图形;若需高性能(如大量旋转对象),应改用 InstructionGroup 或 Canvas.add() 动态管理指令。
- 坐标系陷阱:Window.mouse_pos 返回的是窗口绝对坐标(左下为原点),而 Rectangle.pos 默认也是窗口坐标系,二者单位一致,可直接运算。
- 平滑优化:如需缓动效果,可用 kivy.clock.Clock.schedule_interval 替代实时绑定,配合插值算法(如 angle = lerp(old_angle, target_angle, 0.1))。
- 交互扩展:可进一步结合 on_touch_down 实现点击发射、拖拽调整位置等逻辑。
通过本方案,你已掌握 Kivy 中基于画布的动态旋转核心技术——它灵活、精准,是构建游戏瞄准、仪表盘指针、旋转 UI 元素等场景的基石。










