新闻详情

新闻详情

首页 / 资讯中心 / 详情

Pygame游戏开发实战:口红机挑战中的碰撞检测与状态机设计

发布时间:2026/9/15 13:52:03来源:尧图网络
Pygame游戏开发实战:口红机挑战中的碰撞检测与状态机设计
简介一份基于Pygame开发的口红机口红挑战游戏完整源代码适合想学习2D游戏开发的Python初学者及有基础的程序员。游戏包含三关递增难度的口红插入挑战、60秒倒计时、碰撞检测和得分系统覆盖Pygame游戏结构、时间管理、关卡设计、碰撞检测、用户交互、图形绘制、声音效果与状态机管理等核心知识点。压缩包共19个文件以主程序py、说明文档md、字体ttf和png/jpg图片素材构成包体大小约7.85MB素材与代码分离便于替换口红、目标区等视觉资源或修改游戏界面。目前已有771人学习下载这套源码既有完整可运行的游戏逻辑又保留了清晰的模块划分可直接阅读代码理解2D游戏开发流程也可在此基础上扩展音效、增加关卡或移植到更多场景。1. 口红机挑战从线下玩法到Pygame实现的取舍Python口红机这个项目拆开看就是Pygame事件循环、矩形碰撞和状态机三件事。玩法很直白限时60秒把口红插进方格孔位填满三关的指定数量就算赢。商场里的实体机器靠机械结构判定插入精度Pygame版则退化成一系列colliderect矩形相交检测这个物理层面的简化恰好给了初学者一个观察游戏循环如何运转的窗口。代码量不大覆盖了2D游戏开发最常用的一批API事件队列、Surface渲染、字体加载、音效触发。对刚学会Python语法、想看看游戏主循环长什么样的新手以及想快速搭一套带计时和碰撞逻辑的交互Demo的开发者这份Python小游戏源代码都值得花一个下午拆一拆。2. Pygame初始化与游戏循环口红机的主流程拆解一个Pygame游戏跑起来绕不开四件事初始化、主循环、事件处理、渲染。口红机游戏把入口拆得比较清楚先看初始化部分它决定了窗口、资源和帧率基准后面的所有逻辑都建立在这一层之上。2.1 窗口初始化与全局配置set_mode、set_caption与FPS基准初始化要做的第一件事是向Pygame声明我要用哪些模块然后创建窗口。口红机项目里窗口尺寸、背景色、帧率这些参数通常放在文件头部方便全局修改。import pygame import sys # 全局配置窗口尺寸与帧率 SCREEN_WIDTH 640 SCREEN_HEIGHT 480 FPS 60 pygame.init() screen pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption(口红机挑战) clock pygame.time.Clock() # 加载口红与目标孔位图片png带透明通道 lipstick_img pygame.image.load(lipstick_one.png).convert_alpha() target_img pygame.image.load(lipstick_index.png).convert_alpha()set_mode传入元组返回的screen是所有blit绘制的目标Surface。Clock的作用是控制主循环节奏调用tick(FPS)让每帧耗时稳定在约16.7毫秒1000/60。需要注意图片加载后的convert_alpha()如果不加每帧绘制带透明通道的PNG时会做额外的像素格式转换帧率会明显下降尤其在目标孔位数变多以后这个开销会被碰撞检测的遍历放大从卡顿发展为肉眼可见掉帧。提示图片和脚本放同一目录时pygame.image.load用相对路径即可。如果画面出现白色方块或花屏优先检查图片路径是否正确或文件是否真的带透明通道。2.2 主循环与事件分发event.get与mouse.get_pos主循环的写法每个Pygame项目都类似先处理事件再更新逻辑最后渲染。口红机的主要交互是鼠标点击所以事件分支需要同时监听退出和鼠标按下两种情况。running True while running: # 固定帧率 clock.tick(FPS) # 事件处理 for event in pygame.event.get(): if event.type pygame.QUIT: running False elif event.type pygame.MOUSEBUTTONDOWN and event.button 1: # 左键点击交给口红插入逻辑 mouse_x, mouse_y pygame.mouse.get_pos() handle_insert(mouse_x, mouse_y) # 更新逻辑倒计时、碰撞检测、关卡切换 update_game_state() # 渲染 screen.fill((245, 222, 179)) render_game_elements(screen) pygame.display.flip()事件队列用pygame.event.get()一次性取完避免事件跨帧堆积。MOUSEBUTTONDOWN表示按下瞬间触发用它做按下即插入反馈更跟手如果想模拟实体机器那种按到底才生效可以换成MOUSEBUTTONUP两者的手感差异明显后续调整时值得对比。逻辑更新和渲染拆成两个函数调试时能单独注释掉渲染部分跑逻辑排查性能瓶颈会很顺手。update_game_state内部管理倒计时和碰撞render_game_elements只管往屏幕上画东西不修改任何游戏状态这种分层让代码结构清楚很多。2.3 游戏状态机菜单、进行中、过关与失败口红机不是单循环跑到底它在等待开始→游戏中→过关/失败→再来一局之间切换。用状态机管理代码比堆一摞if flag清晰得多后续加暂停、加结算动画都能直接扩。# 游戏状态常量定义 STATE_MENU 0 # 菜单/等待开始 STATE_PLAYING 1 # 游戏中 STATE_LEVEL_CLEAR 2 # 当前关卡完成 STATE_GAMEOVER 3 # 时间耗尽或失误超限 game_state STATE_MENU current_level 1 score 0 time_left 60 mistakes 0状态迁移的条件集中在一个函数里处理比散落在主循环各处容易维护。def update_game_state(): global game_state, current_level, time_left if game_state STATE_PLAYING: # 剩余时间每秒减1 time_left 60 - int((pygame.time.get_ticks() - start_ticks) / 1000) if time_left 0: game_state STATE_GAMEOVER elif check_level_complete(): game_state STATE_LEVEL_CLEAR elif game_state STATE_LEVEL_CLEAR: # 重新计时进入下一关 current_level 1 if current_level 3: game_state STATE_GAMEOVER # 或进入胜利界面 else: start_level(current_level) game_state STATE_PLAYINGstart_level内部需要重置倒计时起点、清空已插入位置列表并按关卡序号重新生成目标矩形。这个设计把状态迁移和具体玩法剥离开以后要加暂停状态只需在STATE_PLAYING旁加一个枚举值再在事件分支拦截暂停键。很多新手写游戏败在把所有判断堆进while循环分支一多就失控口红机用状态机做了一个比较标准的示范这也是这份源码在结构上最值得抄的部分。3. 插入判定与计时碰撞检测、倒计时与三关难度曲线口红机最核心的逻辑不是画面而是口红是否插进了目标孔位。它本质是2D矩形碰撞问题Pygame提供了colliderect但直接用会遇到三个问题重复位置判定、边缘误触、难度递增参数怎么调。3.1 矩形碰撞与去重判定colliderect的边界问题colliderect挂在pygame.Rect对象上接收另一个Rect返回布尔值。它判断的是两个矩形是否有重叠区域所以鼠标即使点在目标方形边缘只要矩形相交就算命中。def handle_insert(mouse_x, mouse_y): # 以鼠标位置为中心构造口红矩形 lipstick_rect pygame.Rect(mouse_x - 30, mouse_y - 30, 60, 60) # 遍历所有目标孔位 for idx, slot_rect in enumerate(slot_rects): if not lipstick_rect.colliderect(slot_rect): continue # 已经插过的孔位不能再插否则重复计分 if idx in inserted_slots: show_warning(此位置已插入口红) return inserted_slots.append(idx) score 10 play_hit_sound() breaklipstick_rect宽高取60像素对应口红图片在屏幕上的覆盖面积。colliderect是矩形可行性检测两个矩形只要有交点就算命中所以判定松紧是可控的。实际运行时直接用colliderect有一个体验问题目标方格尺寸偏小时玩家会明显感觉明明对准了却没反应。常见做法是对目标矩形做inflate(20, 20)外扩之后再参与检测等于把目标区域放大20像素手感更宽容反过来觉得太容易就缩小口红矩形的宽度高度。去重是区别于打地鼠类玩法的关键。inserted_slots用列表记录已命中的下标命中前先查一遍是否已存在否则同一个孔位会反复加分。这里用list就够孔位数量通常不超过十个如果目标数量非常多换成set能把查找复杂度从O(n)降到O(1)。提示Rect.colliderect对负坐标也有效鼠标拖出窗口不会报错。但口红机里最好在点击事件中加坐标边界判断鼠标在屏幕范围外时不触发插入逻辑避免出现隔空插中的错觉。3.2 倒计时实现get_ticks与dt帧间隔的取舍倒计时的原理不复杂记录关卡开始时的毫秒时间戳每帧用当前时间减开始时间除以1000得出已过秒数再用限时减去这个值。# 关卡开始时记录 start_ticks pygame.time.get_ticks() TIME_LIMIT 60 # 单关限时单位秒 # 每帧更新 def update_countdown(): global time_left elapsed_ms pygame.time.get_ticks() - start_ticks time_left TIME_LIMIT - int(elapsed_ms / 1000) if time_left 0: time_left 0 return time_left用get_ticks的好处是它基于系统时钟不受帧率波动影响。即便某一帧因为图片加载或音效解码卡顿倒计时也不会慢掉或快进这一点对计时类游戏至关重要。另一种常见做法是time_left - dt其中dt clock.tick(FPS) / 1000.0但帧率不稳时这种累减会累积误差。定时需求我基本都用get_ticks只有位移计算才用dt。显示层需要注意time_left是整数秒渲染到画面上会呈现每秒跳一次的效果。如果要做倒计时进度条而不是纯数字就保留浮点剩余时间TIME_LIMIT - elapsed_ms / 1000.0用它计算进度条宽度能实现毫秒级连续流动数字显示仍然用整数版。3.3 三关难度参数目标数量、失误上限与目标区域尺寸三关不能是同一个玩法换皮。源码的难度递进主要靠三个可调维度目标孔位数量、失误容忍次数、目标方格边长。下面是根据代码整理出的参数表。关卡目标孔位数限时秒失误上限目标方格边长像素第1关460590第2关660375第3关860260目标孔位数递增意味着同样时间内要完成更多次插入方格边长缩小直接压缩了colliderect的命中面积失误上限递减对每次点击准确率提出更高要求。三者组合就是完整难度曲线。LEVEL_CONFIG { 1: {slots: 4, time_limit: 60, max_mistakes: 5, slot_size: 90}, 2: {slots: 6, time_limit: 60, max_mistakes: 3, slot_size: 75}, 3: {slots: 8, time_limit: 60, max_mistakes: 2, slot_size: 60}, } def start_level(level): cfg LEVEL_CONFIG[level] global time_limit, max_mistakes, slot_rects, inserted_slots time_limit cfg[time_limit] max_mistakes cfg[max_mistakes] inserted_slots [] # 清空本关已插入记录 slot_rects generate_slot_positions(cfg[slots], cfg[slot_size])generate_slot_positions负责在640x480的窗口里生成不重叠的Rect列表。常见做法是把屏幕分成网格在格子中心放置Rect生成后需要做一次去重检查任意两个Rect的间距不小于方块边长加一个缓冲值否则相邻孔位会被colliderect误判。失误统计规则需要单独定义点击空白区域算一次失误重复点击已插入孔位只提示不扣分。从源码行为看这个规则对体验影响很大——如果重复点击也算失误玩家的试错成本会高到让人烦躁。调难度时优先动孔位数和方格边长失误上限别低于2鼠标操作的精度终归有限。4. 视觉渲染与音效Surface绘制、中文字体与mixer音频逻辑跑通只是第一步界面如果只是几个黑白矩形玩家感受不到口红机的质感。这一部分处理渲染层的三个要点图片正确贴屏、中文不显示成方块字、音效在关键帧触发不卡顿。4.1 图片加载与缩放convert_alpha、transform.scale与屏幕适配口红机的图片资源分两组口红图片和带孔位的背景图。图片在初始化阶段加载成Surface绘制通过blit完成其中SRCALPHA的用法很关键。def render_game_elements(screen): # 绘制背景含孔位 screen.blit(background_img, (0, 0)) # 绘制所有目标孔位的高亮遮罩 for idx, rect in enumerate(slot_rects): if idx in inserted_slots: # 已插入的孔位变灰创建带透明通道的临时Surface gray pygame.Surface(rect.size, pygame.SRCALPHA) gray.fill((128, 128, 128, 100)) screen.blit(gray, rect) # 绘制跟随鼠标移动的口红 mouse_x, mouse_y pygame.mouse.get_pos() screen.blit(lipstick_img, (mouse_x - lipstick_img.get_width() // 2, mouse_y - lipstick_img.get_height() // 2))核心是用pygame.Surface(rect.size, pygame.SRCALPHA)创建一个带透明通道的临时Surface再填充半透明灰色以此实现已插入区域变暗的视觉反馈。blit的第二个参数是左上角坐标减掉图片宽高的一半让口红中心对齐鼠标位置手感更接近真实世界的拿取。transform.scale是高频接口。口红原始图片分辨率可能很大直接blit会超出窗口正确做法是按比例缩放后再用# 将口红缩放到50x50 scaled_lipstick pygame.transform.scale(lipstick_img, (50, 50)) # 缩放放在初始化阶段做一次不要放在每帧渲染里缩放做一次并缓存结果每帧直接使用避免重复计算开销。如果背景图和口红缩放的尺寸比例不协调优先以背景孔位的实际大小反推口红应缩放的尺寸保证视觉上放得进去。4.2 中文文字渲染pygame.font与楷体GB2312.ttf的加载顺序Pygame默认字体不支持中文直接SysFont(SimHei, 24)在Windows上可能显示正常换到Linux就变成方框。源码里带了一个楷体GB2312.ttf用pygame.font.Font显式加载这个做法在跨平台时最稳。def load_font(size): try: font pygame.font.Font(楷体GB2312.ttf, size) except FileNotFoundError: # 字体文件缺失时的降级方案 font pygame.font.Font(None, size) return font def draw_text(screen, text, size, color, x, y): font load_font(size) text_surface font.render(text, True, color) text_rect text_surface.get_rect() text_rect.topleft (x, y) screen.blit(text_surface, text_rect)render的第二个参数True开启抗锯齿字体边缘更平滑。get_rect()拿到文本Surface的矩形设topleft靠左上角排版设center做居中。每次draw_text都调用load_font会重复创建字体对象性能浪费实际项目里应该把字体对象按size缓存例如用一个全局字典记录已创建的pygame.font.Font实例。字体缺失时降级到Font(None, size)Pygame默认字体对中文支持差但至少不会崩溃。这种fail-open策略在资源文件不齐的机器上很有用——另一位开发者从GitHub拉代码后发现ttf没有被一起提交程序还能运行只是在弹窗里把缺失情况如实暴露出来。4.3 音效触发与混音控制mixer初始化时机与音量调节音效在口红机里戏份不重但成败在咔哒那一声插进去一声点空另一声。mixer模块在pygame.init()时会跟随初始化独立使用时需要显式调pygame.mixer.init()。pygame.mixer.init(frequency44100, size-16, channels2, buffer512) try: hit_sound pygame.mixer.Sound(hit.wav) miss_sound pygame.mixer.Sound(miss.wav) except pygame.error: hit_sound None miss_sound None def play_hit_sound(): if hit_sound: hit_sound.set_volume(0.6) # 范围0.0~1.0 hit_sound.stop() # 截断上一轮播放避免爆音 hit_sound.play()frequency44100是CD音质标准size-16表示16位有符号整数采样buffer512决定了音频缓冲大小越小延迟越低但过小会增加卡顿风险512适合大多数机器。Sound.play()每次调用返回一个Channel对象同一音效在短时间连续触发多次会叠加播放听感上就是爆音。改进办法是先stop()再play()手动保证同一时刻每个音效最多只有一个实例在放。渲染这一节的每个环节单独看都不复杂组合顺序很讲究图片先缩放后blit字体先缓存后render音效先stop后play。这些习惯能把系统从能跑往前推一大步做出有点质感的完成度。5. 运行调试与打包帧率曲线、判定阈值与单文件分发最后处理三个真实运行中几乎必然遇到的问题帧率对移动速度的影响、矩形碰撞判定阈值怎么量化、把带资源的程序打包成双击可运行的文件。5.1 帧率与位移量的换算如果口红带移动过渡动画而不是瞬移帧率就决定速度参数的直观含义。clock.tick(60)时每帧约16.7ms某帧耗时超过33ms按帧计算的位移量就会让整体速度变慢。正确做法是引入dt clock.tick(FPS) / 1000.0位移量乘以dt速度单位变成像素/秒而不是像素/帧。5.2 判定阈值量化从矩形碰撞到圆心距离colliderect返回布尔值不好观察差多少才没中。调试时可以临时打印鼠标落点与最近孔位中心的欧氏距离import math def nearest_slot_distance(mouse_x, mouse_y): min_dist float(inf) for rect in slot_rects: cx, cy rect.centerx, rect.centery dist math.hypot(mouse_x - cx, mouse_y - cy) min_dist min(min_dist, dist) return min_dist把矩形相交换成圆心距离小于阈值判定曲线会更平滑视觉上更接近插进去了。math.hypot对几十个孔位遍历的开销可忽略如果孔位上百且性能吃紧就先用colliderect粗筛再精细判定。5.3 PyInstaller打包与资源路径验证打包时真正的坑不在Python代码在资源文件路径。PyInstaller会解包到临时目录相对路径可能失效需要显式指定--add-datapyinstaller --onefile --noconsole \ --add-data lipstick_one.png;. \ --add-data lipstick_index.png;. \ --add-data 楷体GB2312.ttf;. \ lipstick_challenge_game.pyWindows下--add-data分隔符是分号Linux和macOS用冒号。--onefile把解释器、依赖库和资源全打进一个exe缺点是启动稍慢优势是分发方便。打包后首轮验证别直接发人先在同一台机器不同工作目录跑一遍看资源是否加载正常。程序里如果写的是pygame.image.load(lipstick_one.png)这种相对路径运行工作目录不对就会报错稳妥做法是用os.path.dirname(__file__)拼出资源绝对路径打包后也能定位到解包目录。三关全过、零失误是验证逻辑闭环的标准用例调试输出里时间归零前score累计到300且current_level越过3说明插入判定、计分和状态迁移都正常若倒计时异常跳变优先查start_ticks是否在每次start_level时被重置——这是计时类游戏里最容易反复踩的bug口红机源码里花十分钟就能把它定位到。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

语音识别本地部署:大模型时代的企业数据主权保卫战与落地指南 2026/9/15 14:40:08

语音识别本地部署:大模型时代的企业数据主权保卫战与落地指南

进入2026年,人工智能已经不再是停留在PPT上的概念,而是深度嵌入到了各行各业的业务流中。然而,伴随AI狂热而来的,是一场隐秘的企业危机——数据隐私泄露。频发的“内部会议录音上传云端被滥用”、“核心商业机密成为开源模型训练语…

阅读更多 →
SQL Server 1433端口弱口令引爆勒索病毒:从攻击链到应急防护全复盘 2026/9/15 14:40:08

SQL Server 1433端口弱口令引爆勒索病毒:从攻击链到应急防护全复盘

1. 事件复盘:一个端口引发的全盘危机1.1 攻击路径还原上周接到一个做制造业的朋友电话,声音都是抖的:公司ERP系统全部瘫痪,所有业务部门停摆,客户的订单数据、生产计划、财务对账表全被打不开了,桌面上多了…

阅读更多 →
语音识别本地部署:企业级AI转写的破局之路与实战解析 2026/9/15 14:40:08

语音识别本地部署:企业级AI转写的破局之路与实战解析

在人工智能技术爆发的今天,语音识别(ASR)已经从消费级的“尝鲜玩具”演变为企业生产力工具的基础设施。从会议记录、客服质检到医疗问诊、法律取证,语音数据正在以前所未有的速度转化为核心商业资产。然而,当企业真正准…

阅读更多 →
储能项目地图可视化:Leaflet实战避坑指南 2026/9/15 14:40:08

储能项目地图可视化:Leaflet实战避坑指南

1. 为什么储能项目非得用 Leaflet 而不是“更炫”的地图库? 去年帮一家省级新能源投资平台做项目资产看板时,我第一反应是上 Three.js 做 3D 地图大屏——毕竟客户提需求时反复强调“要震撼”“要能旋转”“要像科幻片里那样”。结果原型做完&#xff0…

阅读更多 →
HTTP安全响应头配置实战:CSP与X-Frame-Options防攻击指南 2026/9/15 14:40:08

HTTP安全响应头配置实战:CSP与X-Frame-Options防攻击指南

干了这么多年Web安全,我一直有个很深的感触:很多团队对HTTP安全响应头的重视程度,远远配不上它带来的防护价值。尤其是前阵子做了一次头部互联网厂商的站点安全评估,扫了一圈下来发现,居然连一些日活几千万的业务线&am…

阅读更多 →
精讲五大排序算法:冒泡、选择、插入、希尔与快排的原理与实战 2026/9/15 14:37:08

精讲五大排序算法:冒泡、选择、插入、希尔与快排的原理与实战

作为一个常年跟数据结构和算法打交道的开发者,我越来越觉得排序算法不只是一堆需要背下来的代码模板,它背后是一整套关于"怎么高效地整理数据"的思考方式。很多人学排序时容易陷入一种误区:看视频觉得懂了,合上书全忘了…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞