20234120 2024-2025-2 《Python程序设计》实验四报告

20234120 2024-2025-2 《Python程序设计》实验四报告

课程:《Python程序设计》
班级: 2341班
姓名: 李卓颖
学号:20234120
实验教师:王志强
实验日期:2025年5月13日
必修/选修: 专业选修课

一、实验内容和要求

(一)实验内容

Python综合应用:爬虫、数据处理、可视化、机器学习、神经网络、游戏、网络安全等。
例如:编写从社交网络爬取数据,实现可视化舆情监控或者情感分析。
例如:利用公开数据集,开展图像分类、恶意软件检测等
例如:利用Python库,基于OCR技术实现自动化提取图片中数据,并填入excel中。
例如:爬取天气数据,实现自动化微信提醒
例如:利用爬虫,实现自动化下载网站视频、文件等。
例如:编写小游戏:坦克大战、贪吃蛇、扫雷等等
注:在Windows/Linux系统上使用VIM、PDB、IDLE、Pycharm等工具编程实现。
本次结课大作业选择了编写小游戏的方向,用python程序实现一个简单的汽车迷宫小游戏,这次是向有有相关经验的同学请教并且结合自己的更改来进行游戏的编写。

(二)实验要求

  • 1.程序能运行,功能丰富。(需求提交源代码,并建议录制程序运行的视频)
  • 2.综合实践报告,要体现实验分析、设计、实现过程、结果等信息,格式规范,逻辑清晰,结构合理。
  • 3.在实践报告中,需要对全课进行总结,并写课程感想体会、意见和建议等。

二、实验过程及结果

(一)编写程序

- 1.环境配置

在实验开始前,为了编写游戏小程序,需下载Pygame软件包。
Pygame是一个基于Python的开源库,专门用于开发2D游戏和交互式多媒体应用,具有跨平台特性,支持图形绘制、声音播放、用户输入处理等功能。

- 2.素材准备

在网络上查找相关图片素材和可能用到的音频素材并保存至文件夹。

  • 3.主程序代码分析
    (1)导入模块与初始化

    详细释义:
    config:配置文件,存储屏幕宽高、帧率等常量。
    GameManager:自定义的游戏逻辑控制器。
    draw_text:用于在屏幕上绘制文本。
    pygame.init() 和 pygame.mixer.init() 分别初始化所有 Pygame 模块和音频模块。
    (2)设置窗口
    设置屏幕窗口大小:

    创建游戏窗口和时钟对象,设置窗口图标,并将标题设置为“drawing.game”.

    (3)加载背景音乐并开始播放
    插入背景音乐,设置播放时机,并将音乐大小调整至20%。

    (4)初始化游戏管理器(后续继续详细解释)

    (5)主循环控制游戏流程
    主循环持续运行,直到用户点击关闭按钮或在胜利界面按下任意键即退出主循环,游戏结束。如果已经胜利 (success_finished == True),则检测按键事件来退出游戏。

    (6)胜利画面(MVP结算界面)
    将屏幕填充黑色背景,使用 draw_text 函数插入 "Win!" 文字,并设置居中显示。

    (7)游戏进行中的更新逻辑
    若成功通关(success_time 不为 -1),等待 2 秒后尝试进入下一关;如果没有下一关,则标记为全部通关 (success_finished = True)。
    否则调用 game_manager.update() 更新游戏状态,若返回值为 True 表示当前关卡完成,记录时间准备跳转。

    (8)完整代码如下:
点击查看代码
import pygame
import config
from game_manager import GameManager
from utils.draw_text import draw_text
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((config.SCREEN_WIDTH,config.SCREEN_HEIGTH))
clock = pygame.time.Clock()

ico = pygame.image.load("static/images/maze.png").convert()
pygame.display.set_icon(ico)

pygame.display.set_caption("drawing.game")

pygame.mixer.music.load("static/sounds/bgm.wav")
pygame.mixer.music.set_volume(0.2)
pygame.mixer.music.play(-1)

game_manager = GameManager(screen,1)

running = True
success_time = -1
success_finished = False
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif success_finished and event.type == pygame.KEYDOWN:
            running = False

    if success_finished:
        screen.fill("black")
        draw_text(screen,"Win!",200, config.SCREEN_WIDTH / 2, config.SCREEN_HEIGTH / 2)
        pygame.display.flip()
    else:
        if success_time >= 0:
            if pygame.time.get_ticks() - success_time > 2000:
                has_next = game_manager.next_level()
                if not has_next:
                    success_finished = True
                    continue
                success_time = -1

        screen.fill("black")
        if game_manager.update():
            success_time = pygame.time.get_ticks()

    pygame.display.flip()#屏幕更新
    clock.tick(config.FPS)

pygame.quit()

- 4.GameManager模块代码分析

GameManager 类是整个游戏的核心控制器,负责加载关卡、管理游戏对象(如玩家、墙壁、星星和目标)、处理碰撞检测以及控制游戏流程。
(1)初始化 (init)
screen:主窗口屏幕
level:当前游戏关卡,默认为第1关
player:玩家(即小车)
walls:墙壁 stars_cnt:关卡中星星的数量
stars:星星 target:皇冠

(2)加载墙壁 (load_walls)、星星 (load_stars)、目标皇冠 (load_targets)
清空现有的墙壁组、星星组、目标组,并根据给定的参数重新加载新的墙壁、根据给定的坐标重新加载新的星星、根据给定的坐标重新加载新的目标,即皇冠。

(3)加载玩家 (load_player)
清除已有玩家,然后根据给定的参数创建一个新的玩家对象,即小车。

(4)加载关卡数据 (load)
根据当前关卡号从文件读取关卡数据,并调用相应的方法加载墙壁、星星、皇冠和玩家。

(5)跳转下一关 (next_level)
增加关卡编号,如果下一关存在则加载之;否则返回 False 表示没有更多关卡,出现“WIN”界面。

(6)检查碰撞 (check_collide)
检测玩家与墙壁、星星和目标之间的碰撞,并执行相应的操作(如播放声音、减少星星计数等)。

(7)更新游戏状态 (update)
更新并绘制星星、目标、玩家和墙壁的状态,此处设置为1/60秒更新一次画面。
调用 check_collide 方法进行碰撞检测,返回是否成功完成当前关卡(即所有星星被收集且玩家到达目标)。

(8)完整代码如下:

点击查看代码
import pygame
from player import Player
from wall import Wall
from utils.collide import collided_rect, collided_circle
from star import Star
from target import Target
import os

class GameManager:
    def __init__(self, screen, level = 1):
        self.screen = screen
        self.level = level
        self.player = None
        self.walls = pygame.sprite.Group()
        self.stars_cnt = 0
        self.stars = pygame.sprite.Group()
        self.target = pygame.sprite.Group()
        self.load()
        self.eat_stars_sound = pygame.mixer.Sound("static/sounds/eat_stars.wav")
        self.eat_stars_sound.set_volume(0.5)
        self.success_sound = pygame.mixer.Sound("static/sounds/success.wav")
        self.success_sound.set_volume(0.5)

    def load_walls(self, walls):
        self.walls.empty()
        for x,y,width,height in walls:
            wall = Wall(x, y, width, height)
            wall.add(self.walls)

    def load_stars(self, stars):
        self.stars.empty()
        for x,y in stars:
            star = Star(x, y)
            star.add(self.stars)

    def load_targets(self, targets):
        self.target.empty()
        for x,y in targets:
            target = Target(x, y)
            target.add(self.target)

    def load_player(self, center_x, center_y, forward_angle):
        if self.player:
            self.player.kill()
        self.player = Player(center_x, center_y, forward_angle)

    def load(self):
        with open("static/maps/level%d.txt" % self.level, "r") as fin:
            walls_cnt = int(fin.readline())
            walls = []
            for i in range(walls_cnt):
                x, y, width, height = map(int,fin.readline().split())
                walls.append((x, y, width, height))
            self.load_walls(walls)
            self.stars_cnt = int(fin.readline())
            stars = []
            for i in range(self.stars_cnt):
                x, y = map(int,fin.readline().split())
                stars.append((x, y))
            self.load_stars(stars)
            targets_cnt = int(fin.readline())
            targets = []
            for i in range(targets_cnt):
                x, y = map(int,fin.readline().split())
                targets.append((x,y))
            self.load_targets(targets)
            center_x, center_y, forward_angle = map(int,fin.readline().split())
            self.load_player(center_x, center_y, forward_angle)

    def next_level(self):
        self.level += 1
        if not os.path.isfile("static/maps/level%d.txt" % self.level):
            return False
        self.load()
        return True

    def check_collide(self):
        if pygame.sprite.spritecollide(self.player, self.walls, False, collided_rect):
            self.player.crash()
        if pygame.sprite.spritecollide(self.player,self.stars,True, collided_circle):
            self.stars_cnt -= 1
            self.eat_stars_sound.play()
        if self.stars_cnt == 0:
            if pygame.sprite.spritecollide(self.player,self.target,True,collided_circle):
                self.success_sound.play()
                return True
        return False

    def update(self):
        self.stars.update()
        self.stars.draw(self.screen)
        self.target.update()
        self.target.draw(self.screen)
        self.player.update()
        success = self.check_collide()
        self.screen.blit(self.player.image, self.player.rect)

        self.walls.update()
        self.walls.draw(self.screen)
        return success

- 5.其他模块设置

  • (1)player模块
点击查看代码
import pygame
import config
import math

class Player(pygame.sprite.Sprite):
    def __init__(self, center_x, center_y, forward_angle):
        super().__init__()
        self.width = 100
        self.height = 50
        self.forward_angle = forward_angle
        self.image_source = pygame.image.load("static/images/car.png").convert()
        self.image = pygame.transform.scale(self.image_source, (self.width,self.height))
        self.image = pygame.transform.rotate(self.image, -self.forward_angle)
        self.image.set_colorkey("black")
        self.rect = self.image.get_rect()
        self.rect.center = (center_x,center_y)
        self.last_time = pygame.time.get_ticks()
        self.delta_time = 0

        self.move_velocity_limit = 300
        self.move_velocity = 0
        self.move_acc = 600
        self.rotate_velocity_limit = 140
        self.rotate_velocity = 0
        self.friction = 0.9

        self.crash_sound = pygame.mixer.Sound("static/sounds/crash.mp3")
        self.crash_sound.set_volume(0.2)
        self.move_sound = pygame.mixer.Sound("static/sounds/move.mp3")
        self.move_sound.set_volume(0.8)
        self.move_voice_channel = pygame.mixer.Channel(7)

    def update_delta_time(self):
        cur_time = pygame.time.get_ticks()
        self.delta_time = (cur_time - self.last_time) / 1000
        self.last_time = cur_time

    def input(self):
        key_pressed = pygame.key.get_pressed()
        if key_pressed[pygame.K_w] or key_pressed[pygame.K_UP]:
            self.move_velocity += self.move_acc * self.delta_time
            self.move_velocity = min(self.move_velocity, self.move_velocity_limit)
            if not self.move_voice_channel.get_busy():
                self.move_voice_channel.play(self.move_sound)
        elif key_pressed[pygame.K_s] or key_pressed[pygame.K_DOWN]:
            self.move_velocity -= self.move_acc * self.delta_time
            self.move_velocity = max(self.move_velocity, -self.move_velocity_limit)
            if not self.move_voice_channel.get_busy():
                self.move_voice_channel.play(self.move_sound)
        else:
            self.move_velocity = int(self.move_velocity * self.friction)
            if self.move_voice_channel.get_busy():
                self.move_voice_channel.stop()
        sign = 1
        if self.move_velocity < 0:
            sign = -1
        if key_pressed[pygame.K_d] or key_pressed[pygame.K_RIGHT]:
            self.rotate_velocity = self.rotate_velocity_limit * sign
        elif key_pressed[pygame.K_a] or key_pressed[pygame.K_LEFT]:
            self.rotate_velocity = -self.rotate_velocity_limit * sign
        else:
            self.rotate_velocity = 0

    def rotate(self, direction = 1):
        self.forward_angle += self.rotate_velocity * self.delta_time * direction
        self.image = pygame.transform.scale(self.image_source, (self.width, self.height))
        self.image = pygame.transform.rotate(self.image, -self.forward_angle)
        self.image.set_colorkey("black")
        center = self.rect.center
        self.rect = self.image.get_rect()
        self.rect.center = center

    def move(self, direction = 1):
        if direction == 1 and abs(self.move_velocity) > 50:
            self.rotate(direction)
        vx = self.move_velocity * math.cos(math.pi * self.forward_angle / 180) * direction
        vy = self.move_velocity * math.sin(math.pi * self.forward_angle / 180) * direction
        self.rect.x += vx * self.delta_time
        self.rect.y += vy * self.delta_time
        if direction == -1 and abs(self.move_velocity) > 50:
            self.rotate(direction)

    def crash(self):
        self.crash_sound.play()
        self.move(-1)
        if self.move_velocity  >= 0:
            self.move_velocity = min(-self.move_velocity,-100)
        else:
            self.move_velocity = max(self.move_velocity,100)
        self.rotate_velocity *= -1

    def update(self):
        self.update_delta_time()
        self.input()
        self.move()

- (2)star模块
点击查看代码
import pygame

class Star(pygame.sprite.Sprite):
    def __init__(self,center_x,center_y):
        super().__init__()
        self.image_source = pygame.image.load("static/images/star.png")
        self.image = pygame.transform.scale(self.image_source, (50, 50))
        self.image.set_colorkey("black")
        self.rect = self.image.get_rect()
        self.rect.center = (center_x, center_y)
        self.scale = 1
        self.scale_delta = 0.01

    def update(self):
        self.scale += self.scale_delta
        if self.scale > 1.1 or self.scale < 0.9:
            self.scale_delta *= -1
        self.image = pygame.transform.scale(self.image_source,(50*self.scale, 50*self.scale))
        center = self.rect.center
        self.rect = self.image.get_rect()
        self.rect.center = center
- (3)target模块
点击查看代码
import pygame

class Target(pygame.sprite.Sprite):
    def __init__(self,center_x,center_y):
        super().__init__()
        self.image_source = pygame.image.load("static/images/target.png")
        self.image = pygame.transform.scale(self.image_source, (100, 100))
        self.image.set_colorkey("black")
        self.rect = self.image.get_rect()
        self.rect.center = (center_x, center_y)
        self.scale = 1
        self.scale_delta = 0.01

    def update(self):
        self.scale += self.scale_delta
        if self.scale > 1.1 or self.scale < 0.9:
            self.scale_delta *= -1
        self.image = pygame.transform.scale(self.image_source,(100*self.scale, 100*self.scale))
        center = self.rect.center
        self.rect = self.image.get_rect()
        self.rect.center = center
- (4)Wall模块
点击查看代码
import pygame

class Wall(pygame.sprite.Sprite):
    def __init__(self,x,y,width,height):
        super().__init__()
        self.image = pygame.Surface((width,height))
        self.image.fill((106,90,205))
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y

(二)游戏运行

- 游戏界面展示

第一关界面展示:

第二关界面展示:

第三关界面展示:

第四关界面展示:

三、实验过程中遇到的问题和解决过程

  • 问题1:由于游戏设置墙壁和小车,可能会有碰撞产生,由于插入的小车图片是矩形的并不是完全贴合小车,可能会进行“误判”,对玩家造成不便。
    问题1解决方案:经同学指点和网上搜索,决定加入一新的“碰撞检测”模块,用于判断玩家和障碍物是否发生碰撞
    - 1.collided_rect(a, b):矩形边缘线段碰撞检测
    (1)获取玩家的四个角点(考虑旋转)
    构造玩家(小车)的矩形边角点,并进行了0.8倍的缩放,并使用 .rotate() 方法进行旋转,这样最终得到的是围绕中心旋转后的四个顶点坐标。

    (2)检查这些边线是否与墙壁的矩形有交集
    遍历这四个点构成的四条边,检查每条边是否与的矩墙壁边界交叉,如果有一条边交叉,则返回 True 表示发生碰撞。

    (3)额外检测另一组较小的矩形边
    这组边更靠近中心,可能是用于更敏感的碰撞检测,此处指车头。

    (4)返回值
    若任意一条线段与墙壁的矩形边框相交,返回 True;否则返回 False。

    - 2.collided_circle(a, b):圆形距离碰撞检测
    获取两个精灵对象的中心点坐标,计算两点之间的欧几里得距离,如果距离小于 50单位像素(改为动态计算每个对象的半径会更准确,但太复杂没看懂),则认为两者发生碰撞。
  • 3.通过以上两步,检测的范围会更小更精确一些,完整代码如下:
点击查看代码
import pygame
import math

def collided_rect(a, b):
    p = []
    for i, j in [(1, -1), (1, 1), (-1, 1), (-1, -1)]:
        t = pygame.Vector2(i * a.width / 2 * 0.8, j * a.height / 2 * 0.8).rotate(a.forward_angle)
        p.append(t + a.rect.center)
    for i in range(4):
        x = p[i]
        y = p[(i + 1) % 4]
        if b.rect.clipline(x, y):
            return True

    p.clear()
    for i, j in [(1, -1), (1, 1), (-1, 1), (-1, -1)]:
        t = pygame.Vector2(i * a.width / 2, j * a.height / 2 * 0.2).rotate(a.forward_angle)
        p.append(t + a.rect.center)
    for i in range(4):
        x = p[i]
        y = p[(i + 1) % 4]
        if b.rect.clipline(x, y):
            return True
    return False

def collided_circle(a,b):
    x1, y1 = a.rect.center
    x2, y2 = b.rect.center
    dx, dy = x1 - x2, y1 - y2
    if math.sqrt(dx * dx + dy * dy) < 50 :
        return True
    return False

其他(感悟、思考等)

通过本次基于 Python 和 Pygame 的游戏开发实验,我对游戏程序的基本结构和实现原理有了更加系统而深入的理解。整个项目虽然规模不大,但它涵盖了图形渲染、事件响应、资源加载、状态管理、碰撞检测等多个核心模块,是一个小型但功能完整的游戏雏形。在编写和调试代码的过程中,我不仅巩固了 Python 编程的基础知识,还学习到了许多与游戏开发相关的实用技能。
首先,在技术层面,我掌握了 Pygame 框架的使用方法。包括如何初始化游戏窗口、设置图标和标题、加载图像和音频资源、控制帧率以及处理用户的输入事件等。这些看似基础的操作,却是构建一个交互式游戏不可或缺的部分。尤其是对 pygame.sprite.Group() 的使用,让我明白了如何高效地管理多个游戏对象,并统一进行更新和绘制。
其次,在游戏逻辑的设计方面,我学会了如何组织游戏的核心流程。GameManager 类作为整个游戏的“大脑”,负责加载关卡数据、管理玩家与游戏元素之间的交互、判断胜利条件以及切换关卡。通过阅读并分析该类的实现方式,我意识到良好的模块化设计对于项目的可维护性和扩展性至关重要。例如,将墙壁、星星、目标等不同类型的对象分别管理,并通过统一接口进行操作,大大提高了代码的可读性和复用性。
此外,我还深入理解了碰撞检测这一关键机制。实验中使用了两种不同的碰撞检测函数:“collided_rect”用于检测旋转矩形边线是否与其它物体相交,适用于高精度的障碍物碰撞;而“collided_circle”则是基于圆心距离的简单判断,常用于角色与可收集物品之间的接触判定。通过对这两种方式的学习和对比,我认识到在不同场景下选择合适算法的重要性,也初步了解了游戏物理的一些原理。
在整个开发过程中,我也遇到了不少问题,比如图像加载失败、声音播放异常、精灵位置偏移等。这些问题促使我不断查阅资料、调试代码、反复测试,最终逐一解决。这个过程不仅锻炼了我的编程能力,也提升了我的问题分析与解决能力,使我更加耐心和细致地对待每一个细节。更重要的是,这次实验让我真正体会到了从零开始构建一个完整小游戏的乐趣。尽管它只是一个简单的迷宫吃星游戏,但当我看到自己的代码逐步运行起来,角色能够自由移动、吃到星星、触发胜利动画时,内心充满了成就感。这种动手实践的过程,远比单纯地学习语法或理论知识更加生动、直观且富有意义。
总的来说,本次实验不仅让我掌握了一个完整的Pygame游戏开发案例,更让我深刻体会到软件开发中逻辑思维、代码规范与用户体验之间的紧密联系。它不仅是一次技术上的提升,更是一次思维模式和工程意识的成长。未来,我希望在此基础上继续深入学习游戏开发相关知识,尝试制作更加复杂和有趣的小型游戏,进一步拓展自己的编程能力和创造力。

课程总结

本学期的《Python程序设计》课程对我而言是一次意义深远的探索之旅。从最初的编程“门外汉”,到如今能够独立构思并实现如猜拳游戏、相对复杂的小车迷宫游戏,这段学习经历不仅极大地拓展了我的技术视野,更在逻辑思维能力、问题解决技巧和编程实战能力上带来了显著的蜕变与提升。
课程伊始,王老师带领我们系统性地构建了Python基础知识的坚实框架。我深入理解了核心数据类型(列表、字典、元组等)的特性和应用场景,掌握了利用if-else条件分支进行决策控制、运用for/while循环处理重复任务,以及通过函数封装实现代码复用和结构化的精髓。初学时的挫折记忆犹新,一个中文标点导致的“Hello World”运行失败,深刻警示我编程中细节的至关重要性。正是这些看似微小的挑战,逐步培养了我严谨的编码习惯。随着基础知识的巩固,我成功实现了剪刀石头布、简易计算器等趣味项目,真切体会到将理论知识转化为实际成果的满足感,也深刻领悟到“细节决定成败”这一编程箴言的真谛。
回顾整个学习过程,最令我印象深刻的一句话是:“人生苦短,我用 Python。”这不仅仅是一句口号,更是一种态度。Python 的简洁优雅、高效强大,让我这个原本畏惧编程的人也能逐渐上手,并从中感受到乐趣。王老师的教学风格风趣幽默又细致入微,即使面对我们这些基础薄弱的同学也始终耐心指导,帮助我们在一次次试错中不断成长。此外,由于同时选修数据库应用,我在两门课程的推动下也学会了如何利用 CSDN 社区资源解决问题,养成了查阅文档、调试代码、独立思考的良好习惯。
《Python程序设计》课程,于我而言,不仅是一次技能的学习,更是一场启迪思维、激发潜能、塑造能力的成长洗礼。它生动地印证了老师您结课时赠予我们的那句话:“那些看似波澜不惊的日复一日,终将在某天,让我们看到坚持的意义。” Python 之于我,不再只是一个陌生的词汇,而是打开计算机世界大门的钥匙,是我大学学习道路上一段宝贵的经历和起点,这段学习旅程中的每一份坚持与付出,都已化作今日能力的提升与视野的开阔,并将成为驱动我在求知道路上不断前行的永恒动力。
由于发现很多同学听课效率较为低下,希望老师在后续的教学中能够继续随机抽人并且多到同学们身边走走,这样有利于让同学们更加集中精力听讲,希望以后还能有机会听到您的课程。
最后,感谢志强老师一学期以来的教导,祝老师天天开心,身体健康!

posted @ 2025-06-02 20:06  drowing  阅读(86)  评论(0)    收藏  举报