吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 66|回复: 0
上一主题 下一主题
收起左侧

[Python 原创] 【AI辅助编译】【开源分享】【桌宠】四百行代码实现-会生气的桌面宠物

[复制链接]
跳转到指定楼层
楼主
iuyang 发表于 2026-4-17 17:51 回帖奖励
本帖最后由 iuyang 于 2026-4-17 17:56 编辑

一、 桌宠简介
这小东西源自某社畜(就是我)对鼠标指针的突发恶意:D
AI工具为Gemini 3 Pro简简单单400行代码,一个拥有物理属性情绪系统的“伪指针”。它会伪装成你的系统光标在屏幕上游荡。
假如你试图挑逗它(指拖着它让它在屏幕边缘上撞),它会逐渐红温,红温到某个阈值时,它和你玩一场你追我赶的互动小游戏。
当你被追上时:

是的,它会把你的鼠标指针撞的稀巴烂,但如图所示,这个粉碎的过程是可逆的:D
二、 核心功能与特色
  • 项目启动最初:它会自动读取你的 Windows 注册表,实时抓取系统当前正在使用的系统光标皮肤,尽量和你目前的鼠标指针风格一致。
  • 运动物理模拟:超级简单的物理引擎,支持惯性滑动、摩擦力衰减以及带回弹效果的屏幕边界检测,让小指针变得QQ弹弹的。
  • 主要玩法
    • 游荡/拖拽:期初,它会在桌面随机晃荡,你也可以通过左键按住拖拽并利用加速度将其“甩”出去,看着它在屏幕边缘反复弹跳。
    • 愤怒机制:频繁的碰撞和拖拽会积攒愤怒值,外观随之逐渐变红。
    • 追逐与晕眩:极度愤怒后会进入晕眩状态,随后会对你的真实鼠标指针发起高速冲撞,追逐过程中愤怒值会快速降低,直至归零,恢复往常。
  • 那要是被追上了呢?
  • 如上图所示,当伪指针撞烂真实鼠标后,会触发全屏重构特效,一层无形的窗口会隐藏并锁定你的鼠标,此时你的系统指针彻底不能使用,且不可见。
    • 此时只需要狂按鼠标,就能让碎片聚集到你鼠标爆炸的区域,完成重构。
    • 难度进阶:每次重构/逃脱成功后,宠物的追逐速度都会永久提升。

三、 附源码(python)
[Python] 纯文本查看 复制代码
import os
import sys
import random
import math
import io
import time
import ctypes
import winreg  # 用于读取 Windows 注册表

from PIL import Image
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtCore import Qt, QTimer, QPointF
from PyQt5.QtGui import QPainter, QColor, QImage, QPixmap, QCursor, QFont


# ================== 系统 API 封装 ==================
def set_cursor_pos(x, y):
    """强制设置系统真实鼠标的位置(用于重生阶段死锁鼠标)"""
    ctypes.windll.user32.SetCursorPos(int(x), int(y))


def show_system_cursor(show=True):
    """显示或隐藏系统真实鼠标光标"""
    # ShowCursor 是基于计数器的,必须循环调用直到状态符合预期
    if show:
        while ctypes.windll.user32.ShowCursor(True) < 0: pass
    else:
        while ctypes.windll.user32.ShowCursor(False) >= 0: pass


# ================== 注册表路径获取 ==================
def get_system_cursor_path():
    """从注册表获取用户当前正在使用的系统指针路径"""
    try:
        # 打开用户指针配置的注册表项
        key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Control Panel\Cursors")
        # 精准获取 "Arrow" (标准选择) 的路径
        value, reg_type = winreg.QueryValueEx(key, "Arrow")
        winreg.CloseKey(key)

        if isinstance(value, str):
            # 展开路径中的环境变量(例如将 %SystemRoot% 替换为 C:\Windows)
            real_path = os.path.expandvars(value)
            if os.path.exists(real_path):
                return real_path
    except Exception as e:
        print(f"获取注册表指针失败: {e}")

    # 路径获取失败时的默认备选方案
    fallbacks = [r"C:\Windows\Cursors\arrow.cur", r"C:\Windows\Cursors\arrow_m.cur"]
    for path in fallbacks:
        if os.path.exists(path): return path
    return ""


# ================== 资源处理 ==================
def load_cursor_pixmap(cursor_path: str, scale: float = 0.2) -> QPixmap:
    """加载 .cur 文件并转换为 PyQt 可用的贴图,同时处理尺寸缩放和去黑底"""
    if not cursor_path or not os.path.exists(cursor_path):
        # 路径无效时返回一个透明占位符
        pix = QPixmap(32, 32);
        pix.fill(Qt.transparent);
        return pix
    try:
        img = Image.open(cursor_path).convert("RGBA")
        # 采用 1.32 系数来适配系统 DPI 视觉大小
        w = img.size[0] * 1.32
        h = img.size[1] * 1.32

        # 缩放至程序设定的显示比例
        filt = getattr(Image, 'Resampling', Image).LANCZOS
        img = img.resize((int(w * scale), int(h * scale)), resample=filt)

        # 算法去黑底:将接近纯黑的像素转为全透明,防止出现黑边
        data = img.getdata()
        new = [(0, 0, 0, 0) if r < 10 and g < 10 and b < 10 else (r, g, b, a) for (r, g, b, a) in data]
        img.putdata(new)

        # 将内存中的 Pillow 图像转为 QPixmap
        buf = io.BytesIO()
        img.save(buf, format='PNG')
        return QPixmap.fromImage(QImage.fromData(buf.getvalue(), 'PNG'))
    except:
        pix = QPixmap(32, 32);
        pix.fill(Qt.transparent);
        return pix


# ================== 核心特效:重生覆盖层 ==================
class RebirthOverlay(QWidget):
    """当鼠标被“撞碎”后,覆盖全屏的点击重生互动层"""

    def __init__(self, death_pos, on_finish_callback):
        super().__init__()
        # 设置:无边框、最顶层、工具窗口(不显示在任务栏)
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool)
        self.setAttribute(Qt.WA_TranslucentBackground)  # 背景透明
        self.setGeometry(QApplication.primaryScreen().geometry())  # 全屏

        self.death_pos = death_pos  # 爆炸发生的位置
        self.on_finish = on_finish_callback  # 完成后的回调函数

        self.progress = 0.0  # 重构进度 (0-100)
        self.shake_val = 30.0  # 屏幕震动强度
        self.particles = []  # 粒子列表
        self.phase = 0  # 阶段:0爆炸, 1落地聚集, 2点击上升, 3最终重构

        # 初始化 100 个粒子并赋予随机初速度
        for _ in range(100):
            angle = random.uniform(0, 2 * math.pi)
            speed = random.uniform(10, 40)
            self.particles.append({
                'pos': QPointF(death_pos),
                'vel': QPointF(math.cos(angle) * speed, math.sin(angle) * speed),
                'size': random.uniform(1, 3),
                'drift': random.uniform(0.6, 1.4),  # 个体差异系数
                'landed': False, 'arrived': False
            })

        self.timer = QTimer(self)
        self.timer.timeout.connect(self.physics_tick)
        self.timer.start(16)  # 约 60 帧

        # 强制拦截全局输入
        self.setFocusPolicy(Qt.StrongFocus);
        self.setFocus()
        self.grabMouse()  # 抢夺鼠标所有权,此时只有本窗口能响应点击
        show_system_cursor(False)  # 隐藏真实鼠标

    def mousePressEvent(self, e):
        """处理点击重生的交互"""
        if e.button() == Qt.LeftButton:
            self.shake_val = 15.0  # 点击震动
            if self.phase >= 1:
                self.progress += 3.0  # 增加进度
                for p in self.particles:
                    if self.phase == 1:  # 聚集阶段:向爆炸点 X 轴靠拢
                        p['vel'].setX(p['vel'].x() + (self.death_pos.x() - p['pos'].x()) * 0.15)
                    elif self.phase == 2:  # 上升阶段:未到达高度的粒子获得向上冲力
                        if not p['arrived']:
                            p['vel'].setY(p['vel'].y() - random.uniform(20, 38) * p['drift'])
            if self.progress >= 100: self.complete()

    def physics_tick(self):
        """重生层的物理模拟逻辑"""
        # 关键:每一帧都把真实鼠标死锁在原地,防止玩家乱动
        set_cursor_pos(self.death_pos.x(), self.death_pos.y())

        screen_h = self.height()
        gravity, friction = 1.0, 0.94  # 重力与摩擦力系数

        # 自动切换阶段判定
        if self.phase == 1 and self.progress >= 30:
            self.phase = 2
        elif self.phase == 2:
            arrived_count = sum(1 for p in self.particles if p.get('arrived', False))
            if self.progress >= 85 or arrived_count > 90: self.phase = 3
            if not (QApplication.mouseButtons() & Qt.LeftButton):
                self.progress = max(30.1, self.progress - 0.25)  # 不点击进度会略微下降

        for p in self.particles:
            if not p.get('arrived', False):
                p['pos'] += p['vel'];
                p['vel'] *= friction  # 运动更新

            if self.phase == 0:  # 爆炸溅射
                p['vel'].setY(p['vel'].y() + gravity)
                if p['pos'].y() > screen_h - 20:  # 碰到屏幕底部
                    p['pos'].setY(screen_h - 20);
                    p['vel'] = QPointF(0, 0);
                    p['landed'] = True
                if all(pt.get('landed', False) for pt in self.particles): self.phase = 1

            elif self.phase == 1:  # 地面蠕动
                p['pos'].setY(screen_h - 20 + math.sin(time.time() * 12) * 1.2)

            elif self.phase == 2:  # 点击上升
                if not p.get('arrived', False):
                    p['vel'].setY(p['vel'].y() + gravity)
                    if p['pos'].y() > screen_h - 15: p['pos'].setY(screen_h - 15)  # 地板限制
                    if p['pos'].y() <= self.death_pos.y():  # 核心修正:到达爆炸点即停止
                        p['pos'].setY(self.death_pos.y());
                        p['vel'] = QPointF(0, 0);
                        p['arrived'] = True
                else:
                    p['pos'] += (self.death_pos - p['pos']) * 0.1  # 到达后的吸附微调

            elif self.phase == 3:  # 螺旋聚合
                angle = time.time() * 40 + random.random()
                dist = 20 * (1.1 - self.progress / 100.0)
                target = QPointF(self.death_pos.x() + math.cos(angle) * dist * 4,
                                 self.death_pos.y() + math.sin(angle) * dist * 4)
                p['pos'] += (target - p['pos']) * 0.4  # 快速向中心坍缩

        if self.shake_val > 0.1: self.shake_val *= 0.9
        self.update()

    def complete(self):
        """重构完成,释放所有限制"""
        self.timer.stop();
        self.releaseMouse();
        show_system_cursor(True)
        self.on_finish();
        self.close()

    def paintEvent(self, e):
        """绘制粒子和进度文字"""
        p = QPainter(self);
        p.setRenderHint(QPainter.Antialiasing)
        if self.shake_val > 0.5:  # 处理屏幕震动位移
            p.translate(random.uniform(-self.shake_val, self.shake_val),
                        random.uniform(-self.shake_val, self.shake_val))

        # 极其透明的黑色覆盖,确保捕获所有点击
        p.fillRect(self.rect(), QColor(0, 0, 0, 1))

        p.setBrush(QColor(255, 255, 255, 240));
        p.setPen(Qt.NoPen)
        for pt in self.particles: p.drawEllipse(pt['pos'], pt['size'], pt['size'])

        if self.phase >= 1:  # 绘制引导文字
            p.setFont(QFont("Microsoft YaHei", 24, QFont.Bold));
            p.setPen(QColor(255, 255, 255, 220))
            txt = "点击左键聚集..." if self.phase == 1 else "快速点击左键,重构指针!"
            if self.phase == 3: txt = "汇聚中..."
            p.drawText(self.rect(), Qt.AlignCenter, f"{txt}\n{int(self.progress)}%")


# ================== 核心逻辑:伪指针宠物 ==================
class FakeCursor(QWidget):
    def __init__(self, cursor_path):
        super().__init__()
        self.cursor_pixmap = load_cursor_pixmap(cursor_path, 0.2)
        # 窗口属性:无边框、顶层、透明
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.Tool | Qt.WindowStaysOnTopHint)
        self.setAttribute(Qt.WA_TranslucentBackground)
        self.resize(self.cursor_pixmap.width(), self.cursor_pixmap.height())

        self.pos_f = QPointF(QCursor.pos())  # 当前坐标(浮点型以保证平滑)
        self.velocity = QPointF(0, 0)  # 速度向量
        self.dragging = False  # 是否正被拖拽
        self.m_last = QPointF(0, 0)  # 上一帧鼠标位置(用于计算拖拽速度)
        self.bounced_from_drag = False  # 标记是否是从拖拽状态甩出的

        self.state = "IDLE"  # 状态:IDLE(游荡), CHASING(追逐), STUNNED(晕眩), REBIRTH(重生)
        self.anger = 0.0  # 愤怒值
        self.anger_max = 200.0  # 愤怒阈值
        self.difficulty = 0  # 难度(随重生次数增加追逐速度)
        self.stun_timer = 0  # 晕眩计时

        self.main_timer = QTimer();
        self.main_timer.timeout.connect(self.game_loop);
        self.main_timer.start(16)

    def game_loop(self):
        """主逻辑循环(状态机)"""
        dt = 0.016;
        real_p = QPointF(QCursor.pos())

        if self.state == "STUNNED":  # 晕眩状态:红闪并原地不动
            self.stun_timer += dt
            if self.stun_timer > 2.0: self.state = "CHASING"; self.stun_timer = 0
            self.update();
            return

        if self.state == "CHASING":  # 追逐状态:高速撞向真实鼠标
            vec = real_p - self.pos_f;
            dist = math.hypot(vec.x(), vec.y())
            if dist < 12:  # 发生碰撞:触发重构事件
                self.trigger_rebirth(real_p);
                return
            self.pos_f += (vec / dist) * (450 + self.difficulty * 45) * dt
            self.move_and_clamp();
            self.anger -= 12 * dt
            if self.anger <= 0: self.state = "IDLE"
            self.update();
            return

        if self.state == "REBIRTH":  # 重生状态:在背景自由游荡
            self.wander_logic(dt);
            self.check_bounce();
            self.move_and_clamp();
            return

        # IDLE 状态:自然游荡 + 处理愤怒值
        self.anger = max(0, self.anger - 15 * dt)
        if not self.dragging:
            self.wander_logic(dt)
            self.check_bounce()

        self.move_and_clamp()  # 执行位置更新及边界死锁
        self.update()

    def wander_logic(self, dt):
        """随机游荡逻辑"""
        self.pos_f += self.velocity * dt;
        self.velocity *= 0.95  # 惯性衰减
        if math.hypot(self.velocity.x(), self.velocity.y()) < 5 and random.random() < 0.04:
            # 随机选择新方向
            ang = random.uniform(0, 2 * math.pi);
            spd = random.uniform(300, 700)
            self.velocity = QPointF(spd * math.cos(ang), spd * math.sin(ang))

    def check_bounce(self):
        """边缘碰撞检测与反弹逻辑"""
        geom = QApplication.primaryScreen().availableGeometry();
        hit = False
        # 核心:碰撞时立即将坐标回弹 1 像素,防止陷在墙里
        if self.pos_f.x() <= geom.left():
            self.pos_f.setX(geom.left() + 1);
            self.velocity.setX(abs(self.velocity.x()));
            hit = True
        elif self.pos_f.x() >= geom.right() - self.width():
            self.pos_f.setX(geom.right() - self.width() - 1);
            self.velocity.setX(-abs(self.velocity.x()));
            hit = True

        if self.pos_f.y() <= geom.top():
            self.pos_f.setY(geom.top() + 1);
            self.velocity.setY(abs(self.velocity.y()));
            hit = True
        elif self.pos_f.y() >= geom.bottom() - self.height():
            self.pos_f.setY(geom.bottom() - self.height() - 1);
            self.velocity.setY(-abs(self.velocity.y()));
            hit = True

        # 如果是被玩家甩到墙上的,增加愤怒值
        if hit and self.bounced_from_drag:
            self.anger = min(self.anger_max, self.anger + 80)
            if self.anger >= self.anger_max: self.state = "STUNNED"; self.velocity = QPointF(0, 0)
            self.bounced_from_drag = False

    def move_and_clamp(self):
        """位置死锁:严禁伪指针超出屏幕"""
        geom = QApplication.primaryScreen().availableGeometry()
        x = max(geom.left(), min(self.pos_f.x(), geom.right() - self.width()))
        y = max(geom.top(), min(self.pos_f.y(), geom.bottom() - self.height()))
        self.pos_f = QPointF(x, y);
        self.move(self.pos_f.toPoint())

    def trigger_rebirth(self, pos):
        """启动重生特效"""
        self.state = "REBIRTH";
        self.anger = 0
        self.velocity = QPointF(random.uniform(-400, 400), random.uniform(-400, 400))
        self.ov = RebirthOverlay(pos, self.finish_rebirth)
        self.ov.show()

    def finish_rebirth(self):
        """重生特效完成后的回调"""
        self.state = "IDLE";
        self.difficulty += 1

    def mousePressEvent(self, e):
        """拖拽起始点"""
        if e.button() == Qt.LeftButton and self.state == "IDLE":
            self.dragging = True;
            self.m_last = QPointF(e.globalPos())

    def mouseMoveEvent(self, e):
        """处理拖拽过程中的位置更新"""
        if self.dragging:
            now = QPointF(e.globalPos());
            delta = now - self.m_last
            self.pos_f += delta
            # 将拖拽速度保留,用于释放后的“甩射”效果
            self.velocity = delta / 0.016
            self.m_last = now
            self.move_and_clamp()

    def mouseReleaseEvent(self, e):
        """拖拽释放"""
        if self.dragging: self.dragging = False; self.bounced_from_drag = True

    def paintEvent(self, e):
        """绘制伪指针及愤怒颜色叠加层"""
        p = QPainter(self);
        ratio = self.anger / self.anger_max
        # 准备一个带颜色的滤镜图层
        pix = QPixmap(self.cursor_pixmap.size());
        pix.fill(Qt.transparent);
        pp = QPainter(pix)
        pp.drawPixmap(0, 0, self.cursor_pixmap)
        pp.setCompositionMode(QPainter.CompositionMode_SourceAtop)  # 仅给非透明部分染色

        if self.state == "STUNNED":
            # 晕眩时剧烈红闪
            blink = int(130 + 125 * math.sin(time.time() * 35))
            pp.fillRect(pix.rect(), QColor(255, 0, 0, blink))
        else:
            # 平时根据愤怒值逐渐变红
            pp.fillRect(pix.rect(), QColor(255, 0, 0, int(210 * ratio)))
        pp.end();
        p.drawPixmap(0, 0, pix)


# ================== 程序入口 ==================
if __name__ == '__main__':
    app = QApplication(sys.argv)

    # 动态抓取当前 Windows 系统的光标皮肤
    system_cursor = get_system_cursor_path()
    print(f"正在加载系统指针: {system_cursor}")

    pet = FakeCursor(system_cursor)
    pet.show()

    sys.exit(app.exec_())



四、  pyinstaller打包.exe程序下载(蓝奏云)
https://iuyang.lanzouw.com/iOkHL3ng1d7e
密码:23q0

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-4-18 06:09

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表