OpenCV快速入门:彩蛋——小游戏制作

文章目录

  • 前言
  • 一、游戏玩法
    • 1.1 核心玩法
    • 1.2 特殊事件
  • 二、功能模块划分
    • 2.1 主游戏文件 (`main.py`)
    • 2.2 游戏对象 (`game_objects.py`)
    • 2.3 游戏逻辑 (`game_logic.py`)
    • 2.4 事件和奖励 (`events_and_rewards.py`)
    • 2.5. 游戏界面 (`game_ui.py`)
  • 三、完整代码
    • 3.1 主游戏文件 (`main.py`)
      • 3.1.1 游戏初始化
      • 3.1.2 主游戏循环
      • 3.1.3 游戏结束
    • 3.2 游戏对象 (`game_objects.py`)
      • 3.2.1 `GameObject` 类
      • 3.2.2 `Player` 类
      • 3.2.3 `Monster` 类
      • 3.2.4 `Bullet` 类
    • 3.3 游戏逻辑 (`game_logic.py`)
      • 3.3.1 `GameLogic` 类
      • 3.3.2 方法
    • 3.4 事件和奖励 (`events_and_rewards.py`)
      • 3.4.1 `EventsAndRewards` 类
      • 3.4.2 方法
    • 3.5 游戏界面 (`game_ui.py`)
      • 3.5.1 `GameUI` 类
      • 3.5.2 绘制UI的方法
      • 3.5.3 升级菜单
  • 总结


前言

在这篇博客是一个小彩蛋,我们将探索如何使用Python和OpenCV库来制作一个简单的塔防小游戏(虽然OpenCV的设计初衷并不是用来做游戏的)。我们将详细介绍游戏的玩法以及其功能模块的划分。

Game


一、游戏玩法

这个游戏是一个垂直滚动的射击游戏。玩家培养一个位于屏幕底部的可以向上射击的角色,目的是击败出现在屏幕上部的各种怪物。游戏随着时间的推移,难度会逐渐增加。

1.1 核心玩法

  • 怪物生成:游戏会定期生成不同类型的怪物,随着时间增长,怪物出现的频率会加快。
  • 射击机制:玩家角色会向上射击,击中怪物后可以获得分数、金钱和经验值。
  • 升级系统:玩家可以使用金钱来升级子弹,例如增加子弹速度、穿透能力或添加追踪功能。

1.2 特殊事件

  • 游戏中还包含只有在特定条件下才会触发的特殊事件,如“bonus_coin”, “sudden_attack”, “treasure_chest”,为游戏增添额外的乐趣和挑战。

二、功能模块划分

游戏的代码被划分为几个主要的模块,每个模块都承担着特定的功能。

2.1 主游戏文件 (main.py)

  • 初始化游戏窗口和实体:设置窗口大小并创建玩家、怪物等游戏实体。
  • 游戏循环:包含游戏的主要循环,控制怪物的生成和子弹的射击,处理游戏逻辑和UI更新。

2.2 游戏对象 (game_objects.py)

  • GameObject类:基类,定义了游戏中所有对象共有的属性和方法。
  • Player类:定义玩家的属性和行为,如绘制、射击和升级。
  • Monster类:定义怪物的属性和行为,包括不同怪物的外观和移动方式。
  • Bullet类:定义子弹的属性和行为,包括移动、绘制和碰撞检测。

2.3 游戏逻辑 (game_logic.py)

  • GameLogic类:处理游戏的核心逻辑,如移动子弹、检测碰撞和更新游戏状态。

2.4 事件和奖励 (events_and_rewards.py)

  • EventsAndRewards类:管理游戏中的特殊事件和奖励,如随机触发事件和处理其效果。

2.5. 游戏界面 (game_ui.py)

  • GameUI类:负责游戏界面的绘制,如显示分数、生命值和经验值。

三、完整代码

3.1 主游戏文件 (main.py)

这段代码展示了如何将不同的游戏组件整合到一个主循环中,以创建一个基本的2D射击游戏。它使用了 OpenCV 来处理图形显示和键盘输入,展示了如何在 Python 中使用 OpenCV 创建简单的游戏。通过这个示例,可以看到如何结合面向对象的设计来构建游戏逻辑,以及如何处理游戏中的不同事件和状态。这个游戏可以进一步扩展,例如增加更多的怪物类型、升级选项、关卡设计等,以提升游戏体验和复杂度。

import cv2
import numpy as np
import random
import time
from game_objects import Player, Monster
from game_ui import GameUI
from game_logic import GameLogic
from events_and_rewards import EventsAndRewards

# 设置窗口大小
window_width, window_height = 400, 800

# 初始化游戏窗口
window = np.zeros((window_height, window_width, 3), dtype=np.uint8)

# 创建游戏实体
player = Player(185, 750, 30)  # 位置
# 创建游戏逻辑、界面和事件管理器 初始时没有怪物 没有子弹
game_logic = GameLogic(player, [], [], window_width, window_height)
game_ui = GameUI((window_width, window_height))
events_and_rewards = EventsAndRewards(game_logic)

# 用于控制怪物生成和子弹射击的时间
last_monster_spawn_time = time.time()
last_bullet_shoot_time = time.time()

monster_spawn_interval = 1.0  # 每秒生成一个怪物
bullet_shoot_interval = 1.0  # 每秒射击一次
game_logic.paused = False  # 游戏状态

# 主游戏循环
while True:
    game_logic.check_for_upgrade(player, window, game_ui)

    if not game_logic.paused:
        current_time = time.time()
        # 更新怪物生成间隔
        monster_spawn_interval = game_logic.get_monster_spawn_interval()
        bullet_shoot_interval = game_logic.bullet_shoot_interval()
        # 每秒生成一个怪物
        if current_time - last_monster_spawn_time > monster_spawn_interval:
            for i in range(max(1, int(player.level / 3))):
                size = random.randint(10, 50)  # 假设怪物宽度
                x = random.randint(0, window_width - size)  # 假设怪物宽度为30
                game_logic.monsters.append(Monster(x, 0, "random_type", size))  # 使用随机类型或其他逻辑

            last_monster_spawn_time = current_time

        # 每秒射击一次
        if current_time - last_bullet_shoot_time > bullet_shoot_interval:
            player.shoot(game_logic.bullets, game_logic.monsters)
            last_bullet_shoot_time = current_time

        # 清空窗口
        game_ui.clear_window(window, player)
        # 绘制玩家
        player.draw(window)

        # 更新和绘制子弹
        for bullet in game_logic.bullets:
            bullet.move(monsters=game_logic.monsters)
            bullet.draw(window)

        # 更新和绘制怪物
        for monster in game_logic.monsters:
            monster.move(0, 1, player.y - 10)  # 假设怪物每次下移1像素
            monster.draw(window)

        # 处理游戏逻辑
        game_logic.update()

        if len(game_logic.monsters) > 50:
            # 处理事件和奖励
            event = events_and_rewards.trigger_random_event()
            if event:
                # 如果有事件被触发,处理该事件
                events_and_rewards.apply_event_effects(event)

        # 显示窗口内容
        cv2.imshow("Game Window", window)

        # 检查是否按下了退出键(例如Esc键)
        key = cv2.waitKey(1)
        if key == 27:  # Esc键的ASCII码
            break

# 游戏结束,关闭窗口
cv2.destroyAllWindows()

这段代码集成了下面定义的 Player, Monster, GameUI, GameLogic, 和 EventsAndRewards 类,以创建一个完整的游戏环境。以下是对代码的主要部分的解析:

3.1.1 游戏初始化

  • 窗口设置: 设定游戏窗口的大小(400x800像素),并初始化一个全黑的画布。
  • 游戏实体创建: 创建玩家、游戏逻辑处理器、用户界面管理器和事件与奖励处理器的实例。
  • 时间控制变量: 用于控制怪物生成和子弹射击的时间间隔。

3.1.2 主游戏循环

  • 升级检查: 在每次循环开始时,检查玩家是否满足升级条件。
  • 怪物生成: 根据设定的间隔和玩家等级,定期在游戏窗口的顶部随机位置生成怪物。
  • 子弹射击: 根据玩家的射击间隔定期发射子弹。
  • 绘制游戏元素: 包括清空窗口、绘制玩家、子弹和怪物。
  • 游戏逻辑更新: 调用 GameLogicupdate 方法来处理子弹移动、子弹和怪物的碰撞以及怪物和墙的碰撞。
  • 事件和奖励处理: 当怪物数量超过50时,触发随机事件,并应用相应的效果。
  • 显示和键盘输入: 使用 OpenCV 显示游戏窗口,并检测是否按下退出键(如Esc键)。

3.1.3 游戏结束

  • 关闭窗口: 当玩家按下退出键时,跳出循环并关闭游戏窗口。

3.2 游戏对象 (game_objects.py)

这段代码提供了一个2D游戏的基本结构,包含玩家、怪物和子弹的互动。玩家可以升级子弹,而怪物具有不同的形状和行为。代码还包括子弹的追踪和穿透功能,为游戏增加了更多策略性。通过对这些类和方法的进一步扩展和定制,可以创建更复杂和有趣的游戏体验。

import cv2
import numpy as np


class GameObject:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def draw(self, window):
        pass  # To be implemented in subclasses

    def move(self, dx, dy):
        self.x += dx
        self.y += dy


class Player:
    def __init__(self, x, y, size=30):
        self.x = x
        self.y = y
        self.size = size
        self.health = 3000  # 初始生命值
        self.score = 0  # 初始分数
        self.money = 0  # 初始金钱
        self.experience = 0  # 初始经验值
        self.level = 1  # 初始等级
        self.color = (255, 255, 255)  # 玩家颜色

        self.bullet_speed = 5  # 子弹速度
        self.bullet_size = 5  # 子弹大小
        self.shoot_interval = 1.0  # 发射间隔,初始为1秒
        self.tracking_bullet = False  # 是否发射跟踪弹
        self.piercing_bullet = 0  # 是否发射贯穿弹 穿透怪物次数

        self.shoot_interval_multiplier = 1  # 发射间隔,升级成本乘数
        self.piercing_bullet_multiplier = 1  # 贯穿弹,升级成本乘数

    def draw(self, window):
        top_left = (self.x, self.y)
        bottom_right = (self.x + self.size, self.y + self.size)
        cv2.rectangle(window, top_left, bottom_right, self.color, -1, lineType=cv2.LINE_AA)
        wall_height = 5  # 假设墙的高度为5像素
        wall_color = (127, 127, 127)  # 灰色
        cv2.rectangle(window, (0, self.y - 10), (window.shape[1], self.y - 10 + wall_height), wall_color, -1)

    def shoot(self, bullets, monsters):
        # 发射子弹
        bullet_x = self.x + self.size // 2
        bullet_y = self.y
        bullet_direction = (0, -1)  # 向上射击
        bullets.append(Bullet(bullet_x, bullet_y, bullet_direction,
                              self.bullet_speed, self.bullet_size,
                              monsters, self.tracking_bullet, self.piercing_bullet))

    def upgrade_bullet(self, upgrade_type):
        # 实现升级子弹的逻辑
        if upgrade_type == "Increase Bullet":
            self.shoot_interval = max(0.1, self.shoot_interval - 0.1)  # 减少发射间隔,最小为0.1秒
            self.money = self.money - 10 * self.shoot_interval_multiplier  # 扣减金币
            self.shoot_interval_multiplier = self.shoot_interval_multiplier + 0.5

        elif upgrade_type == "Piercing Bullet":
            self.piercing_bullet = self.piercing_bullet + 1  # 穿透怪物次数+1
            self.money = self.money - 20 * self.piercing_bullet_multiplier  # 扣减金币
            self.piercing_bullet_multiplier = self.piercing_bullet_multiplier + 0.5

        elif upgrade_type == "Tracking Bullet":
            self.tracking_bullet = True  # 启用跟踪弹
            self.money = self.money - 500  # 扣减金币

    def upgrade(self):
        self.experience = 0  # 重置经验值
        self.level += 1  # 提升等级


class Monster(GameObject):
    def __init__(self, x, y, monster_type, size=30):
        super().__init__(x, y)
        self.monster_type = monster_type
        self.size = size  # 添加怪物的尺寸属性

    def draw(self, window):
        if self.monster_type == "random_type":
            self.monster_type = np.random.choice(["triangle", "ellipse", "diamond"])

        half_size = self.size // 2

        if self.monster_type == "triangle":
            points = np.array([
                [self.x, self.y - half_size],
                [self.x + half_size, self.y + half_size],
                [self.x - half_size, self.y + half_size]
            ], np.int32)
            cv2.polylines(window, [points], True, (0, 255, 0), thickness=2, lineType=cv2.LINE_AA)

        elif self.monster_type == "ellipse":
            cv2.ellipse(window, (self.x, self.y), (half_size, half_size // 2), 0, 0, 360, (0, 0, 255), -1,
                        lineType=cv2.LINE_AA)

        elif self.monster_type == "diamond":
            points = np.array([
                [self.x, self.y - half_size],
                [self.x + half_size, self.y],
                [self.x, self.y + half_size],
                [self.x - half_size, self.y]
            ], np.int32)
            cv2.polylines(window, [points], True, (255, 255, 0), thickness=2, lineType=cv2.LINE_AA)

    def move(self, dx, dy, wall_y=0):
        if self.y + dy + self.size <= wall_y:  # 确保怪物不会移动到墙的位置
            super().move(dx, dy)


class Bullet(GameObject):
    def __init__(self, x, y, direction, speed, size, monsters, tracking=False, piercing_count=0):
        super().__init__(x, y)
        self.direction = direction  # 射击方向
        self.speed = speed
        self.size = size
        self.tracking = tracking  # 是否追踪敌人
        self.piercing_count = piercing_count  # 穿透怪物次数
        # 初始化时确定方向
        if monsters:
            self.direction = self.calculate_direction_to_closest_monster(monsters)
        else:
            self.direction = (0, -1)  # 如果没有怪物,默认向上

    def draw(self, window):
        cv2.circle(window, (int(self.x), int(self.y)), self.size, (255, 255, 0), -1, lineType=cv2.LINE_AA)

    def move(self, dx=0, dy=0, monsters=None):
        if self.tracking and monsters:
            self.update_direction_to_closest_monster(monsters)
        else:
            # 如果不追踪或没有怪物,继续向原始方向移动
            dx, dy = self.direction[0] * self.speed, self.direction[1] * self.speed
        super().move(dx, dy)

    def update_direction_to_closest_monster(self, monsters):
        if monsters:
            closest_monster = min(monsters,
                                  key=lambda monster: np.linalg.norm([monster.x - self.x, monster.y - self.y]))
            dx, dy = closest_monster.x - self.x, closest_monster.y - self.y
            distance = np.sqrt(dx ** 2 + dy ** 2)
            self.direction = (dx / distance, dy / distance)

    def calculate_direction_to_closest_monster(self, monsters):
        shoot_offset = 15  # 射击提前量
        closest_monster = min(monsters, key=lambda monster: np.linalg.norm([monster.x - self.x, monster.y - self.y]))
        dx, dy = closest_monster.x - self.x, closest_monster.y - self.y + shoot_offset
        distance = np.sqrt(dx ** 2 + dy ** 2)
        return dx / distance, dy / distance

3.2.1 GameObject

  • 基础类:所有游戏对象的基类,包含基本属性如坐标 (x, y)。
  • 方法
    • draw(window): 在游戏窗口中绘制对象,具体实现在子类中。
    • move(dx, dy): 移动对象,改变其坐标。

3.2.2 Player

  • 功能:表示游戏中的玩家。
  • 属性:包括位置 (x, y), 大小 (size), 生命值 (health), 分数 (score), 金钱 (money), 经验 (experience), 等级 (level), 玩家颜色 (color),以及与子弹相关的属性。
  • 方法
    • draw(window): 在游戏窗口中绘制玩家。
    • shoot(bullets, monsters): 发射子弹。
    • upgrade_bullet(upgrade_type): 升级子弹的类型。
    • upgrade(): 提升玩家等级。

3.2.3 Monster

  • 功能:代表游戏中的怪物。
  • 属性:除了 GameObject 的基本属性外,还包括怪物类型 (monster_type) 和大小 (size)。
  • 方法
    • draw(window): 根据怪物类型在游戏窗口中绘制不同的图形。
    • move(dx, dy, wall_y): 移动怪物,但不允许它们穿过墙。

3.2.4 Bullet

  • 功能:代表游戏中的子弹。
  • 属性:包括方向 (direction), 速度 (speed), 大小 (size), 是否追踪 (tracking), 穿透次数 (piercing_count)。
  • 方法
    • draw(window): 在游戏窗口中绘制子弹。
    • move(dx, dy, monsters): 根据是否追踪怪物来更新子弹的方向和位置。
    • update_direction_to_closest_monster(monsters): 更新子弹的方向,使其指向最近的怪物。
    • calculate_direction_to_closest_monster(monsters): 计算并返回指向最近怪物的方向。

3.3 游戏逻辑 (game_logic.py)

GameLogic 类是游戏的核心,负责管理游戏中的动态元素,如玩家、子弹和怪物的行为。它通过更新游戏状态、处理碰撞和升级逻辑来保持游戏的连贯性。这个类可以被进一步扩展,以增加更多的游戏特性和复杂性。通过这种方式,可以创建一个更丰富、更引人入胜的游戏体验。

import cv2
import numpy as np


class GameLogic:
    def __init__(self, player, monsters, bullets, window_width, window_height):
        self.paused = False
        self.player = player
        self.monsters = monsters
        self.bullets = bullets
        self.window_width = window_width
        self.window_height = window_height

    def update(self):
        self.move_bullets()
        self.check_bullet_monster_collision()
        self.check_monster_wall_collision()
        self.remove_offscreen_objects()

    def move_bullets(self):
        for bullet in self.bullets:
            bullet.move()

    def check_bullet_monster_collision(self):
        for bullet in self.bullets[:]:
            for monster in self.monsters[:]:
                if self.is_collision(bullet, monster):
                    self.player.score += int(monster.size / 10)  # 加分
                    self.player.money += np.random.randint(1, max(2, int(monster.size / 10)))  # 加金币
                    self.player.experience += int(monster.size / self.player.level)  # 加经验
                    self.monsters.remove(monster)
                    if bullet.piercing_count <= 0:
                        self.bullets.remove(bullet)
                    else:
                        bullet.piercing_count = bullet.piercing_count - 1
                    break  # 退出内层循环,避免迭代已移除的子弹

    def check_monster_wall_collision(self):
        for monster in self.monsters[:]:
            if monster.y + monster.size >= (self.player.y-10):  # 假设怪物底部触碰到墙
                self.player.health -= 1  # 每个怪物每秒减少1点生命值

    def remove_offscreen_objects(self):
        self.bullets = [bullet for bullet in self.bullets if self.is_on_screen(bullet)]
        self.monsters = [monster for monster in self.monsters if self.is_on_screen(monster)]

    def is_collision(self, obj1, obj2):
        collision_size = obj2.size if obj2.size > 20 else 20
        # 简单的碰撞检测逻辑
        if (obj1.x < obj2.x + collision_size and
                obj1.x + collision_size > obj2.x and
                obj1.y < obj2.y + obj2.size and
                obj1.y + collision_size > obj2.y):
            return True
        return False

    def is_on_screen(self, obj):
        # 检查物体是否还在屏幕中
        return 0 <= obj.x <= self.window_width and 0 <= obj.y <= self.window_height

    def get_monster_spawn_interval(self):
        # 随着等级增加,生成怪物的间隔时间减少
        # 例如,每提升一级,时间减少3%
        interval_decrease = 0.03 * self.player.level
        base_interval = 1.0
        new_interval = max(0.1, base_interval - interval_decrease)  # 设置一个最小间隔时间
        return new_interval

    def bullet_shoot_interval(self):
        return self.player.shoot_interval

    def check_for_upgrade(self, player, window, game_ui):
        if player.experience >= 100 and player.level < 20:
            player.upgrade()
            if player.level >= 20:
                # 清空窗口
                window.fill(0)

                cv2.putText(window, "Victory!!!", (130, 400), cv2.FONT_HERSHEY_SIMPLEX, 1,
                            (0, 127, 255), 2, lineType=cv2.LINE_AA)
                # 更新窗口
                cv2.imshow("Game Window", window)

                # 等待按下Esc键
                while True:
                    if cv2.waitKey(1) == 27:  # Esc键的ASCII码
                        break
            else:
                # 绘制UI
                game_ui.clear_window(window, player)
                while player.money > 0:
                    self.paused = True
                    selected_option = game_ui.show_upgrade_menu(window, player)
                    # 根据选择的升级选项进行升级
                    player.upgrade_bullet(selected_option)
                    # 绘制UI
                    game_ui.clear_window(window, player)
                    if selected_option == "Cancel":
                        break
                self.paused = False

该类的主要作用是控制游戏的流程,包括移动子弹、检测碰撞、移除屏幕外的对象、生成怪物和处理玩家升级。以下是对关键功能的解析:

3.3.1 GameLogic

  • 构造函数 (__init__): 初始化游戏逻辑,包括玩家、怪物、子弹以及窗口的宽度和高度。
  • 属性:
    • paused: 游戏是否暂停。
    • player: 玩家对象。
    • monsters: 怪物列表。
    • bullets: 子弹列表。
    • window_width, window_height: 游戏窗口的尺寸。

3.3.2 方法

  • update: 更新游戏逻辑,包括移动子弹、检查子弹与怪物的碰撞、检查怪物与墙的碰撞和移除屏幕外的对象。
  • move_bullets: 移动游戏中的所有子弹。
  • check_bullet_monster_collision: 检测子弹与怪物之间的碰撞。如果发生碰撞,增加玩家的得分、金币和经验值。若子弹具有穿透能力,减少其穿透次数。
  • check_monster_wall_collision: 检测怪物是否碰撞到墙。如果碰撞到墙,减少玩家的生命值。
  • remove_offscreen_objects: 移除屏幕外的子弹和怪物。
  • is_collision: 简单的碰撞检测逻辑,用于判断两个对象是否碰撞。
  • is_on_screen: 检查对象是否还在屏幕内。
  • get_monster_spawn_interval: 计算怪物生成的间隔时间,随玩家等级提高而减少。
  • bullet_shoot_interval: 返回玩家当前的子弹发射间隔。
  • check_for_upgrade: 检查玩家是否满足升级条件。如果满足,允许玩家进行升级,并处理游戏胜利逻辑。

3.4 事件和奖励 (events_and_rewards.py)

EventsAndRewards 类通过随机事件为游戏增加了不可预测性和多样性。这些事件不仅增加了游戏的趣味性,还为玩家提供了额外的挑战和奖励。这种机制可以进一步扩展,比如增加更多的事件类型、调整奖励规则或修改触发概率,以创造更丰富的游戏体验。通过与 GameLogic 类的交互,EventsAndRewards 类能够有效地融入游戏的总体框架中。

import random


class EventsAndRewards:
    def __init__(self, game_logic):
        self.events = ["bonus_coin", "sudden_attack", "treasure_chest"]
        self.rewards = {"bonus_coin": 10, "sudden_attack": 5, "treasure_chest": 50, }
        self.trigger_probability = 0.1  # 设置触发概率为10%
        self.game_logic = game_logic

    def trigger_random_event(self):
        # 使用随机数决定是否触发事件
        if random.random() < self.trigger_probability:
            # 如果触发,随机选择一个事件
            event = random.choice(self.events)
            return event
        else:
            # 如果不触发,返回None或其他标记
            return None

    def apply_event_effects(self, event):
        if event == "bonus_coin":
            self.handle_bonus_coin()
        elif event == "sudden_attack":
            self.handle_sudden_attack()
        elif event == "treasure_chest":
            self.handle_treasure_chest()

    def handle_bonus_coin(self):
        self.game_logic.player.money = self.game_logic.player.money + random.randint(2, self.rewards["bonus_coin"])

    def handle_sudden_attack(self):
        shoot_count = random.randint(1, self.rewards["sudden_attack"])
        for i in range(shoot_count):
            self.game_logic.player.shoot(self.game_logic.bullets, self.game_logic.monsters)

    def handle_treasure_chest(self):
        self.game_logic.player.money = self.game_logic.player.money + random.randint(2, self.rewards["treasure_chest"])

这段代码负责在游戏中触发和处理各种随机事件以及给予相应的奖励。以下是对其主要组成部分的解析:

3.4.1 EventsAndRewards

  • 构造函数 (__init__): 初始化事件处理器,设置事件类型、对应奖励和触发概率。
  • 属性:
    • events: 可能发生的事件列表(如 “bonus_coin”, “sudden_attack”, “treasure_chest”)。
    • rewards: 每个事件对应的奖励。
    • trigger_probability: 事件触发的概率。
    • game_logic: 游戏逻辑对象,用于访问和修改游戏状态。

3.4.2 方法

  • trigger_random_event: 根据设定的概率随机决定是否触发一个事件。如果触发,随机选择并返回一个事件;否则返回 None
  • apply_event_effects: 根据触发的事件类型,调用相应的处理方法。
  • handle_bonus_coin: 处理“bonus_coin”事件,随机增加玩家的金币数量。
  • handle_sudden_attack: 处理“sudden_attack”事件,使玩家自动发射一定数量的子弹。
  • handle_treasure_chest: 处理“treasure_chest”事件,随机增加玩家的金币数量。

3.5 游戏界面 (game_ui.py)

GameUI 类负责游戏中所有与用户界面相关的视觉元素的显示,包括分数、生命值、金钱等。它还处理玩家与升级菜单的交互,允许玩家使用金币来选择不同的升级选项。这个类将游戏逻辑与用户界面清晰地分离开来,使得游戏的代码更加模块化和易于管理。通过这种方式,可以更容易地进行UI的更新和改进,以提供更好的玩家体验。

import cv2
import numpy as np


class GameUI:
    def __init__(self, window_size):
        self.window_size = window_size
        self.font = cv2.FONT_HERSHEY_SIMPLEX
        self.font_scale = 0.5
        self.font_color = (255, 255, 255)
        self.line_type = 1

    def draw_score(self, window, score):
        cv2.putText(window, f'Score: {score}', (10, 20), self.font, self.font_scale, self.font_color, self.line_type,
                    lineType=cv2.LINE_AA)

    def draw_health(self, window, health):
        cv2.putText(window, f'Health: {health}', (10, 40), self.font, self.font_scale, self.font_color, self.line_type,
                    lineType=cv2.LINE_AA)

    def draw_money(self, window, money):
        cv2.putText(window, f'Coins: {int(money)}', (10, 60), self.font, self.font_scale, self.font_color, self.line_type,
                    lineType=cv2.LINE_AA)

    def draw_experience(self, window, experience):
        cv2.putText(window, f'EXP: {experience}', (10, 80), self.font, self.font_scale, self.font_color, self.line_type,
                    lineType=cv2.LINE_AA)

    def draw_level(self, window, level):
        cv2.putText(window, f'Level: {level}', (10, 100), self.font, self.font_scale, self.font_color, self.line_type,
                    lineType=cv2.LINE_AA)

    def draw_upgrade_menu(self, window, upgrades):
        start_y = 100
        cv2.putText(window, 'Upgrades:', (10, start_y), self.font, self.font_scale, self.font_color, self.line_type,
                    lineType=cv2.LINE_AA)
        for i, upgrade in enumerate(upgrades):
            text = f"{i + 1}. {upgrade['name']} - Cost: {upgrade['cost']}"
            cv2.putText(window, text, (10, start_y + 20 * (i + 1)), self.font, self.font_scale, self.font_color,
                        self.line_type, lineType=cv2.LINE_AA)

    def clear_window(self, window, player):
        # 清空窗口
        window.fill(0)
        # 绘制UI
        self.draw_score(window, player.score)
        self.draw_health(window, player.health)
        self.draw_money(window, player.money)
        self.draw_experience(window, player.experience)
        self.draw_level(window, player.level)

    def show_upgrade_menu(self, window, player):
        upgrade_options = ["Increase Bullet", "Piercing Bullet", "Tracking Bullet"]
        upgrade_costs = [int(10 * player.shoot_interval_multiplier), int(20 * player.piercing_bullet_multiplier),
                         500]  # 升级成本
        return_option = None
        text_cord = 200

        def mouse_callback(event, x, y, flags, param):
            nonlocal return_option
            if event == cv2.EVENT_LBUTTONDOWN:
                for i, option in enumerate(upgrade_options):
                    if text_cord - 10 + (i - 1) * 30 <= y <= text_cord + 10 + i * 30:  # 按钮位置
                        if player.money >= upgrade_costs[i]:
                            if option == "Tracking Bullet" and player.tracking_bullet is not True:
                                return_option = option
                            elif option != "Tracking Bullet":
                                return_option = option
                        else:
                            cv2.putText(window, "Not Enough Coins", (50, text_cord + (len(upgrade_options) + 2) * 30),
                                        cv2.FONT_HERSHEY_SIMPLEX, 0.6,
                                        (0, 0, 255), 2, lineType=cv2.LINE_AA)
                        break
                # Cancel位置
                if text_cord - 10 + len(upgrade_options) * 30 <= y <= text_cord + 10 + (
                        len(upgrade_options) + 1) * 30:
                    return_option = "Cancel"
                    return return_option

        cv2.setMouseCallback("Game Window", mouse_callback)

        while return_option is None:
            cv2.rectangle(window, (10, text_cord - 40),
                          (window.shape[1] - 10, text_cord + (len(upgrade_options) + 1) * 30),
                          (200, 127, 0), -1, lineType=cv2.LINE_AA)
            # 显示升级选项
            for i, option in enumerate(upgrade_options):
                if player.tracking_bullet is True and option == "Tracking Bullet":
                    continue
                text = f"{option} - Coins: {upgrade_costs[i]}"
                cv2.putText(window, text, (50, text_cord + i * 30), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2,
                            lineType=cv2.LINE_AA)

            cv2.putText(window, "Cancel", (50, text_cord + len(upgrade_options) * 30), cv2.FONT_HERSHEY_SIMPLEX, 0.6,
                        (255, 255, 255), 2, lineType=cv2.LINE_AA)

            cv2.imshow("Game Window", window)
            cv2.waitKey(1)

        cv2.setMouseCallback("Game Window", lambda *args: None)  # 移除鼠标事件
        return return_option

这段代码定义了一个名为 GameUI 的类,用于在游戏中显示用户界面(UI),包括得分、生命值、金币、经验值和等级,以及处理升级菜单的显示和交互。以下是对其主要组成部分的解析:

3.5.1 GameUI

  • 构造函数 (__init__): 初始化UI的一些基础设置,如窗口大小、字体、字体大小、字体颜色和线型。
  • 属性:
    • window_size: 游戏窗口的大小。
    • font, font_scale, font_color, line_type: 用于绘制文本的字体设置。

3.5.2 绘制UI的方法

  • draw_score: 在窗口上绘制玩家的得分。
  • draw_health: 在窗口上绘制玩家的生命值。
  • draw_money: 在窗口上绘制玩家的金币数量。
  • draw_experience: 在窗口上绘制玩家的经验值。
  • draw_level: 在窗口上绘制玩家的等级。

3.5.3 升级菜单

  • draw_upgrade_menu: 绘制升级选项的菜单。
  • clear_window: 清空窗口并重新绘制所有UI元素。
  • show_upgrade_menu: 显示升级菜单,并处理玩家的升级选择。这个方法包含了一个鼠标回调函数 mouse_callback,用于处理玩家点击升级选项时的交互。

总结

这个游戏在玩法流程上还不够完善,但是也展示了如何使用Python和OpenCV来创建有趣的游戏体验。通过将游戏代码模块化,我们不仅使代码更加清晰易懂,也方便了未来的维护和扩展。这个项目是理解游戏开发基础和练习编程技能的绝佳途径。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/207891.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络(超详解!) 第二节 物理层(下)

1.信道复用技术 复用 (multiplexing) 是通信技术中的基本概念。 它允许用户使用一个共享信道进行通信&#xff0c;降低成本&#xff0c;提高利用率。 1.频分复用 FDM(Frequency Division Multiplexing) 将整个带宽分为多份&#xff0c;用户在分配到一定的频带后&#xff0c;…

20、LED点阵屏

LED点阵屏介绍 LED点阵屏由若干个独立的LED组成&#xff0c;LED以矩阵的形式排列&#xff0c;以灯珠亮灭来显示文字、图片、视频等。LED点阵屏广泛应用于各种公共场合&#xff0c;如汽车报站器、广告屏以及公告牌等 LED点阵屏分类 按颜色&#xff1a;单色、双色、全彩 按像素…

企业营销管理能够实现自动化吗?怎么做?

当今企业面临着越来越多的营销难题&#xff1a;如何有效培育潜在客户、如何提高营销活动的效果、如何优化营销资源的分配......企业的营销管理怎么做&#xff1f;或许CRM系统营销自动化会起到作用。 客户细分&#xff1a; 企业可以通过CRM的客户细分功能&#xff0c;根据客户…

【libGDX】立方体手动旋转

1 前言 本文主要介绍使用 libGDX 绘制立方体&#xff0c;并实现手动触摸事件控制立方体旋转。 为方便控制触摸旋转&#xff0c;并提高渲染性能&#xff0c;我们通过改变相机的位置和姿态实现立方体旋转效果。 读者如果对 libGDX 不太熟悉&#xff0c;请回顾以下内容。 使用Me…

11.31链表,之前的数据结构(未完,饼)

根据输入序列建立二叉树 链表 回顾一下二分面积最小 一些性质题回顾 哈夫曼树构建 第十一周——哈夫曼树 5 1 2 2 5 9 37 桶排序 #include <iostream> #include <vector> #include <algorithm> #include<stack> #include<queue> #includ…

docker部署kerberos,群晖nas中nfs开启kerberos校验

背景 nas开启nfs存储共享&#xff0c;默认情况下只能给IP/24做限制, 达不到安全效果 需要增加kerberos策略校验&#xff0c;并且持久化kerberos数据&#xff0c;避免容器重启丢失数据 环境描述 宿主机系统&#xff1a;CentOS Linux release 7.9.2009 (Core) Docker版本&#xf…

ESP32-Web-Server编程- 实现 Web 登录网页

ESP32-Web-Server编程- 实现 Web 登录网页 概述 是时候实现更加安全的网页了。登录机制是最简单的控制网页访问权限的方法。 需求及功能解析 本节演示如何在 ESP32 上部署一个 Web 服务器&#xff0c;并建立登录页面的机制&#xff0c;用户可以实现登录、登出的功能&#x…

【Python表白限定】李峋同款可写字版跳动的爱心(完整代码)

文章目录 跳动的爱心环境需求完整代码详细分析系列文章 跳动的爱心 环境需求 python3.11.4PyCharm Community Edition 2023.2.5pyinstaller6.2.0&#xff08;可选&#xff0c;这个库用于打包&#xff0c;使程序没有python环境也可以运行&#xff0c;如果想发给好朋友的话需要这…

nginx配置反向代理及负载均衡

目录 1.前端发送的请求&#xff0c;是如何请求到后端服务的1.nginx 反向代理的好处&#xff1a;2.nginx 反向代理的配置方式&#xff1a;3. nginx 负载均衡的配置方式 1.前端发送的请求&#xff0c;是如何请求到后端服务的 1.nginx 反向代理的好处&#xff1a; 提高访问速度 因…

一文解决msxml3.dll文件缺失问题,快速修复msxml3.dll

在了解问题之前&#xff0c;我们必须首先清楚msxml3.dll到底是什么。DLL&#xff08;Dynamic Link Libraries&#xff09;文件是Windows操作系统使用的一个重要组成部分&#xff0c;用于存储执行特定操作或任务的代码和数据。msxml3.dll为Windows系统提供处理XML文档的功能。如…

小米摄像头拆机教程

今天拆解一下好久不用的小米摄像头&#xff0c;记录下拆机过程&#xff0c;有需要的小伙伴可以自行查看 一、拆底座 首先拿出底座的四个橡皮塞、把对应的螺丝拧下来就可以了&#xff0c;这一步还是比较简单的 二、拆下底部排线 三、拆下底部电机和底座 按下方的红圈拆掉电机上的…

全网最新最全的Jmeter接口测试:jmeter组件元件介绍

JMeter 的主要测试组件总结如下&#xff1a; 1. 测试计划是使用 JMeter 进行测试的起点&#xff0c;它是其它 JMeter 测试元件的容器 2. 线程组代表一定数量的并发用户&#xff0c;它可以用来模拟并发用户发送请求。实际的 请求内容在Sampler中定义&#xff0c;它被线程组包含…

Redis主从复制实现RCE

文章目录 前置知识概念redis module 利用条件利用工具思路例题 [网鼎杯 2020 玄武组]SSRFMe 前置知识 概念 背景是多台服务器要保存同一份数据&#xff0c;如何实现其一致性呢&#xff1f;数据的读写操作是否每台服务器都可以处理&#xff1f;这里Redis就提供了主从复制的模式…

c++ pcl出现LNK2019 宏定义 PCL_NO_PRECOMPILE

问题&#xff1a;c pcl使用拟合圆柱时出现LNK2019问题&#xff1b; 说明&#xff1a;lib等配置没有问题&#xff1b; 解决方案 在上述代码中添加如下代码即可 #define PCL_NO_PRECOMPILE 是 C 中的预处理器指令&#xff0c;用于在代码中定义一个宏。而 #undef PCL_NO_PRECOM…

汽车行驶不同工况数据

1、内容简介 略 28-可以交流、咨询、答疑 2、内容说明 汽车行驶不同工况数据 汽车行驶不同工况数据 ECE、EUDC、FTP75、NEDC、自定义 3、仿真分析 4、参考论文 略 链接&#xff1a;https://pan.baidu.com/s/1AAJ_SlHseYpa5HAwMJlk1w 提取码&#xff1a;rvol

Unittest自动化测试之unittestunittest_生成测试报告

unittest_生成测试报告 测试报告为测试结果的统计即展示&#xff0c;是自动化测试不可或缺的一部分&#xff0c;利用unittest 可以生成测试报告 方式一、使用第三方 HTMLTestRunner 执行测试用例集&#xff0c;生成网页版测试报告&#xff08;推荐&#xff09; HTMLTestRunn…

进程与线程的区别

作者简介&#xff1a; zoro-1&#xff0c;目前大二&#xff0c;正在学习Java&#xff0c;数据结构,mysql,javaee等 作者主页&#xff1a; zoro-1的主页 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f496; 进程与线程的区别 进程线程进程与线…

Java流处理之序列化和打印流

文章目录 序列化概述ObjectOutputStream类构造方法序列化操作 ObjectInputStream类构造方法反序列化操作1**反序列化操作2** 案例&#xff1a;序列化集合案例分析案例实现 打印流概述PrintStream类构造方法改变打印流向 序列化 概述 Java 提供了一种对象序列化的机制。用一个…

回顾Django的第二天

1.http 1.1http请求协议与响应协议 1.1.1简介 http协议包含由浏览器发送数据到服务器需要遵循的请求协议与服务器发送数据到浏览器需要遵循的请求协议。用于HTTP协议交互的信被为HTTP报文。请求端(客户端)的HTTP报文 做请求报文,响应端(服务器端)的 做响应报文。HTTP报文本身…

Linux5-计划任务、进程

计划任务 一、cron 计划任务 周期性计划任务 cron 任务概述 • 用途:按照设置的时间间隔为用户反复执行某一项固定的系统任务 • 软件包&#xff1a;cronie、crontabs • 系统服务&#xff1a;crond • 日志文件&#xff1a;/var/log/crond 管理计划任务策略 • 使用 cro…
最新文章