本文详解如何使用 Kivy 的 PushMatrix/PopMatrix 与 Rotate 指令,结合鼠标位置实时计算角度,使矩形(如炮台)始终朝向鼠标,实现动态旋转交互。
本文详解如何使用 kivy 的 `pushmatrix`/`popmatrix` 与 `rotate` 指令,结合鼠标位置实时计算角度,使矩形(如炮台)始终朝向鼠标,实现动态旋转交互。
在 Kivy 中,直接修改 Widget 的 rotation 属性无法精准控制旋转中心或实现“指向鼠标”的语义(因其默认绕自身中心旋转)。正确做法是:在画布(canvas)中动态重绘,并利用 OpenGL 变换指令实现以指定锚点为中心的实时旋转变换。
核心思路如下:
- 定义一个固定旋转原点(例如矩形的中心线顶端,模拟炮管枢轴点);
- 在每次鼠标移动时,计算从该原点指向鼠标的向量夹角;
- 使用 PushMatrix + Rotate(angle=..., origin=...) 包裹图形绘制指令,确保旋转围绕指定点发生;
- 调用 self.canvas.clear() 避免旧图形残留(注意:此方式适用于简单动态对象;复杂场景建议使用 InstructionGroup 或自定义 Canvas 管理)。
以下是关键代码实现(已优化结构与健壮性):
from kivy.core.window import Window
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.app import App
from kivy.uix.widget import Widget
import math
from kivy.graphics import Color, Rectangle, PushMatrix, PopMatrix, Rotate
from kivy.config import Config
Config.set('graphics', 'resizable', True)
class MenuScreen(Screen):
pass
class Game(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cannon_size = (150, 50)
self.cannon_pos = (300, 200) # 初始位置(可居中:Window.width//2 - 75, Window.height//2 - 25)
# 旋转中心设为矩形顶部中点(即“炮管转轴”)
self.pivot = (self.cannon_pos[0] + self.cannon_size[0] / 2,
self.cannon_pos[1] + self.cannon_size[1])
self.cannon_angle = 0
# 绑定鼠标移动事件(注意:mouse_pos 是窗口坐标,需确保窗口已初始化)
Window.bind(mouse_pos=self.update_cannon_rotation)
def update_cannon_rotation(self, *args):
mouse_x, mouse_y = Window.mouse_pos
# 计算相对于 pivot 的向量
dx = mouse_x - self.pivot[0]
dy = mouse_y - self.pivot[1]
# atan2 返回 [-π, π],转为 [0, 360) 度,且适配 Kivy Y 轴向下惯例
# 注意:Kivy 的 Rotate 正方向为逆时针,而视觉上“指向鼠标”需补偿 90°(因矩形默认水平放置,长边在X轴)
self.cannon_angle = math.degrees(math.atan2(dy, dx)) - 90
# 清空画布并重绘
self.canvas.clear()
with self.canvas:
PushMatrix()
# 关键:以 pivot 为原点旋转
Rotate(angle=self.cannon_angle, origin=self.pivot)
Color(0.2, 0.6, 1, 1) # 蓝色炮管
Rectangle(pos=self.cannon_pos, size=self.cannon_size)
PopMatrix()
def on_enter(self):
# 进入游戏屏时确保初始绘制
self.update_cannon_rotation()
class Myapp(ScreenManager):
pass
class MyApp(App):
def build(self):
return Myapp()
if __name__ == '__main__':
MyApp().run()
? 注意事项与最佳实践:
- ✅ Window.mouse_pos 返回的是窗口绝对坐标,不受 widget 坐标系影响,适合全局定位;
- ⚠️ canvas.clear() 会清除整个 widget 的 canvas,若界面含其他静态元素(如背景),应将其绘制在 canvas.before 或独立 widget 中;
- ? 若需更高性能(如高频更新),避免每帧 clear() + 全量重绘,改用 InstructionGroup 缓存非动态部分,仅更新 Rotate 指令参数;
- ? 角度计算中 -90 补偿源于 Kivy 默认矩形沿 X 轴正向延伸(→),而“炮管朝向鼠标”要求其“前端”(即矩形右侧中点)对准鼠标——故需将初始朝向由 0°(东)校正为 -90°(北),再叠加动态偏移;
- ? 实际项目中,建议将旋转逻辑封装为可复用的 RotatableSprite 类,并支持缩放、锚点配置等扩展能力。
通过本方案,你不仅能实现鼠标跟随旋转,更掌握了 Kivy 底层画布变换的核心范式:状态隔离(PushMatrix/PopMatrix)、原点控制(origin)、以及向量几何驱动的交互逻辑——这是构建游戏、可视化工具或高级 UI 的关键基础。










