[pygame] pygame设计联机对战桌游(二)

本系列总目录:https://blog.csdn.net/wxlxy316/article/details/104246724

内容概要

一、学习pygame提供的官方教程(2)

官方教程

3.3 设计伊始

3.3.1 加载模块

启动游戏,首先应该加载模块

#!/usr/bin/env python
#
# Tom's Pong
# A simple pong game with realistic physics and AI
# http://www.tomchance.uklinux.net/projects/pong.shtml
#
# Released under the GNU General Public License

VERSION = "0.4"

try:
    import sys
    import random
    import math
    import os
    import getopt
    import pygame
    from socket import *
    from pygame.locals import *
except:
    print("couldn't load module.")
    sys.exit(2)
3.3.2 资源处理

对于资源加载,由于它们和游戏逻辑并无关系,我们常常将它独立为一些函数,并且最先设计,从而为之后的设计开发所用。通常,我将所有这种性质的代码放在自己的无类函数中;一般来说,这些将是资源处理功能。您当然可以为它们创建类,以便可以将它们分组在一起,并且可能有一个可以控制所有资源的对象。与任何良好的编程环境一样,由您自己制定最佳实践和风格。

编写自己的资源处理功能总是一个好主意,因为尽管pygame有打开图像和声音的方法,并且其他模块将有打开其他资源的方法,但是这些方法可能占用多个行,但是它们可能需要一致的自行修改,它们通常无法提供令人满意的错误处理。编写资源处理功能可以为您提供复杂的,可重用的代码,并可以更好地控制资源。以图像加载功能为例:

def load_png(name):
    """ Load image and return image object"""
    fullname = os.path.join('data', name)
    try:
        image = pygame.image.load(fullname)
        if image.get_alpha() is None:
            image = image.convert()
        else:
            image = image.convert_alpha()
    except:
        print('Cannot load image:', fullname)
        raise SystemExit
    return image, image.get_rect()

pygame.image.load()与从文件中加载新图像所提供的功能相比,这里我们提供了一种更为复杂的图像加载功能。请注意,该函数的第一行是一个文档字符串,描述该函数的功能以及它返回的对象。该函数假定所有图像都在名为data的目录中,因此它将使用文件名并创建完整路径名,例如data/ball.png,使用os模块以确保跨平台兼容性。然后,它尝试加载图像,并转换任何alpha区域,以便获得透明性,如果存在问题,它将返回更容易理解的错误。最后,它返回图像对象及其rect

您可以执行类似的功能来加载其他资源,例如加载声音。您还可以创建资源处理类,以使用更复杂的资源为您提供更大的灵活性。例如,您可以制作一个音乐课,其__init__功能是加载声音(也许是从load_sound()功能中借用的),暂停音乐的功能和重新启动音乐的功能。另一个方便的资源处理类是用于网络连接。打开套接字,通过适当的安全性和错误检查传递数据,关闭套接字,手指地址和其他网络任务的功能可以使编写具有网络功能的游戏相对轻松。

请记住,这些函数/类的主要任务是确保在开始编写游戏对象类和主循环时,几乎没有任何事情要做。类继承可以使这些基本类特别方便。但是不要太过分;仅将由一个类使用的函数应写为该类的一部分,而不是全局函数。

3.4 游戏对象类

加载模块并编写资源处理功能后,您将要继续编写一些游戏对象。这样做的方法相当简单,尽管乍一看似乎很复杂。您为游戏中每种类型的对象编写一个类,然后为这些对象创建这些类的实例。然后,您可以使用这些类的方法来操作对象,从而为对象提供一些运动和交互功能。因此,您的游戏(采用伪代码)将如下所示:

#!/usr/bin/python

# [load modules here]

# [resource handling functions here]

class Ball:
    # [ball functions (methods) here]
    # [e.g. a function to calculate new position]
    # [and a function to check if it hits the side]

def main:
    # [initiate game environment here]

    # [create new object as instance of ball class]
    ball = Ball()

    while 1:
        # [check for user input]

        # [call ball's update function]
        ball.update()

当然,这是一个非常简单的示例,您需要放入所有代码,而不是那些放在方括号中的注释。但是您应该了解基本概念。您创建一个类,在其中放置了球的所有功能,包括__init__,它将创建球的所有属性,update将放到该位置,然后将其移动到新位置,然后将其移动到新位置。

然后,您可以为所有其他游戏对象创建更多类,然后创建它们的实例,以便可以在main函数和主程序循环中轻松处理它们。将其与在main 函数中启动球进行比较,然后具有许多无类函数来操纵设定的球对象,您将希望看到为什么使用类是一个优点:它允许您将每个对象的所有代码放入一个地方; 它使使用对象更容易;它使添加新对象并对其进行操作更加灵活。您可以为每个新的球对象简单地创建Ball类的新实例,而不是为每个新的球对象添加更多的代码。魔法!

3.4.1 球类
class Ball(pygame.sprite.Sprite):
    """A ball that will move across the screen
    Returns: ball object
    Functions: update, calcnewpos
    Attributes: area, vector"""

    def __init__(self, vector):
        pygame.sprite.Sprite.__init__(self)
        self.image, self.rect = load_png('ball.png')
        screen = pygame.display.get_surface()
        self.area = screen.get_rect()
        self.vector = vector

    def update(self):
        newpos = self.calcnewpos(self.rect,self.vector)
        self.rect = newpos

    def calcnewpos(self,rect,vector):
        (angle,z) = vector
        (dx,dy) = (z*math.cos(angle),z*math.sin(angle))
        return rect.move(dx,dy)

该类具有__init__设置球的update函数,将球的矩形更改为新位置的calcnewpos函数,以及根据球的当前位置和其向量计算球的新位置的函数

3.4.1.1 精灵(sprite)对象

您在游戏中渲染的每个图像都是一个sprite对象,因此,每个对象的类都应该继承Sprite该类。

这是Python非常好的特性之一,类继承。现在,Ball该类具有Sprite该类随附的所有功能,并且该类的任何对象实例都Ball将被Pygame注册为sprite。对于文本和背景(不会移动),可以将对象着色到背景上,而Pygame以不同的方式处理子对象,当您查看整个程序的代码时,您会看到。

基本上,您既创建了一个球对象,又为该球创建了一个精灵对象,然后在该精灵对象上调用了球形的update函数,从而更新了精灵。

精灵还为您提供确定两个对象是否碰撞的复杂方法。通常,您可能只需要检查主循环以查看其矩形是否重叠,但这将涉及大量代码,这将是浪费的,因为Sprite该类提供了两个函数(spritecollidegroupcollide)为您执行此操作。

3.4.1.2 矢量

球具有一个属性vector,该属性由angle和z(速度)组成,angle从x轴正半轴顺时针旋转计算

2.png
Pygame本身不支持角度移动,我们需要利用calcnewpos函数解析出球在x,y轴上移动的长度,告知sprite对象移动

3.5 用户控制对象

到目前为止,您可以创建一个Pygame窗口,并渲染一个将在屏幕上飞行的球。下一步是制作一些用户可以控制的球棒。

3.5.1 球棒类

球棒类的原理与球类相似。您需要一个__init__函数来初始化球(以便您可以为每个球棒创建对象实例);一个update函数,在将蝙蝠放到屏幕上之前对球棒进行逐帧更改;以及实现该类其他特性的函数。

class Bat(pygame.sprite.Sprite):
    """Movable tennis 'bat' with which one hits the ball
    Returns: bat object
    Functions: reinit, update, moveup, movedown
    Attributes: which, speed"""

    def __init__(self, side):
        pygame.sprite.Sprite.__init__(self)
        self.image, self.rect = load_png('bat.png')
        screen = pygame.display.get_surface()
        self.area = screen.get_rect()
        self.side = side
        self.speed = 10
        self.state = "still"
        self.reinit()

    def reinit(self):
        self.state = "still"
        self.movepos = [0,0]
        if self.side == "left":
            self.rect.midleft = self.area.midleft
        elif self.side == "right":
            self.rect.midright = self.area.midright

    def update(self):
        newpos = self.rect.move(self.movepos)
        if self.area.contains(newpos):
            self.rect = newpos
        pygame.event.pump()

    def moveup(self):
        self.movepos[1] = self.movepos[1] - (self.speed)
        self.state = "moveup"

    def movedown(self):
        self.movepos[1] = self.movepos[1] + (self.speed)
        self.state = "movedown"

movepos提供一对数字给予rect.move函数用于移动’rect’,当movepos值为(0,0),停止移动

3.5.1.1 event事件

那么我们如何知道玩家何时按下按键然后释放它们呢?有了Pygame事件队列系统,就可以虚拟了!
移动棒球的代码非常简单:

for event in pygame.event.get():
    if event.type == QUIT:
        return
    elif event.type == KEYDOWN:
        if event.key == K_UP:
            player.moveup()
        if event.key == K_DOWN:
            player.movedown()
    elif event.type == KEYUP:
        if event.key == K_UP or event.key == K_DOWN:
            player.movepos = [0,0]
            player.state = "still"

3.6 合并

到目前为止,您已经了解了构建简单游戏所需的所有基础知识。您应该了解如何创建Pygame对象,Pygame如何显示对象,它如何处理事件以及如何使用物理将一些运动引入游戏中。现在,我将向您展示如何使用所有这些代码块并将它们组合到一个可以运行的游戏中。我们首先需要的是让球击中屏幕的两侧,并使球棒能够击中球,否则就不会涉及太多的游戏了。我们使用Pygame的collision方法来做到这一点。

3.6.1 球击板边

使侧面弹跳的基本原理很容易掌握。您抓住球的四个角的坐标,并检查它们是否与屏幕边缘的x或y坐标相对应。因此,如果右上角和左上角的y坐标均为零,则您知道球当前位于屏幕的顶部边缘。在update确定球的新位置之后,我们将在函数中进行所有这些操作。

if not self.area.contains(newpos):
      tl = not self.area.collidepoint(newpos.topleft)
      tr = not self.area.collidepoint(newpos.topright)
      bl = not self.area.collidepoint(newpos.bottomleft)
      br = not self.area.collidepoint(newpos.bottomright)
      if tr and tl or (br and bl):
              angle = -angle
      if tl and bl:
              self.offcourt(player=2)
      if tr and br:
              self.offcourt(player=1)

self.vector = (angle,z)

关于collidepoint()函数

collidepoint()
	// test if a point is inside a rectangle
	collidepoint(x, y) -> bool

关于newpos.topleft
Rect对象具有几个虚拟属性,可用于移动和对齐Rect:

x,y
top, left, bottom, right
topleft, bottomleft, topright, bottomright
midtop, midleft, midbottom, midright
center, centerx, centery
size, width, height
w,h

所有这些属性都可以分配给:

rect1.right = 10
rect2.center = (20,30)

在这里,我们检查是否area 包含球的新位置(它总是应该包含该球的新位置,因此我们不需要一个else子句,尽管在其他情况下,您可能需要考虑该子句。然后,我们检查四个角的坐标是否为与该区域的边缘碰撞,并为每个结果创建对象。如果是,则对象的值为1或True;如果没有,则值为None或False。顶部或底部,如果有的话,我们可以改变球的方向。方便地,使用弧度可以通过简单地反转其正/负值来做到这一点,还可以检查球是否偏离了侧面,以及是否有偏离叫offcourt功能。在我的游戏中,这会重置球,在调用函数时将指定的玩家的得分加1分,并显示新的得分。

最后,我们根据新角度重新编译向量。就是这样。球现在会从墙壁上快乐地反弹,并以优雅的姿态离开场外。

3.6.2 球击球棒

使球击中球棒与使其击中屏幕两侧非常相似。我们仍然使用碰撞方法,但是这次我们检查一下球和两个球棒的矩形是否碰撞。在这段代码中,我还添加了一些额外的代码来避免各种故障。您会发现您必须放入各种额外的代码,以避免出现故障和错误,因此习惯于看到它是很好的。

else:
    # Deflate the rectangles so you can't catch a ball behind the bat
    player1.rect.inflate(-3, -3)
    player2.rect.inflate(-3, -3)

    # Do ball and bat collide?
    # Note I put in an odd rule that sets self.hit to 1 when they collide, and unsets it in the next
    # iteration. this is to stop odd ball behaviour where it finds a collision *inside* the
    # bat, the ball reverses, and is still inside the bat, so bounces around inside.
    # This way, the ball can always escape and bounce away cleanly
    if self.rect.colliderect(player1.rect) == 1 and not self.hit:
        angle = math.pi - angle
        self.hit = not self.hit
    elif self.rect.colliderect(player2.rect) == 1 and not self.hit:
        angle = math.pi - angle
        self.hit = not self.hit
    elif self.hit:
        self.hit = not self.hit
self.vector = (angle,z)

我们从一条else语句开始,因为这是从前面的代码块开始进行的,以检查球是否碰到侧面。有道理的是,如果它没有碰到侧面,则可能会击中蝙蝠,因此我们继续执行条件语句。要解决的第一个故障是将玩家的矩形在两个维度上缩小3个像素,以阻止蝙蝠接住后面的球(如果您想像一下,只要移动蝙蝠以使球在其后方移动,矩形重叠,因此通常情况下,球会被“击中”-这样可以避免这种情况)。

接下来,我们检查矩形是否发生碰撞,并修复另一个毛刺。请注意,我已经对代码的这些奇数位进行了评论-对于其他正在查看您的代码的人来说,解释异常的代码位总是一件好事,因此当您回到代码中时,便可以理解它。如果没有解决问题,球可能会击中球棒的一角,改变方向,一帧之后仍会发现自己位于球棒内。然后它会再次认为它已被击中,并改变其方向。这可能会发生多次,使球的运动完全不现实。因此,我们有一个变量,当它被命中时self.hit我们将其设置为True它,然后是False一帧。当我们检查矩形是否发生碰撞时,我们还将检查是否self.hit为True/ False,以停止内部反弹。

这里的重要代码很容易理解。所有矩形都有一个colliderect 函数,您可以在其中输入另一个对象的矩形,True如果矩形确实重叠,False则返回该函数,否则返回。如果这样做,我们可以通过从中减去当前角度来改变方向pi(再次,您可以使用弧度来做一个方便的技巧,它将把角度调整90度并向正确的方向发送;此时您可能会发现才能对弧度有透彻的了解!)只是为了完成故障检查,我们在击中它们之后切换self.hit回False原来的框架。

然后,我们也重新编译向量。当然,您将希望删除前一段代码中的同一行,以便只在if-else条件语句之后执行一次。就是这样!组合的代码现在将允许球击中边和击球。

3.6.3 全部代码

#
# Tom's Pong
# A simple pong game with realistic physics and AI
# http://www.tomchance.uklinux.net/projects/pong.shtml
#
# Released under the GNU General Public License

import sys
import random
import math
import os
import getopt
import pygame
from socket import *
from pygame.locals import *


def load_png(name):
    """ Load image and return image object"""
    fullname = os.path.join('data', name)
    try:
        image = pygame.image.load(fullname)
        if image.get_alpha is None:
            image = image.convert()
        else:
            image = image.convert_alpha()
    except:
        print('Cannot load image:', fullname)

    return image, image.get_rect()


class Ball(pygame.sprite.Sprite):
    """A ball that will move across the screen
    Returns: ball object
    Functions: update, calcnewpos
    Attributes: area, vector"""
    # area <Rect> means the size of screen on minotor

    def __init__(self, vector):
        pygame.sprite.Sprite.__init__(self)
        self.image, self.rect = load_png('ball.png')
        screen = pygame.display.get_surface()
        self.area = screen.get_rect()
        self.vector = vector
        self.hit = 0

    def update(self):
        newpos = self.calcnewpos(self.rect, self.vector)
        self.rect = newpos
        (angle, z) = self.vector

        if not self.area.contains(newpos):
            tl = not self.area.collidepoint(newpos.topleft)
            tr = not self.area.collidepoint(newpos.topright)
            bl = not self.area.collidepoint(newpos.bottomleft)
            br = not self.area.collidepoint(newpos.bottomright)
            if tr and tl or (br and bl):
                angle = -angle
            if tl and bl:
                # self.offcourt()
                angle = math.pi - angle
            if tr and br:
                angle = math.pi - angle
                # self.offcourt()
        else:
            # Deflate the rectangles so you can't catch a ball behind the bat
            player1.rect.inflate(-3, -3)
            player2.rect.inflate(-3, -3)

            # Do ball and bat collide?
            # Note I put in an odd rule that sets self.hit to 1 when they collide, and unsets it in the next
            # iteration. this is to stop odd ball behaviour where it finds a collision *inside* the
            # bat, the ball reverses, and is still inside the bat, so bounces around inside.
            # This way, the ball can always escape and bounce away cleanly
            if self.rect.colliderect(player1.rect) == 1 and not self.hit:
                angle = math.pi - angle
                self.hit = not self.hit
            elif self.rect.colliderect(player2.rect) == 1 and not self.hit:
                angle = math.pi - angle
                self.hit = not self.hit
            elif self.hit:
                self.hit = not self.hit
        self.vector = (angle, z)

    def calcnewpos(self, rect, vector):
        (angle, z) = vector
        (dx, dy) = (z*math.cos(angle), z*math.sin(angle))
        return rect.move(dx, dy)


class Bat(pygame.sprite.Sprite):
    """Movable tennis 'bat' with which one hits the ball
    Returns: bat object
    Functions: reinit, update, moveup, movedown
    Attributes: which, speed"""

    def __init__(self, side):
        pygame.sprite.Sprite.__init__(self)
        self.image, self.rect = load_png('bat.png')
        screen = pygame.display.get_surface()
        self.area = screen.get_rect()
        self.side = side
        self.speed = 10
        self.state = "still"
        self.reinit()

    def reinit(self):
        self.state = "still"
        self.movepos = [0, 0]
        if self.side == "left":
            self.rect.midleft = self.area.midleft
        elif self.side == "right":
            self.rect.midright = self.area.midright

    def update(self):
        newpos = self.rect.move(self.movepos)
        if self.area.contains(newpos):
            self.rect = newpos
        pygame.event.pump()

    def moveup(self):
        self.movepos[1] = self.movepos[1] - (self.speed)
        self.state = "moveup"

    def movedown(self):
        self.movepos[1] = self.movepos[1] + (self.speed)
        self.state = "movedown"


def main():
    # Initialise screen
    pygame.init()
    screen = pygame.display.set_mode((640, 480))
    pygame.display.set_caption('Basic Pong')

    # Fill background
    background = pygame.Surface(screen.get_size())
    background = background.convert()
    background.fill((0, 0, 0))

    # Initialise players
    global player1
    global player2
    player1 = Bat("left")
    player2 = Bat("right")

    # Initialise ball
    speed = 13
    rand = ((0.1 * (random.randint(5, 8))))
    ball = Ball((0.47, speed))

    # Initialise sprites
    playersprites = pygame.sprite.Group((player1, player2))
    ballsprite = pygame.sprite.Group(ball)

    # Blit everything to the screen
    screen.blit(background, (0, 0))
    pygame.display.flip()

    # Initialise clock
    clock = pygame.time.Clock()

    # Event loop
    while 1:
        # Make sure game doesn't run at more than 60 frames per second
        clock.tick(60)

        for event in pygame.event.get():
            if event.type == QUIT:
                return
            elif event.type == KEYDOWN:
                if event.key == K_a:
                    player1.moveup()
                if event.key == K_z:
                    player1.movedown()
                if event.key == K_UP:
                    player2.moveup()
                if event.key == K_DOWN:
                    player2.movedown()
            elif event.type == KEYUP:
                if event.key == K_a or event.key == K_z:
                    player1.movepos = [0, 0]
                    player1.state = "still"
                if event.key == K_UP or event.key == K_DOWN:
                    player2.movepos = [0, 0]
                    player2.state = "still"

        screen.blit(background, ball.rect, ball.rect)
        screen.blit(background, player1.rect, player1.rect)
        screen.blit(background, player2.rect, player2.rect)
        ballsprite.update()
        playersprites.update()
        ballsprite.draw(screen)

        playersprites.draw(screen)
        pygame.display.flip()


if __name__ == '__main__':
    main()

除了向您展示最终产品之外,我还将带您回到TomPong,所有这些都基于此。下载它,看一下源代码,您将看到使用本教程中看到的所有代码以及我在各种版本中添加的许多其他代码的pong的完整实现。作为旋转的一些额外物理方法,以及各种其他错误和故障修复。

哦,可以在http://www.tomchance.uklinux.net/projects/pong.shtml上找到TomPong 。

呼,在辛苦了两期后,我们又成功学习了棒球游戏的设计,休息一下,下期见

本系列总目录:https://blog.csdn.net/wxlxy316/article/details/104246724

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值