桌上冰球游戏

介绍

本文档旨在详细介绍基于Python和simpleguitk库开发的桌上冰球游戏。该游戏是一个简单的二维模拟游戏,玩家通过键盘控制球槌,与电脑对手竞技,试图将冰球打入对方球门以得分。

文档目的

提供游戏开发的详细说明,包括游戏设计理念、实现细节、操作指南和代码解析,以便开发者理解、修改和扩展游戏功能。

游戏设计

游戏规则

  • 玩家通过键盘上下键控制右侧球槌。
  • 目标是将冰球打入对方球门得分。
  • 每进一个球得1分,先得10分者获胜。

游戏元素

  • 冰球:在游戏场地内移动,与球槌和边界发生碰撞。
  • 球槌:玩家和电脑控制,用来击打冰球。
  • 得分板:显示当前双方得分。

技术实现

初始化全局变量

定义游戏所需的各种参数,如画布大小、冰球和球槌的半径、得分等。

资源加载

加载游戏所需的图像和音效资源。

辅助函数

  • draw_score:绘制得分。
  • distance:计算两点之间的距离,用于碰撞检测。
  • check_collision:处理冰球与墙壁、球槌的碰撞。

绘图和事件处理

  • draw:负责在每个画面更新周期内重绘游戏的状态。
  • key_down和key_up:处理键盘事件,控制球槌旋转。

GUI设置和游戏启动

创建游戏窗口,设置绘制函数和键盘事件处理函数,添加“重新开始”按钮,并启动游戏循环。

操作指南

  • 使用键盘上的上下键来控制右侧球槌。
  • 点击“重新开始”按钮可以重置游戏。

示例代码

import simpleguitk as gui
import random
import math
from time import sleep

# 全局变量初始化
CANVAS_WIDTH = 1024  # 画布宽度
CANVAS_HEIGHT = 768  # 画布高度
PUCK_RADIUS = 40  # 冰球半径
MALLET_RADIUS = 47  # 球槌半径
MARGIN_WIDTH = 10  # 桌边宽度
GATE_RADIUS = 200  # 球门弧半径
puck_pos = [CANVAS_WIDTH / 2, CANVAS_HEIGHT / 2]  # 冰球的初始位置
puck_vel = [0, 0]  # 冰球的初始速度
mallet1_angle = 0  # 左侧球槌的角度(以左侧球门中心为原点)
mallet1_angle_vel = math.pi / 180  # 左侧球槌的角速度
mallet1_vel = [0, 0]  # 左侧球槌的线速度
mallet1_pos = [GATE_RADIUS, CANVAS_HEIGHT / 2]  # 左侧球槌的初始位置

mallet2_angle = math.pi  # 右侧球槌的角度(右侧球门中心为原点)
mallet2_angle_vel = 0  # 右侧球槌的角速度
mallet2_vel = [0, 0]  # 由侧球槌的线速度

mallet2_pos = [CANVAS_WIDTH - GATE_RADIUS, CANVAS_HEIGHT / 2]  # 右侧球槌的初始位置
score1 = 0  # 计算机得分
score2 = 0  # 玩家得分
game_over = False  # 一场比赛是否结束

# 加载图片资源
table = gui.load_image('http://202.201.225.74/video/PythonResoure/ProjectResource/images/project4/table2.png')
puck = gui.load_image('http://202.201.225.74/video/PythonResoure/ProjectResource/images/project4/puck.png')
mallet = gui.load_image('http://202.201.225.74/video/PythonResoure/ProjectResource/images/project4/mallet.png')

score_image = [gui.load_image('http://202.201.225.74/video/PythonResoure/ProjectResource/images/project4/0_60x60.png'),
               gui.load_image('http://202.201.225.74/video/PythonResoure/ProjectResource/images/project4/1_60x60.png'),
               gui.load_image('http://202.201.225.74/video/PythonResoure/ProjectResource/images/project4/2_60x60.png'),
               gui.load_image('http://202.201.225.74/video/PythonResoure/ProjectResource/images/project4/3_60x60.png'),
               gui.load_image('http://202.201.225.74/video/PythonResoure/ProjectResource/images/project4/4_60x60.png'),
               gui.load_image('http://202.201.225.74/video/PythonResoure/ProjectResource/images/project4/5_60x60.png'),
               gui.load_image('http://202.201.225.74/video/PythonResoure/ProjectResource/images/project4/6_60x60.png'),
               gui.load_image('http://202.201.225.74/video/PythonResoure/ProjectResource/images/project4/7_60x60.png')]
# 加载音效资源
collision_sound = gui.load_sound(
    'http://202.201.225.74/video/PythonResoure/ProjectResource/sounds/project4/collision.wav')
goal_sound = gui.load_sound('http://202.201.225.74/video/PythonResoure/ProjectResource/sounds/project4/goal.wav')
lose_sound = gui.load_sound('http://202.201.225.74/video/PythonResoure/ProjectResource/sounds/project4/gameOver.ogg')
win_sound = gui.load_sound('http://202.201.225.74/video/PythonResoure/ProjectResource/sounds/project4/applause.ogg')


# 绘制比分的辅助函数
def draw_score(canvas, score1, score2):
    canvas.draw_image(score_image[score1], [30, 30], [60, 60], [CANVAS_WIDTH / 2 - 120, 50], [60, 60])
    canvas.draw_image(score_image[score2], [30, 30], [60, 60], [CANVAS_WIDTH / 2 + 120, 50], [60, 60])


# 计算两点距离的辅助函数
def distance(p, q):
    return math.sqrt((p[0] - q[0]) ** 2 + (p[1] - q[1]) ** 2)


# 碰撞检测辅助函数
def check_collision():
    global puck_vel, mallet1_vel, mallet2_vel, score1, score2
    collided = False
    # 玩家进球
    if puck_pos[1] >= CANVAS_HEIGHT / 2 - GATE_RADIUS + PUCK_RADIUS and puck_pos[
        1] <= CANVAS_HEIGHT / 2 + GATE_RADIUS - PUCK_RADIUS and puck_vel[0] < 0 and puck_pos[0] <= 0:
        score2 += 1
        spawn_puck('RIGHT')
        goal_sound.rewind()
        goal_sound.play()
        sleep(2)
        return
    # 计算机进球
    if puck_pos[1] >= CANVAS_HEIGHT / 2 - GATE_RADIUS + PUCK_RADIUS and puck_pos[
        1] <= CANVAS_HEIGHT / 2 + GATE_RADIUS - PUCK_RADIUS and puck_vel[0] > 0 and puck_pos[0] >= CANVAS_WIDTH:
        score1 += 1
        spawn_puck('LEFT')
        goal_sound.rewind()
        goal_sound.play()
        sleep(2)
        return
    # 碰右壁
    if puck_pos[0] >= CANVAS_WIDTH - PUCK_RADIUS - MARGIN_WIDTH + 8 and (
            puck_pos[1] <= CANVAS_HEIGHT / 2 - GATE_RADIUS + PUCK_RADIUS or puck_pos[
        1] >= CANVAS_HEIGHT / 2 + GATE_RADIUS - PUCK_RADIUS):
        puck_vel[0] = -puck_vel[0]
        collided = True
    # 碰左壁
    if puck_pos[0] <= PUCK_RADIUS + MARGIN_WIDTH - 8 and (
            puck_pos[1] <= CANVAS_HEIGHT / 2 - GATE_RADIUS + PUCK_RADIUS or puck_pos[
        1] >= CANVAS_HEIGHT / 2 + GATE_RADIUS - PUCK_RADIUS):
        puck_vel[0] = -puck_vel[0]
        collided = True
    # 碰下壁
    if puck_pos[1] >= CANVAS_HEIGHT - PUCK_RADIUS - MARGIN_WIDTH + 8:
        puck_vel[1] = -puck_vel[1]
        collided = True
    # 碰上壁
    if puck_pos[1] <= PUCK_RADIUS + MARGIN_WIDTH - 8:
        puck_vel[1] = -puck_vel[1]
        collided = True

    # 冰球和计算机球槌碰撞
    def vector_sub(v1, v2):
        return [v1[0] - v2[0], v1[1] - v2[1]]

    def vector_dotp(v1, v2):
        return v1[0] * v2[0] + v1[1] * v2[1]

    def vector_lengths(v):
        return v[0] ** 2 + v[1] ** 2

    if distance(puck_pos, mallet1_pos) <= PUCK_RADIUS + MALLET_RADIUS - 16:
        if mallet1_angle_vel != 0:
            theta = math.atan2(mallet1_pos[1] - CANVAS_HEIGHT / 2, mallet1_pos[0]) - math.pi / 2
            speed = math.tan(mallet1_angle_vel) * GATE_RADIUS
            mallet1_vel[0] = speed * math.cos(theta)
            mallet1_vel[1] = speed * math.sin(theta)
        pos_sub = vector_sub(puck_pos, mallet1_pos)
        tmp = 2 * (vector_dotp(vector_sub(puck_vel, mallet1_vel), pos_sub)) / vector_lengths(pos_sub)
        puck_vel[0] -= tmp * pos_sub[0]
        puck_vel[1] -= tmp * pos_sub[1]
        collided = True
    # 冰球和玩家球槌碰撞
    if distance(puck_pos, mallet2_pos) <= PUCK_RADIUS + MALLET_RADIUS - 16:
        if mallet2_angle_vel != 0:
            theta = math.atan2(mallet2_pos[1] - CANVAS_HEIGHT / 2, mallet2_pos[0] - CANVAS_WIDTH) - math.pi / 2
            speed = math.tan(mallet2_angle_vel) * GATE_RADIUS
            mallet2_vel[0] = speed * math.cos(theta)
            mallet2_vel[1] = speed * math.sin(theta)

        pos_sub = vector_sub(puck_pos, mallet2_pos)
        tmp = 2 * (vector_dotp(vector_sub(puck_vel, mallet2_vel), pos_sub)) / vector_lengths(pos_sub)
        puck_vel[0] -= tmp * pos_sub[0]
        puck_vel[1] -= tmp * pos_sub[1]

        collided = True
    # 播放碰撞音效
    if collided:
        collision_sound.rewind()
        collision_sound.play()


# 在球桌中央初始化冰球的位置和速度,方向可以向左或向右
def spawn_puck(direction):
    global puck_pos
    puck_pos = [CANVAS_WIDTH / 2, CANVAS_HEIGHT / 2]  # 冰球初始位置
    if direction == 'RIGHT':
        puck_vel[0] = random.randrange(120, 240) / 60  # 冰球水平速度
        if random.randrange(2) == 0:
            puck_vel[1] = -random.randrange(60, 180) / 60  # 冰球垂直速度
        else:
            puck_vel[1] = random.randrange(60, 180) / 60  # 冰球垂直速度
    else:
        puck_vel[0] = -random.randrange(120, 240) / 60  # 冰球水平速度
        if random.randrange(2) == 0:
            puck_vel[1] = -random.randrange(60, 180) / 60  # 冰球垂直速度
        else:
            puck_vel[1] = random.randrange(60, 180) / 60  # 冰球垂直速度


# 初始化全局变量, 也是按钮事件处理函数,用来初始化游戏
def new_game():
    global puck_pos, puck_vel, mallet1_vel, mallet2_vel, mallet1_pos, mallet2_pos, mallet1_angle, mallet2_angle
    global score1, score2, game_over
    puck_pos = [CANVAS_WIDTH / 2, CANVAS_HEIGHT / 2]
    puck_vel = [0, 0]
    mallet1_vel = [0, 0]
    mallet1_angle = 0
    mallet2_vel = [0, 0]
    mallet2_angle = math.pi
    mallet1_pos = [GATE_RADIUS, CANVAS_HEIGHT / 2]
    mallet2_pos = [CANVAS_WIDTH - GATE_RADIUS, CANVAS_HEIGHT / 2]
    score1 = 0
    score2 = 0
    game_over = False
    if random.randrange(0, 2) == 0:
        spawn_puck('LEFT')
    else:
        spawn_puck('RIGHT')


# 主绘制函数
def draw(canvas):
    global score1, score2, mallet1_angle, mallet2_angle, mallet1_angle_vel, game_over

    # 检测碰撞
    check_collision()
    # 绘制冰球桌
    canvas.draw_image(table, [512, 384], [1024, 768], [CANVAS_WIDTH / 2, CANVAS_HEIGHT / 2],
                      [CANVAS_WIDTH, CANVAS_HEIGHT])
    # 绘制比分
    draw_score(canvas, score1, score2)
    # 绘制冰球
    puck_pos[0] += puck_vel[0]
    puck_pos[1] += puck_vel[1]
    canvas.draw_image(puck, [128, 128], [256, 256], puck_pos, [PUCK_RADIUS * 2, PUCK_RADIUS * 2])
    # 绘制左侧球槌
    mallet1_angle += mallet1_angle_vel
    if mallet1_angle_vel > 0 and mallet1_angle > math.radians(90 - 17):
        mallet1_angle_vel = -mallet1_angle_vel
    if mallet1_angle_vel < 0 and mallet1_angle < math.radians(-90 + 17):
        mallet1_angle_vel = -mallet1_angle_vel
    mallet1_pos[0] = GATE_RADIUS * math.cos(mallet1_angle)
    mallet1_pos[1] = CANVAS_HEIGHT / 2 - GATE_RADIUS * math.sin(mallet1_angle)
    canvas.draw_image(mallet, [47, 47], [94, 94], mallet1_pos, [MALLET_RADIUS * 2, MALLET_RADIUS * 2])
    # 绘制右侧球槌
    if mallet2_angle_vel < 0 and mallet2_angle > math.radians(90 + 17):
        mallet2_angle += mallet2_angle_vel
    if mallet2_angle_vel > 0 and mallet2_angle < math.radians(270 - 17):
        mallet2_angle += mallet2_angle_vel
    mallet2_pos[0] = CANVAS_WIDTH + GATE_RADIUS * math.cos(mallet2_angle)
    mallet2_pos[1] = CANVAS_HEIGHT / 2 - GATE_RADIUS * math.sin(mallet2_angle)
    canvas.draw_image(mallet, [47, 47], [94, 94], mallet2_pos, [MALLET_RADIUS * 2, MALLET_RADIUS * 2])
    # 绘制游戏结束信息、播放音效
    msg = ""
    if score1 == 7:
        game_over = True
        msg = "抱歉,你输了"
        if puck_vel[0] != 0:
            lose_sound.rewind()
            lose_sound.play()
            puck_vel[0] = 0
            puck_vel[1] = 0
    if score2 == 7:
        game_over = True
        msg = "恭喜,你赢了"
        if puck_vel[0] != 0:
            win_sound.rewind()
            win_sound.play()
            puck_vel[0] = 0
            puck_vel[1] = 0
    if game_over:
        msg_width = frame.get_canvas_textwidth(msg, 60, 'sans-serif')
        canvas.draw_text(msg, [(CANVAS_WIDTH - msg_width) / 2, CANVAS_HEIGHT / 2 + 130], 60, 'Red',
                         font_face='sans-serif')


# 键盘事件的处理函数
def key_down(key):
    global mallet2_angle_vel
    if game_over:
        return
    if key == gui.KEY_MAP["up"]:
        mallet2_angle_vel = -math.pi / 180
    elif key == gui.KEY_MAP["down"]:
        mallet2_angle_vel = math.pi / 180


def key_up(key):
    global mallet2_angle_vel
    mallet2_angle_vel = 0


# 创建窗口
frame = gui.create_frame("桌上冰球", CANVAS_WIDTH, CANVAS_HEIGHT)
button = frame.add_button('重新开始', new_game, 50)
frame.set_draw_handler(draw)
frame.set_keydown_handler(key_down)
frame.set_keyup_handler(key_up)

# 启动游戏
new_game()
frame.start()

结论

本文档提供了桌上冰球游戏的开发细节,旨在帮助感兴趣的开发者和学习者理解和参与游戏的开发过程。通过阅读本文档,读者应能够掌握使用simpleguitk库进行简单游戏开发的基本方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

力江

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值