项目需求和用什么库是我提出来的,代码除非是越改越错的情况下,否则我不出手,这是AI最后写出来的代码
需求看截图就能看出来,可以拖动、具有物理的小人
import sys
import math
import socket
import struct
from PyQt5.QtWidgets import QApplication, QWidget, QDesktopWidget, QMenu, QAction
from PyQt5.QtCore import Qt, QPoint, QTimer, QThread, pyqtSignal
from PyQt5.QtGui import QPainter, QPen, QColor, QBrush, QMouseEvent, QCursor
import pymunk
class SocketThread(QThread):
"""套接字监听线程"""
create_person_signal = pyqtSignal()
def __init__(self, port=65433):
super().__init__()
self.port = port
self.socket = None
self.running = True
def run(self):
try:
while self.running:
try:
# 接收数据
data, addr = self.socket.recvfrom(1024)
print(data)
if data.startswith(b'create_person'):
self.create_person_signal.emit()
except Exception as e:
pass
except Exception as e:
pass
finally:
if self.socket:
self.socket.close()
def stop(self):
self.running = False
# 发送一个数据包来唤醒阻塞的recvfrom
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(b'quit', ('127.0.0.1', self.port))
sock.close()
except:
pass
self.wait()
class LimbWidget(QWidget):
"""小人身体部位的窗口"""
def __init__(self, space, body, shape, parent=None):
super().__init__(parent)
self.space = space
self.body = body
self.shape = shape
# 设置窗口属性
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool)
self.setAttribute(Qt.WA_TranslucentBackground)
# 根据形状类型设置固定尺寸
if isinstance(self.shape, pymunk.Circle):
# 圆形物体(头部)
self.radius = int(self.shape.radius)
# 为圆形物体设置固定尺寸
size = self.radius * 2 + 20 # 增加边距
self.fixed_width = size
self.fixed_height = size
else:
# 矩形物体(身体和四肢)
self.rect_width = int(self.shape.bb.right - self.shape.bb.left)
self.rect_height = int(self.shape.bb.top - self.shape.bb.bottom)
# 为旋转预留固定空间
diagonal = int(math.sqrt(self.rect_width*self.rect_width + self.rect_height*self.rect_height))
self.fixed_width = diagonal + 20
self.fixed_height = diagonal + 20
self.setGeometry(0, 0, self.fixed_width, self.fixed_height)
self.show()
# 鼠标拖拽相关
self.dragging = False
self.mouse_joint = None
self.mouse_body = None
def update_position(self):
"""更新窗口位置"""
# 获取物体位置
pos = self.body.position
# 将物体中心对齐到窗口中心
self.move(int(pos.x - self.fixed_width / 2), int(pos.y - self.fixed_height / 2))
def paintEvent(self, event):
"""绘制肢体"""
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
# 设置绘制颜色
if isinstance(self.shape, pymunk.Circle): # 头部
painter.setBrush(QBrush(QColor(255, 220, 177)))
painter.setPen(QPen(QColor(200, 150, 100), 2))
# 绘制在窗口中心
painter.drawEllipse(int(self.fixed_width/2 - self.radius),
int(self.fixed_height/2 - self.radius),
self.radius*2, self.radius*2)
else: # 身体其他部分
painter.setBrush(QBrush(QColor(50, 150, 250)))
painter.setPen(QPen(QColor(30, 100, 200), 2))
# 保存当前变换
painter.save()
# 移动到窗口中心并应用旋转
center_x = self.fixed_width / 2
center_y = self.fixed_height / 2
painter.translate(center_x, center_y)
painter.rotate(math.degrees(self.body.angle))
# 在旋转中心绘制矩形(使用固定尺寸)
painter.drawRect(int(-self.rect_width/2), int(-self.rect_height/2), self.rect_width, self.rect_height)
# 恢复变换
painter.restore()
def mousePressEvent(self, event):
"""鼠标按下事件"""
if event.button() == Qt.LeftButton:
self.dragging = True
# 创建鼠标控制的静态物体
self.mouse_body = pymunk.Body(body_type=pymunk.Body.KINEMATIC)
# 获取鼠标点击位置(需要转换坐标系)
mouse_x = event.x() + self.x()
mouse_y = event.y() + self.y()
# 设置鼠标物体的位置
self.mouse_body.position = (mouse_x, mouse_y)
# 创建关节连接鼠标和被点击的物体
self.mouse_joint = pymunk.PivotJoint(
self.mouse_body, self.body,
(mouse_x, mouse_y)
)
self.mouse_joint.max_force = 200000 # 大幅增加拖动力量
self.space.add(self.mouse_joint)
def mouseMoveEvent(self, event):
"""鼠标移动事件"""
if self.dragging and self.mouse_joint:
# 更新鼠标位置(需要转换坐标系)
global_pos = self.mapToGlobal(event.pos())
if self.mouse_body:
self.mouse_body.position = (global_pos.x(), global_pos.y())
def mouseReleaseEvent(self, event):
"""鼠标释放事件"""
if event.button() == Qt.LeftButton and self.dragging:
self.dragging = False
# 移除鼠标关节
if self.mouse_joint:
self.space.remove(self.mouse_joint)
self.mouse_joint = None
# 不需要移除mouse_body,因为它没有被添加到空间中
self.mouse_body = None
class Person:
"""小人角色"""
def __init__(self, space, position):
self.space = space
self.position = position
self.limbs = []
self.widgets = []
self.create_person()
def create_person(self):
"""创建小人"""
# 创建身体部件
# 头部(靠近身体)
head_body = pymunk.Body(10, pymunk.moment_for_circle(10, 0, 15))
head_body.position = (self.position[0], self.position[1] - 45) # 靠近身体
head_shape = pymunk.Circle(head_body, 15)
head_shape.friction = 0.7
self.space.add(head_body, head_shape)
self.limbs.append((head_body, head_shape))
# 身体
body_body = pymunk.Body(10, pymunk.moment_for_box(10, (30, 50)))
body_body.position = self.position
body_shape = pymunk.Poly.create_box(body_body, (30, 50))
body_shape.friction = 0.7
self.space.add(body_body, body_shape)
self.limbs.append((body_body, body_shape))
# 左臂(肩膀位置)
l_arm_body = pymunk.Body(10, pymunk.moment_for_box(10, (8, 40)))
l_arm_body.position = (self.position[0] - 30, self.position[1] - 35) # 保持原来的高度,只增加水平距离
l_arm_shape = pymunk.Poly.create_box(l_arm_body, (8, 40))
l_arm_shape.friction = 0.7
self.space.add(l_arm_body, l_arm_shape)
self.limbs.append((l_arm_body, l_arm_shape))
# 右臂(肩膀位置)
r_arm_body = pymunk.Body(10, pymunk.moment_for_box(10, (8, 40)))
r_arm_body.position = (self.position[0] + 30, self.position[1] - 35) # 保持原来的高度,只增加水平距离
r_arm_shape = pymunk.Poly.create_box(r_arm_body, (8, 40))
r_arm_shape.friction = 0.7
self.space.add(r_arm_body, r_arm_shape)
self.limbs.append((r_arm_body, r_arm_shape))
# 左腿
l_leg_body = pymunk.Body(10, pymunk.moment_for_box(10, (10, 50)))
l_leg_body.position = (self.position[0] - 15, self.position[1] + 65)
l_leg_shape = pymunk.Poly.create_box(l_leg_body, (10, 50))
l_leg_shape.friction = 0.7
self.space.add(l_leg_body, l_leg_shape)
self.limbs.append((l_leg_body, l_leg_shape))
# 右腿
r_leg_body = pymunk.Body(10, pymunk.moment_for_box(10, (10, 50)))
r_leg_body.position = (self.position[0] + 15, self.position[1] + 65)
r_leg_shape = pymunk.Poly.create_box(r_leg_body, (10, 50))
r_leg_shape.friction = 0.7
self.space.add(r_leg_body, r_leg_shape)
self.limbs.append((r_leg_body, r_leg_shape))
# 连接身体各部分(更硬的连接,手臂连接点更远)
# 头和身体连接(更靠近)
head_constraint = pymunk.PinJoint(head_body, body_body, (0, 15), (0, -25))
self.space.add(head_constraint)
# 手臂和身体连接(连接点更远)
# 左臂连接(增加距离以防止碰撞)
l_arm_constraint = pymunk.SlideJoint(l_arm_body, body_body, (0, -20), (-20, -25), 0, 0) # 更远的连接点
# 限制左臂旋转角度
l_arm_motor = pymunk.RotaryLimitJoint(l_arm_body, body_body, -math.pi/1.5, math.pi/1.5)
# 添加关节到空间
self.space.add(l_arm_constraint, l_arm_motor)
# 右臂连接(增加距离以防止碰撞)
r_arm_constraint = pymunk.SlideJoint(r_arm_body, body_body, (0, -20), (20, -25), 0, 0) # 更远的连接点
# 限制右臂旋转角度
r_arm_motor = pymunk.RotaryLimitJoint(r_arm_body, body_body, -math.pi/1.5, math.pi/1.5)
# 添加关节到空间
self.space.add(r_arm_constraint, r_arm_motor)
# 腿和身体连接(连接点保持)
# 左腿连接(增加距离以防止碰撞)
l_leg_constraint = pymunk.SlideJoint(l_leg_body, body_body, (0, -25), (-15, 30), 0, 0) # 更远的连接点
# 限制左腿旋转角度
l_leg_motor = pymunk.RotaryLimitJoint(l_leg_body, body_body, -math.pi/1.5, math.pi/1.5)
# 添加关节到空间
self.space.add(l_leg_constraint, l_leg_motor)
# 右腿连接(增加距离以防止碰撞)
r_leg_constraint = pymunk.SlideJoint(r_leg_body, body_body, (0, -25), (15, 30), 0, 0) # 更远的连接点
# 限制右腿旋转角度
r_leg_motor = pymunk.RotaryLimitJoint(r_leg_body, body_body, -math.pi/1.5, math.pi/1.5)
# 添加关节到空间
self.space.add(r_leg_constraint, r_leg_motor)
def create_widgets(self):
"""创建显示部件"""
for body, shape in self.limbs: # 修复拼写错误:lims -> limbs
widget = LimbWidget(self.space, body, shape)
self.widgets.append(widget)
def update_widgets(self):
"""更新所有部件位置"""
for widget in self.widgets:
widget.update_position()
# 强制更新界面
widget.update()
class PhysicsSpace:
"""物理空间"""
def __init__(self):
self.space = pymunk.Space()
self.space.gravity = (0, 500) # 重力
self.create_boundaries() # 创建物理边界
self.persons = []
def create_boundaries(self):
"""创建更厚更硬的物理边界"""
desktop = QDesktopWidget()
screen_rect = desktop.availableGeometry()
width = screen_rect.width()
height = screen_rect.height()
x_offset = screen_rect.x()
y_offset = screen_rect.y()
# 创建更厚的屏幕边界(厚度为50像素)
wall_thickness = 50
# 创建屏幕边界,基于可用区域
static_lines = [
# 上边界
pymunk.Segment(self.space.static_body, (x_offset - wall_thickness, y_offset - wall_thickness),
(x_offset + width + wall_thickness, y_offset - wall_thickness), wall_thickness),
# 右边界
pymunk.Segment(self.space.static_body, (x_offset + width + wall_thickness, y_offset - wall_thickness),
(x_offset + width + wall_thickness, y_offset + height + wall_thickness), wall_thickness),
# 下边界
pymunk.Segment(self.space.static_body, (x_offset + width + wall_thickness, y_offset + height + wall_thickness),
(x_offset - wall_thickness, y_offset + height + wall_thickness), wall_thickness),
# 左边界
pymunk.Segment(self.space.static_body, (x_offset - wall_thickness, y_offset + height + wall_thickness),
(x_offset - wall_thickness, y_offset - wall_thickness), wall_thickness)
]
for line in static_lines:
line.friction = 1.0 # 增加摩擦力
line.elasticity = 0.1 # 减少弹性
line.collision_type = 1 # 设置碰撞类型
self.space.add(*static_lines)
def add_person(self, position):
"""添加小人"""
person = Person(self.space, position)
person.create_widgets() # 创建小人部件
self.persons.append(person)
return person
class MainWindow(QWidget):
"""主窗口,用于处理右键菜单添加小人"""
def __init__(self):
super().__init__()
self.space = PhysicsSpace()
self.sock = None
# 设置主窗口属性
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setGeometry(0, 0, 1, 1) # 设置一个非常小的窗口
# 创建定时器更新物理和界面
self.timer = QTimer()
self.timer.timeout.connect(self.update)
self.timer.start(16) # 约60 FPS
self.show()
# 添加一个默认小人
desktop = QDesktopWidget()
screen_rect = desktop.availableGeometry()
screen_center = (screen_rect.x() + screen_rect.width() // 2,
screen_rect.y() + screen_rect.height() // 2)
self.space.add_person(screen_center)
# 启动套接字监听线程
self.socket_thread = SocketThread()
self.socket_thread.create_person_signal.connect(self.add_person)
def update(self):
"""更新物理空间和所有小人"""
# 更新物理空间
self.space.space.step(1/60.0)
# 更新所有小人
for person in self.space.persons:
if hasattr(person, 'widgets') and person.widgets:
person.update_widgets()
def add_person(self):
"""在光标位置添加小人"""
# 添加一个小人
desktop = QDesktopWidget()
screen_rect = desktop.availableGeometry()
screen_center = (screen_rect.x() + screen_rect.width() // 2,
screen_rect.y() + screen_rect.height() // 2)
self.space.add_person(screen_center)
def add_person_at_point(self, pos):
"""在指定位置添加小人"""
self.space.add_person(pos)
def closeEvent(self, event):
"""窗口关闭事件"""
if self.socket_thread:
self.socket_thread.stop()
event.accept()
def main():
# 首先尝试绑定端口,检查是否已经有实例在运行
sock = None
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('127.0.0.1', 65433))
# 端口绑定成功,说明这是第一个实例,继续执行
except OSError:
# 端口绑定失败,说明已经有实例在运行,发送消息后退出
try:
# 获取鼠标位置
client_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 发送创建小人命令和坐标
data = b'create_person'
client_sock.sendto(data, ('127.0.0.1', 65433))
client_sock.close()
except Exception as e:
pass
sys.exit(0)
# 这是主实例,继续执行应用
app = QApplication(sys.argv)
# 创建主窗口
main_window = MainWindow()
# 将socket对象保存到窗口中,以便在窗口关闭时正确关闭
main_window.socket_thread.socket = sock
main_window.socket_thread.start()
exit_code = app.exec_()
# 应用退出时关闭socket
if hasattr(main_window, 'sock') and main_window.sock:
main_window.sock.close()
sys.exit(exit_code)
if __name__ == "__main__":
main()