Today, I test one python script with pygame and pyqt6. The python script use classes and show particles. See the result:

This is the source code:
import sys
import random
import pygame
from pygame import Vector2
from PyQt6.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QHBoxLayout,
QSlider, QLabel, QSizePolicy
)
from PyQt6.QtCore import Qt, QTimer
from PyQt6.QtGui import QImage, QPainter
class Particle:
def __init__(self, pos, vel, color):
self.pos = Vector2(pos)
self.vel = Vector2(vel)
self.color = color
self.life = 255
def update(self, gravity):
self.vel.y += gravity
self.pos += self.vel
self.life -= 2
def draw(self, surf):
if self.life > 0:
pygame.draw.circle(
surf,
self.color,
(int(self.pos.x), int(self.pos.y)),
4
)
class PygameWidget(QWidget):
def __init__(self):
super().__init__()
pygame.init()
pygame.display.init()
self.w, self.h = 900, 600
self.surface = pygame.Surface((self.w, self.h))
self.particles = []
self.spawn_rate = 5
self.gravity = 0.1
self.setSizePolicy(
QSizePolicy.Policy.Expanding,
QSizePolicy.Policy.Expanding
)
self.timer = QTimer()
self.timer.timeout.connect(self.game_loop)
self.timer.start(16) # ~60 FPS
def spawn_particles(self):
for _ in range(self.spawn_rate):
pos = (self.w // 2, self.h // 2)
vel = (random.uniform(-2, 2), random.uniform(-2, 2))
color = (255, random.randint(100, 255), 0)
self.particles.append(Particle(pos, vel, color))
def game_loop(self):
self.surface.fill((20, 20, 20))
self.spawn_particles()
alive = []
for p in self.particles:
p.update(self.gravity)
p.draw(self.surface)
if p.life > 0:
alive.append(p)
self.particles = alive
self.update()
def paintEvent(self, event):
data = pygame.image.tobytes(self.surface, "RGB")
img = QImage(
data,
self.w,
self.h,
self.w * 3,
QImage.Format.Format_RGB888
)
painter = QPainter(self)
painter.drawImage(0, 0, img)
painter.end()
def resizeEvent(self, event):
self.w = self.width()
self.h = self.height()
self.surface = pygame.Surface((self.w, self.h))
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("PyQt6 + pygame Particle System")
main_layout = QVBoxLayout()
# widget-ul pygame – ocupă tot spațiul
self.pg_widget = PygameWidget()
main_layout.addWidget(self.pg_widget, stretch=1)
# panou de controale jos
control_panel = QVBoxLayout()
# ----- slider spawn rate -----
spawn_layout = QHBoxLayout()
spawn_label = QLabel("Spawn:")
self.spawn_value = QLabel("5")
spawn_slider = QSlider(Qt.Orientation.Horizontal)
spawn_slider.setRange(1, 50)
spawn_slider.setValue(5)
spawn_slider.valueChanged.connect(self.update_spawn_rate)
spawn_layout.addWidget(spawn_label)
spawn_layout.addWidget(spawn_slider)
spawn_layout.addWidget(self.spawn_value)
# ----- slider gravity -----
gravity_layout = QHBoxLayout()
gravity_label = QLabel("Gravity:")
self.gravity_value = QLabel("0.10")
gravity_slider = QSlider(Qt.Orientation.Horizontal)
gravity_slider.setRange(0, 50)
gravity_slider.setValue(10)
gravity_slider.valueChanged.connect(self.update_gravity)
gravity_layout.addWidget(gravity_label)
gravity_layout.addWidget(gravity_slider)
gravity_layout.addWidget(self.gravity_value)
control_panel.addLayout(spawn_layout)
control_panel.addLayout(gravity_layout)
main_layout.addLayout(control_panel)
self.setLayout(main_layout)
def update_spawn_rate(self, v):
self.pg_widget.spawn_rate = v
self.spawn_value.setText(str(v))
def update_gravity(self, v):
g = v / 100.0
self.pg_widget.gravity = g
self.gravity_value.setText(f"{g:.2f}")
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MainWindow()
w.resize(1000, 800)
w.show()
sys.exit(app.exec())