You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt5为QPolygon图元设置随动裁剪纹理的实现方案咨询

PyQt5任意形状瓦片纹理填充实现方案

原有代码的核心问题

  • 渲染逻辑性能极差:每帧清空场景、重建所有瓦片图元、重复从磁盘加载纹理,是帧率下跌的主要原因
  • QBrush.setTexture() 默认以场景坐标为原点平铺纹理,既不与单个图元绑定,也不会自动按图元形状裁剪,直接使用必然出现纹理不同步、溢出多边形范围的问题
  • 相机实现逻辑冗余:通过重新计算所有瓦片顶点坐标实现平移,完全没必要,图元本身支持位置偏移,批量移动父节点即可实现相机效果

方案1:单瓦片独立纹理裁剪(最优,全形状适配)

纯PyQt5原生API实现,无需额外依赖,纹理自动绑定单个瓦片、自动裁剪到多边形范围内,后续更换瓦片形状(正方形、透视六边形等)不需要修改任何纹理相关代码,素材复用率100%。

核心实现逻辑

  • 资源预加载:纹理图片仅在初始化时加载一次,全局缓存复用
  • 图元复用:所有瓦片图元初始化时创建一次,后续相机移动仅修改位置,不重建
  • 纹理绑定:给每个瓦片的画刷设置对应变换,将纹理原点对齐到瓦片局部坐标原点,纹理会随瓦片同步移动;QGraphicsItem默认只会绘制自身形状范围内的内容,超出多边形的纹理会被自动裁剪,不需要手动做clip操作
  • 相机优化:将所有瓦片放到同一个QGraphicsGroupItem中,相机平移直接修改group的坐标即可,不需要遍历重算每个瓦片的顶点

修改后可运行代码

import sys
import math
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QHBoxLayout, QGraphicsScene, QGraphicsView, QGraphicsPolygonItem, QGraphicsItemGroup
from PyQt5.QtCore import Qt, QPointF, QTimer, QTransform
from PyQt5.QtGui import QPolygonF, QPixmap, QBrush


class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setCentralWidget(QWidget())
        self.horizontal = QHBoxLayout()
        self.centralWidget().setLayout(self.horizontal)
        
        # 相机偏移参数
        self.camera_x = 30
        self.camera_y = 30
        self.scale = 1.0
        self.tile_r = 50 * self.scale
        self.tile_h = self.tile_r * math.sin(math.pi/3)
        self.board = Board(8,8)
    
        self.scene = QGraphicsScene()
        # 预加载纹理,替换成你的图片路径即可
        self.texture_pixmap = QPixmap("your_texture_path.png")
        # 瓦片统一父节点,移动实现相机效果
        self.board_group = QGraphicsItemGroup()
        self.scene.addItem(self.board_group)

        self.init_window()
        self.init_tiles()

        # 60帧刷新,仅更新相机位置,不重建图元
        self.timer = QTimer()
        self.timer.timeout.connect(self.update_camera)
        self.timer.start(int(1000 / 60))
    
    
    def init_window(self):
        self.setGeometry(300,200,800,800)
        self.scene.setSceneRect(0, 0, 700, 700)
        self.view = QGraphicsView(self.scene, self)
        self.view.adjustSize()
        self.horizontal.addWidget(self.view)
        self.show()
    
    def init_tiles(self):
        """初始化所有瓦片,仅执行一次"""
        # 六边形顶点相对自身中心的局部坐标,后续换形状只要改这里的点就行
        relative_points = [
            QPointF(0, -self.tile_r),
            QPointF(self.tile_h, -0.5*self.tile_r),
            QPointF(self.tile_h, 0.5*self.tile_r),
            QPointF(0, self.tile_r),
            QPointF(-self.tile_h, 0.5*self.tile_r),
            QPointF(-self.tile_h, -0.5*self.tile_r)
        ]

        for x in range(self.board.width):
            for y in range(self.board.height):                
                polygon = QPolygonF(relative_points)
                tile_item = QGraphicsPolygonItem(polygon)
                
                # 计算瓦片实际位置
                if y % 2 == 0:
                    pos_x = self.tile_h * 2 * x
                else:
                    pos_x = self.tile_h + self.tile_h * 2 * x
                pos_y = 1.5 * self.tile_r * y
                tile_item.setPos(pos_x, pos_y)

                # 设置纹理画刷,绑定到瓦片局部坐标
                brush = QBrush()
                brush.setTexture(self.texture_pixmap)
                # 纹理平移对齐瓦片左上角,缩放适配瓦片大小
                brush_transform = QTransform()
                brush_transform.translate(-self.tile_h, -self.tile_r)
                # 如果纹理大小和瓦片不匹配,打开下面这行调整缩放比例
                # brush_transform.scale(self.tile_h*2 / self.texture_pixmap.width(), self.tile_r*2 / self.texture_pixmap.height())
                brush.setTransform(brush_transform)
                tile_item.setBrush(brush)
                # 加个边框方便看边界,不需要可以删掉
                tile_item.setPen(Qt.black)

                self.board_group.addToGroup(tile_item)
        
        # 初始化相机位置
        self.board_group.setPos(self.camera_x, self.camera_y)
    
    def update_camera(self):
        """每帧仅更新相机偏移,性能开销极低"""
        self.board_group.setPos(self.camera_x, self.camera_y)
    
    def keyPressEvent(self, event):
        key = event.key()
        if key == Qt.Key_A:
            self.camera_x -= 5
        elif key == Qt.Key_D:
            self.camera_x += 5
        elif key == Qt.Key_W:
            self.camera_y -= 5
        elif key == Qt.Key_S:
            self.camera_y += 5


class Tile():
    def __init__(self):
        pass

class Board():
    def __init__(self, width, height):
        self.height = height
        self.width = width
        self.tiles = [[Tile() for _ in range(height)] for _ in range(width)]


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    sys.exit(app.exec_())

方案2:全局纹理平铺(高性能,适合统一纹理场景)

如果所有瓦片用同一张纹理平铺,不需要单瓦独立纹理,可以用这个方案,性能更高:

  • 先创建一个和棋盘大小一致的QPixmap作为画布
  • 用QPainter先把所有六边形瓦片的形状绘制到画布上作为遮罩
  • 开启QPainter的组合模式CompositionMode_SourceIn,再把纹理平铺绘制到画布上,自动只会保留六边形区域内的纹理
  • 把绘制好的整块棋盘纹理做成一个QGraphicsPixmapItem,移动的时候直接改这个item的位置即可
  • 缺点是如果要做单瓦片的点击变色、高亮之类的交互,需要额外处理遮罩,灵活性不如方案1

适配说明

后续如果要更换瓦片形状,只需要修改init_tiles方法里的relative_points顶点列表,换成正方形、透视六边形的相对坐标即可,纹理逻辑不需要做任何修改,自动适配新形状。

内容的提问来源于stack exchange,提问作者Samittaja

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 13:54:21