Seth Kenlon 发布的文章

6 月 15 日,你可以在当地的游戏商家庆祝桌面角色扮演游戏并获得免费的 RPG 资料。

(LCTT 译注:“ 免费 RPG 日 Free RPG Day ”是受“ 免费漫画书日 Free Comic Book Day ”启发而发起的庆祝活动,从 2007 年开始已经举办多次。这里的 RPG 游戏并非我们通常所指的电脑 RPG 游戏,而是指使用纸和笔的桌面游戏,是一种西方传统游戏形式。)

你有没有想过尝试一下《 龙与地下城 Dungeons & Dragons 》,但不知道如何开始?你是否在年轻时玩过《 开拓者 Pathfinder 》并一直在考虑重返快乐时光?你是否对角色扮演游戏(RPG)感到好奇,但不确定你是否想玩一个?你是否对桌面游戏的概念完全陌生,直到现在才听说过这种 RPG 游戏?无论是哪一个并不重要,因为免费 RPG 日适合所有人!

第一个免费 RPG 日活动发生在 2007 年,是由世界各地的桌面游戏商家举办的。这个想法是以 0 美元的价格为新手和有经验的游戏玩家带来新的、独家的 RPG 快速入门规则和冒险体验。在这样的一天里,你可以走进当地的桌面游戏商家,得到一本小册子,其中包含桌面 RPG 的简单的初学者规则,你可以在商家里与那里的人或者回家与朋友一起玩。这本小册子是给你的,应该一直留着的。

这一活动如此的受欢迎,此后该传统一直延续至今。今年,免费 RPG 日定于 6 月 15 日星期六举行。

有什么收获?

显然,免费 RPG 日背后的想法是让你沉迷于桌面 RPG 游戏。但在你本能的犬儒主义开始之前,考虑到它会慢慢上瘾,爱上一个鼓励你阅读规则和知识的游戏并不太糟,这样你和你的家人、朋友就有了共度时光的借口了。桌面 RPG 是一个功能强大、富有想象力和有趣的媒介,而免费 RPG 日则是对这种游戏很好的介绍。

 title=

开源游戏

像许多其他行业一样,开源现象影响了桌面游戏。回到世纪之交,《Magic:The Gathering and Dungeons&Dragons》 的提供者 威世智公司 Wizards of the Coast 决定通过开发 开源游戏许可证 Open Game License (OGL)来采用开源方法。他们将此许可证用于世界上第一个 RPG(《 龙与地下城 Dungeons & Dragons 》,D&D)的版本 3 和 3.5。几年后,当他们在第四版上(对开源)产生了动摇时,《 Dragon 》杂志的出版商复刻了 D&D 3.5 的“代码”,将其混制版本发布为《 开拓者 Pathfinder 》 RPG,从而保持了创新和整个第三方游戏开发者产业的健康发展。最近,威世智公司在 D&D 5e 版本中才又重回了 OGL。

OGL 允许开发人员至少可以在他们自己产品中使用该游戏的机制。不管你可以不可以使用自定义怪物、武器、王国或流行角色的名称,但你可以随时使用 OGL 游戏的规则和数学计算。事实上,OGL 游戏的规则通常作为系统参考文档(SRD)免费发布的,因此,无论你是否购买了规则书的副本,你都可以了解游戏的玩法。

如果你之前从未玩过桌面 RPG,那么使用笔和纸玩的游戏也可以拥有游戏引擎似乎很奇怪,但计算就是计算,不管是数字的还是模拟的。作为一个简单的例子:假设游戏引擎规定玩家角色有一个代表其力量的数字。当那个玩家角色与一个有其两倍力量的巨人战斗时,在玩家掷骰子以增加她的角色的力量攻击时,真的会感到紧张。如果没有掷出一个很好的点数的话,她的力量将无法与巨人相匹敌。知道了这一点,第三方或独立开发者就可以为这个游戏引擎设计一个怪物,同时了解骰子滚动可能对玩家的能力得分产生的影响。这意味着他们可以根据游戏引擎的优先级进行数学计算。他们可以设计一系列用来杀死的怪物,在游戏引擎的环境中它们具有有意义的能力和技能,并且他们可以宣称与该引擎的兼容性。

此外,OGL 允许出版商为其材料定义产品标识。产品标识可以是出版物的商业外观(图形元素和布局)、徽标、术语、传说、专有名称等。未经出版商同意,任何定义为产品标识的内容都可能无法重复使用。例如,假设一个出版商发行了一本武器手册,其中包括一个名为 Sigint 的魔法砍刀,它对所有针对僵尸的攻击都给予 +2 魔法附加攻击值。这个特性来自一个故事,该砍刀是一个具有潜伏的僵尸基因的科学家锻造的。但是,该出版物在 OGL 第 1e 节中列出的所有武器的正确名称都被保留为产品标识。这意味着你可以在自己的出版物中使用该数字(武器的持久性、它所造成的伤害,+2 魔法奖励等等)以及与该武器相关的传说(它由一个潜伏的僵尸锻造),但是你不能使用该武器的名称(Sigint)。

OGL 是一个非常灵活的许可证,因此开发人员必须仔细阅读其第 1e 节。 一些出版商只保留出版物本身的布局,而其他出版商保留除数字和最通用术语之外的所有内容。

当卓越的 RPG 特许经营权拥抱开源时,至今仍能感受到的它给整个行业掀起的波澜。第三方开发人员可以为 5e 和《开拓者》系统创建内容。由威世智公司创建的整个 DungeonMastersGuild.com 网站为 D&D 5e 制作了独立内容,旨在促进独立出版。StarfinderOpenD6战士,盗贼和法师剑与巫师 等及很多其它游戏都采用了 OGL。其他系统,如 Brent Newhall 的 《Dungeon Delvers》、《Fate》、《Dungeon World》 等等,都是根据知识共享许可授权的的。

获取你的 RPG

在免费 RPG 日,你可以前往当地游戏商铺,玩 RPG 以及获取与朋友将来一起玩的 RPG 游戏材料。就像 Linux 安装节 Linux installfest 软件自由日 Software Freedom Day 一样,该活动的定义很松散。每个商家举办的自由 RPG 日都有所不同,每个商家都可以玩他们选择的任何游戏。但是,游戏发行商捐赠的免费内容每年都是相同的。显然,免费的东西视情况而定,但是当你参加免费 RPG 日活动时,请注意有多少游戏采用了开源许可证(如果是 OGL 游戏,OGL 会打印在书背面)。《开拓者》、《Starfinder》 和 D&D 的任何内容肯定都会带有 OGL 的一些优势。许多其他系统的内容使用知识共享许可。有些则像 90 年代复活的 Dead Earth RPG 一样,使用 GNU 自由文档许可证

有大量的游戏资源是通过开源许可证开发的。你可能需要也可能不需要关心游戏的许可证;毕竟,许可证与你是否可以与朋友一起玩无关。但是如果你喜欢支持自由文化而不仅仅是你运行的软件,那么试试一些 OGL 或知识共享游戏吧。如果你不熟悉游戏,请在免费 RPG 日在当地游戏商家试玩桌面 RPG 游戏!


via: https://opensource.com/article/19/5/free-rpg-day

作者:Seth Kenlon 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在这个从零构建一个 Python 游戏系列的第六部分中,为你的角色创建一些平台来旅行。

这是仍在进行中的关于使用 Pygame 模块来在 Python 3 中创建电脑游戏的系列文章的第六部分。先前的文章是:

一个平台类游戏需要平台。

Pygame 中,平台本身也是个妖精,正像你那个可玩的妖精。这一点是重要的,因为有个是对象的平台,可以使你的玩家妖精更容易与之互动。

创建平台有两个主要步骤。首先,你必须给该对象编写代码,然后,你必须映射出你希望该对象出现的位置。

编码平台对象

要构建一个平台对象,你要创建一个名为 Platform 的类。它是一个妖精,正像你的 Player 妖精 一样,带有很多相同的属性。

你的 Platform 类需要知道很多平台类型的信息,它应该出现在游戏世界的哪里、它应该包含的什么图片等等。这其中很多信息可能还尚不存在,这要看你为你的游戏计划了多少,但是没有关系。正如直到移动你的游戏角色那篇文章结束时,你都没有告诉你的玩家妖精移动速度有多快,你不必事先告诉 Platform 每一件事。

在这系列中你所写的脚本的开头附近,创建一个新的类。在这个代码示例中前三行是用于说明上下文,因此在注释的下面添加代码:

import pygame
import sys
import os
## 新代码如下:

class Platform(pygame.sprite.Sprite):
# x location, y location, img width, img height, img file    
def __init__(self,xloc,yloc,imgw,imgh,img):
    pygame.sprite.Sprite.__init__(self)
    self.image = pygame.image.load(os.path.join('images',img)).convert()
    self.image.convert_alpha()
    self.image.set_colorkey(ALPHA)
    self.rect = self.image.get_rect()
    self.rect.y = yloc
    self.rect.x = xloc

当被调用时,这个类在某个 X 和 Y 位置上创建一个屏上对象,具有某种宽度和高度,并使用某种图像作为纹理。这与如何在屏上绘制出玩家或敌人非常类似。

平台的类型

下一步是绘制出你的平台需要出现的地方。

瓷砖方式

实现平台类游戏世界有几种不同的方法。在最初的横向滚轴游戏中,例如,马里奥超级兄弟和刺猬索尼克,这个技巧是使用“瓷砖”方式,也就是说有几个代表地面和各种平台的块,并且这些块被重复使用来制作一个关卡。你只能有 8 或 12 种不同的块,你可以将它们排列在屏幕上来创建地面、浮动的平台,以及你游戏中需要的一切其它的事物。有人发现这是制作游戏最容易的方法了,因为你只需要制作(或下载)一小组关卡素材就能创建很多不同的关卡。然而,这里的代码需要一点数学知识。

 title=

SuperTux ,一个基于瓷砖的电脑游戏。

手工绘制方式

另一种方法是将每个素材作为一个整体图像。如果你喜欢为游戏世界创建素材,那你会在用图形应用程序构建游戏世界的每个部分上花费很多时间。这种方法不需要太多的数学知识,因为所有的平台都是整体的、完整的对象,你只需要告诉 Python 将它们放在屏幕上的什么位置。

每种方法都有优势和劣势,并且根据于你选择使用的方式,代码稍有不同。我将覆盖这两方面,所以你可以在你的工程中使用一种或另一种,甚至两者的混合。

关卡绘制

总的来说,绘制你的游戏世界是关卡设计和游戏编程中的一个重要的部分。这需要数学知识,但是没有什么太难的,而且 Python 擅长数学,它会有所帮助。

你也许发现先在纸张上设计是有用的。拿一张表格纸,并绘制一个方框来代表你的游戏窗体。在方框中绘制平台,并标记其每一个平台的 X 和 Y 坐标,以及它的宽度和高度。在方框中的实际位置没有必要是精确的,你只要保持数字合理即可。譬如,假设你的屏幕是 720 像素宽,那么你不能在一个屏幕上放 8 块 100 像素的平台。

当然,不是你游戏中的所有平台都必须容纳在一个屏幕大小的方框里,因为你的游戏将随着你的玩家行走而滚动。所以,可以继续绘制你的游戏世界到第一屏幕的右侧,直到关卡结束。

如果你更喜欢精确一点,你可以使用方格纸。当设计一个瓷砖类的游戏时,这是特别有用的,因为每个方格可以代表一个瓷砖。

 title=

一个关卡地图示例。

坐标系

你可能已经在学校中学习过笛卡尔坐标系。你学习的东西也适用于 Pygame,除了在 Pygame 中你的游戏世界的坐标系的原点 0,0 是放置在你的屏幕的左上角而不是在中间,是你在地理课上用过的坐标是在中间的。

 title=

在 Pygame 中的坐标示例。

X 轴起始于最左边的 0,向右无限增加。Y 轴起始于屏幕顶部的 0,向下延伸。

图片大小

如果你不知道你的玩家、敌人、平台是多大的,绘制出一个游戏世界是毫无意义的。你可以在图形程序中找到你的平台或瓷砖的尺寸。例如在 Krita 中,单击“图像”菜单,并选择“属性”。你可以在“属性”窗口的最顶部处找到它的尺寸。

另外,你也可以创建一个简单的 Python 脚本来告诉你的一个图像的尺寸。打开一个新的文本文件,并输入这些代码到其中:

#!/usr/bin/env python3

from PIL import Image
import os.path
import sys

if len(sys.argv) > 1:
    print(sys.argv[1])
else:
    sys.exit('Syntax: identify.py [filename]')

pic = sys.argv[1]
dim = Image.open(pic)
X   = dim.size[0]
Y   = dim.size[1]

print(X,Y)

保存该文本文件为 identify.py

要使用这个脚本,你必须安装一些额外的 Python 模块,它们包含了这个脚本中新使用的关键字:

$ pip3 install Pillow --user

一旦安装好,在你游戏工程目录中运行这个脚本:

$ python3 ./identify.py images/ground.png
(1080, 97)

在这个示例中,地面平台的图形的大小是 1080 像素宽和 97 像素高。

平台块

如果你选择单独地绘制每个素材,你必须创建想要插入到你的游戏世界中的几个平台和其它元素,每个素材都放在它自己的文件中。换句话说,你应该让每个素材都有一个文件,像这样:

 title=

每个对象一个图形文件。

你可以按照你希望的次数重复使用每个平台,只要确保每个文件仅包含一个平台。你不能使用一个文件包含全部素材,像这样:

 title=

你的关卡不能是一个图形文件。

当你完成时,你可能希望你的游戏看起来像这样,但是如果你在一个大文件中创建你的关卡,你就没有方法从背景中区分出一个平台,因此,要么把对象绘制在它们自己的文件中,要么从一个更大的文件中裁剪出它们,并保存为单独的副本。

注意: 如同你的其它素材,你可以使用 GIMP、Krita、MyPaint,或 Inkscape 来创建你的游戏素材。

平台出现在每个关卡开始的屏幕上,因此你必须在你的 Level 类中添加一个 platform 函数。在这里特例是地面平台,它重要到应该拥有它自己的一个组。通过把地面看作一组特殊类型的平台,你可以选择它是否滚动,或它上面是否可以站立,而其它平台可以漂浮在它上面。这取决于你。

添加这两个函数到你的 Level 类:

def ground(lvl,x,y,w,h):
    ground_list = pygame.sprite.Group()
    if lvl == 1:
        ground = Platform(x,y,w,h,'block-ground.png')
        ground_list.add(ground)

    if lvl == 2:
        print("Level " + str(lvl) )

    return ground_list

def platform( lvl ):
    plat_list = pygame.sprite.Group()
    if lvl == 1:
        plat = Platform(200, worldy-97-128, 285,67,'block-big.png')
        plat_list.add(plat)
        plat = Platform(500, worldy-97-320, 197,54,'block-small.png')
        plat_list.add(plat)
    if lvl == 2:
        print("Level " + str(lvl) )
       
    return plat_list

ground 函数需要一个 X 和 Y 位置,以便 Pygame 知道在哪里放置地面平台。它也需要知道平台的宽度和高度,这样 Pygame 知道地面延伸到每个方向有多远。该函数使用你的 Platform 类来生成一个屏上对象,然后将这个对象添加到 ground_list 组。

platform 函数本质上是相同的,除了其有更多的平台。在这个示例中,仅有两个平台,但是你可以想有多少就有多少。在进入一个平台后,在列出另一个前你必须添加它到 plat_list 中。如果你不添加平台到组中,那么它将不出现在你的游戏中。

提示: 很难想象你的游戏世界的 0 是在顶部,因为在真实世界中发生的情况是相反的;当估计你有多高时,你不会从上往下测量你自己,而是从脚到头顶来测量。

如果对你来说从“地面”上来构建你的游戏世界更容易,将 Y 轴值表示为负数可能有帮助。例如,你知道你的游戏世界的底部是 worldy 的值。因此 worldy 减去地面的高度(在这个示例中是 97)是你的玩家正常站立的位置。如果你的角色是 64 像素高,那么地面减去 128 正好是你的玩家的两倍高。事实上,一个放置在 128 像素处平台大约是相对于你的玩家的两层楼高度。一个平台在 -320 处比三层楼更高。等等。

正像你现在可能所知的,如果你不使用它们,你的类和函数是没有价值的。添加这些代码到你的设置部分(第一行只是上下文,所以添加最后两行):

enemy_list  = Level.bad( 1, eloc )
ground_list = Level.ground( 1,0,worldy-97,1080,97 )
plat_list   = Level.platform( 1 )

并把这些行加到你的主循环(再一次,第一行仅用于上下文):

enemy_list.draw(world)  # 刷新敌人
ground_list.draw(world)  # 刷新地面
plat_list.draw(world)  # 刷新平台

瓷砖平台

瓷砖类游戏世界更容易制作,因为你只需要在前面绘制一些块,就能在游戏中一再使用它们创建每个平台。在像 OpenGameArt.org 这样的网站上甚至有一套瓷砖供你来使用。

Platform 类与在前面部分中的类是相同的。

groundplatformLevel 类中,然而,必须使用循环来计算使用多少块来创建每个平台。

如果你打算在你的游戏世界中有一个坚固的地面,这种地面是很简单的。你只需要从整个窗口的一边到另一边“克隆”你的地面瓷砖。例如,你可以创建一个 X 和 Y 值的列表来规定每个瓷砖应该放置的位置,然后使用一个循环来获取每个值并绘制每一个瓷砖。这仅是一个示例,所以不要添加这到你的代码:

# Do not add this to your code
gloc = [0,656,64,656,128,656,192,656,256,656,320,656,384,656]

不过,如果你仔细看,你可以看到所有的 Y 值是相同的,X 值以 64 的增量不断地增加 —— 这就是瓷砖的大小。这种重复是精确地,是计算机擅长的,因此你可以使用一点数学逻辑来让计算机为你做所有的计算:

添加这些到你的脚本的设置部分:

gloc = []
tx   = 64
ty   = 64

i=0
while i <= (worldx/tx)+tx:
    gloc.append(i*tx)
    i=i+1

ground_list = Level.ground( 1,gloc,tx,ty )

现在,不管你的窗口的大小,Python 会通过瓷砖的宽度分割游戏世界的宽度,并创建一个数组列表列出每个 X 值。这里不计算 Y 值,因为在平的地面上这个从不会变化。

为了在一个函数中使用数组,使用一个 while 循环,查看每个条目并在适当的位置添加一个地面瓷砖:

def ground(lvl,gloc,tx,ty):
    ground_list = pygame.sprite.Group()
    i=0
    if lvl == 1:
        while i < len(gloc):
            ground = Platform(gloc[i],worldy-ty,tx,ty,'tile-ground.png')
            ground_list.add(ground)
            i=i+1

    if lvl == 2:
        print("Level " + str(lvl) )

    return ground_list

除了 while 循环,这几乎与在上面一部分中提供的瓷砖类平台的 ground 函数的代码相同。

对于移动的平台,原理是相似的,但是这里有一些技巧可以使它简单。

你可以通过它的起始像素(它的 X 值)、距地面的高度(它的 Y 值)、绘制多少瓷砖来定义一个平台,而不是通过像素绘制每个平台。这样,你不必操心每个平台的宽度和高度。

这个技巧的逻辑有一点复杂,因此请仔细复制这些代码。有一个 while 循环嵌套在另一个 while 循环的内部,因为这个函数必须考虑每个数组项的三个值来成功地建造一个完整的平台。在这个示例中,这里仅有三个平台以 ploc.append 语句定义,但是你的游戏可能需要更多,因此你需要多少就定义多少。当然,有一些不会出现,因为它们远在屏幕外,但是一旦当你进行滚动时,它们将呈现在眼前。

def platform(lvl,tx,ty):
    plat_list = pygame.sprite.Group()
    ploc = []
    i=0
    if lvl == 1:
        ploc.append((200,worldy-ty-128,3))
        ploc.append((300,worldy-ty-256,3))
        ploc.append((500,worldy-ty-128,4))
        while i < len(ploc):
            j=0
            while j <= ploc[i][2]:
                plat = Platform((ploc[i][0]+(j*tx)),ploc[i][1],tx,ty,'tile.png')
                plat_list.add(plat)
                j=j+1
            print('run' + str(i) + str(ploc[i]))
            i=i+1
           
    if lvl == 2:
        print("Level " + str(lvl) )

    return plat_list

要让这些平台出现在你的游戏世界,它们必须出现在你的主循环中。如果你还没有这样做,添加这些行到你的主循环(再一次,第一行仅被用于上下文)中:

        enemy_list.draw(world)  # 刷新敌人
        ground_list.draw(world) # 刷新地面
        plat_list.draw(world)   # 刷新平台

启动你的游戏,根据需要调整你的平台的放置位置。如果你看不见屏幕外产生的平台,不要担心;你不久后就可以修复它。

到目前为止,这是游戏的图片和代码:

 title=

到目前为止,我们的 Pygame 平台。

#!/usr/bin/env python3
# draw a world
# add a player and player control
# add player movement
# add enemy and basic collision
# add platform

# GNU All-Permissive License
# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided the copyright
# notice and this notice are preserved.  This file is offered as-is,
# without any warranty.

import pygame
import sys
import os

'''
Objects
'''

class Platform(pygame.sprite.Sprite):
    # x location, y location, img width, img height, img file    
    def __init__(self,xloc,yloc,imgw,imgh,img):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(os.path.join('images',img)).convert()
        self.image.convert_alpha()
        self.rect = self.image.get_rect()
        self.rect.y = yloc
        self.rect.x = xloc

class Player(pygame.sprite.Sprite):
    '''
    Spawn a player
    '''
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.movex = 0
        self.movey = 0
        self.frame = 0
        self.health = 10
        self.score = 1
        self.images = []
        for i in range(1,9):
            img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert()
            img.convert_alpha()
            img.set_colorkey(ALPHA)
            self.images.append(img)
            self.image = self.images[0]
            self.rect  = self.image.get_rect()

    def control(self,x,y):
        '''
        control player movement
        '''
        self.movex += x
        self.movey += y

    def update(self):
        '''
        Update sprite position
        '''

        self.rect.x = self.rect.x + self.movex
        self.rect.y = self.rect.y + self.movey

        # moving left
        if self.movex < 0:
            self.frame += 1
            if self.frame > ani*3:
                self.frame = 0
            self.image = self.images[self.frame//ani]

        # moving right
        if self.movex > 0:
            self.frame += 1
            if self.frame > ani*3:
                self.frame = 0
            self.image = self.images[(self.frame//ani)+4]

        # collisions
        enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False)
        for enemy in enemy_hit_list:
            self.health -= 1
            print(self.health)

        ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False)
        for g in ground_hit_list:
            self.health -= 1
            print(self.health)


class Enemy(pygame.sprite.Sprite):
    '''
    Spawn an enemy
    '''
    def __init__(self,x,y,img):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(os.path.join('images',img))
        #self.image.convert_alpha()
        #self.image.set_colorkey(ALPHA)
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.counter = 0

    def move(self):
        '''
        enemy movement
        '''
        distance = 80
        speed = 8

        if self.counter >= 0 and self.counter <= distance:
            self.rect.x += speed
        elif self.counter >= distance and self.counter <= distance*2:
            self.rect.x -= speed
        else:
            self.counter = 0

        self.counter += 1

class Level():
    def bad(lvl,eloc):
        if lvl == 1:
            enemy = Enemy(eloc[0],eloc[1],'yeti.png') # spawn enemy
            enemy_list = pygame.sprite.Group() # create enemy group
            enemy_list.add(enemy)              # add enemy to group

        if lvl == 2:
            print("Level " + str(lvl) )

        return enemy_list

    def loot(lvl,lloc):
        print(lvl)

    def ground(lvl,gloc,tx,ty):
        ground_list = pygame.sprite.Group()
        i=0
        if lvl == 1:
            while i < len(gloc):
                print("blockgen:" + str(i))
                ground = Platform(gloc[i],worldy-ty,tx,ty,'ground.png')
                ground_list.add(ground)
                i=i+1

        if lvl == 2:
            print("Level " + str(lvl) )

        return ground_list

'''
Setup
'''
worldx = 960
worldy = 720

fps = 40 # frame rate
ani = 4  # animation cycles
clock = pygame.time.Clock()
pygame.init()
main = True

BLUE  = (25,25,200)
BLACK = (23,23,23 )
WHITE = (254,254,254)
ALPHA = (0,255,0)

world = pygame.display.set_mode([worldx,worldy])
backdrop = pygame.image.load(os.path.join('images','stage.png')).convert()
backdropbox = world.get_rect()
player = Player() # spawn player
player.rect.x = 0
player.rect.y = 0
player_list = pygame.sprite.Group()
player_list.add(player)
steps = 10 # how fast to move

eloc = []
eloc = [200,20]
gloc = []
#gloc = [0,630,64,630,128,630,192,630,256,630,320,630,384,630]
tx = 64 #tile size
ty = 64 #tile size

i=0
while i <= (worldx/tx)+tx:
    gloc.append(i*tx)
    i=i+1
    print("block: " + str(i))

enemy_list = Level.bad( 1, eloc )
ground_list = Level.ground( 1,gloc,tx,ty )

'''
Main loop
'''
while main == True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit(); sys.exit()
            main = False

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                player.control(-steps,0)
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                player.control(steps,0)
            if event.key == pygame.K_UP or event.key == ord('w'):
                print('jump')

        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                player.control(steps,0)
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                player.control(-steps,0)
            if event.key == ord('q'):
                pygame.quit()
                sys.exit()
                main = False

#    world.fill(BLACK)
    world.blit(backdrop, backdropbox)
    player.update()
    player_list.draw(world) #refresh player position
    enemy_list.draw(world)  # refresh enemies
    ground_list.draw(world)  # refresh enemies
    for e in enemy_list:
        e.move()
    pygame.display.flip()
    clock.tick(fps)

(LCTT 译注:到本文翻译完为止,该系列已经近一年没有继续更新了~)


via: https://opensource.com/article/18/7/put-platforms-python-game

作者:Seth Kenlon 选题:lujun9972 译者:robsean 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在本系列的第五部分,学习如何增加一个坏蛋与你的好人战斗。

在本系列的前几篇文章中(参见 第一部分第二部分第三部分 以及 第四部分),你已经学习了如何使用 Pygame 和 Python 在一个空白的视频游戏世界中生成一个可玩的角色。但没有恶棍,英雄又将如何?

如果你没有敌人,那将会是一个非常无聊的游戏。所以在此篇文章中,你将为你的游戏添加一个敌人并构建一个用于创建关卡的框架。

在对玩家妖精实现全部功能之前,就来实现一个敌人似乎就很奇怪。但你已经学到了很多东西,创造恶棍与与创造玩家妖精非常相似。所以放轻松,使用你已经掌握的知识,看看能挑起怎样一些麻烦。

针对本次训练,你能够从 Open Game Art 下载一些预创建的素材。此处是我使用的一些素材:

  • 印加花砖(LCTT 译注:游戏中使用的花砖贴图)
  • 一些侵略者
  • 妖精、角色、物体以及特效

创造敌方妖精

是的,不管你意识到与否,你其实已经知道如何去实现敌人。这个过程与创造一个玩家妖精非常相似:

  1. 创建一个类用于敌人生成
  2. 创建 update 方法使得敌人能够检测碰撞
  3. 创建 move 方法使得敌人能够四处游荡

从类入手。从概念上看,它与你的 Player 类大体相同。你设置一张或者一组图片,然后设置妖精的初始位置。

在继续下一步之前,确保你有一张你的敌人的图像,即使只是一张临时图像。将图像放在你的游戏项目的 images 目录(你放置你的玩家图像的相同目录)。

如果所有的活物都拥有动画,那么游戏看起来会好得多。为敌方妖精设置动画与为玩家妖精设置动画具有相同的方式。但现在,为了保持简单,我们使用一个没有动画的妖精。

在你代码 objects 节的顶部,使用以下代码创建一个叫做 Enemy 的类:

class Enemy(pygame.sprite.Sprite):
    '''
    生成一个敌人
    '''
    def __init__(self,x,y,img):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(os.path.join('images',img))
        self.image.convert_alpha()
        self.image.set_colorkey(ALPHA)
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y

如果你想让你的敌人动起来,使用让你的玩家拥有动画的 相同方式

生成一个敌人

你能够通过告诉类,妖精应使用哪张图像,应出现在世界上的什么地方,来生成不只一个敌人。这意味着,你能够使用相同的敌人类,在游戏世界的任意地方生成任意数量的敌方妖精。你需要做的仅仅是调用这个类,并告诉它应使用哪张图像,以及你期望生成点的 X 和 Y 坐标。

再次,这从原则上与生成一个玩家精灵相似。在你脚本的 setup 节添加如下代码:

enemy   = Enemy(20,200,'yeti.png')  # 生成敌人
enemy_list = pygame.sprite.Group()  # 创建敌人组
enemy_list.add(enemy)               # 将敌人加入敌人组

在示例代码中,X 坐标为 20,Y 坐标为 200。你可能需要根据你的敌方妖精的大小,来调整这些数字,但尽量生成在一个范围内,使得你的玩家妖精能够碰到它。Yeti.png 是用于敌人的图像。

接下来,将敌人组的所有敌人绘制在屏幕上。现在,你只有一个敌人,如果你想要更多你可以稍后添加。一但你将一个敌人加入敌人组,它就会在主循环中被绘制在屏幕上。中间这一行是你需要添加的新行:

    player_list.draw(world)
    enemy_list.draw(world)  # 刷新敌人
    pygame.display.flip()

启动你的游戏,你的敌人会出现在游戏世界中你选择的 X 和 Y 坐标处。

关卡一

你的游戏仍处在襁褓期,但你可能想要为它添加另一个关卡。为你的程序做好未来规划非常重要,因为随着你学会更多的编程技巧,你的程序也会随之成长。即使你现在仍没有一个完整的关卡,你也应该按照假设会有很多关卡来编程。

思考一下“关卡”是什么。你如何知道你是在游戏中的一个特定关卡中呢?

你可以把关卡想成一系列项目的集合。就像你刚刚创建的这个平台中,一个关卡,包含了平台、敌人放置、战利品等的一个特定排列。你可以创建一个类,用来在你的玩家附近创建关卡。最终,当你创建了一个以上的关卡,你就可以在你的玩家达到特定目标时,使用这个类生成下一个关卡。

将你写的用于生成敌人及其群组的代码,移动到一个每次生成新关卡时都会被调用的新函数中。你需要做一些修改,使得每次你创建新关卡时,你都能够创建一些敌人。

class Level():
    def bad(lvl,eloc):
        if lvl == 1:
            enemy = Enemy(eloc[0],eloc[1],'yeti.png') # 生成敌人
            enemy_list = pygame.sprite.Group() # 生成敌人组
            enemy_list.add(enemy)              # 将敌人加入敌人组
        if lvl == 2:
            print("Level " + str(lvl) )

        return enemy_list

return 语句确保了当你调用 Level.bad 方法时,你将会得到一个 enemy_list 变量包含了所有你定义的敌人。

因为你现在将创造敌人作为每个关卡的一部分,你的 setup 部分也需要做些更改。不同于创造一个敌人,取而代之的是你必须去定义敌人在那里生成,以及敌人属于哪个关卡。

eloc = []
eloc = [200,20]
enemy_list = Level.bad( 1, eloc )

再次运行游戏来确认你的关卡生成正确。与往常一样,你应该会看到你的玩家,并且能看到你在本章节中添加的敌人。

痛击敌人

一个敌人如果对玩家没有效果,那么它不太算得上是一个敌人。当玩家与敌人发生碰撞时,他们通常会对玩家造成伤害。

因为你可能想要去跟踪玩家的生命值,因此碰撞检测发生在 Player 类,而不是 Enemy 类中。当然如果你想,你也可以跟踪敌人的生命值。它们之间的逻辑与代码大体相似,现在,我们只需要跟踪玩家的生命值。

为了跟踪玩家的生命值,你必须为它确定一个变量。代码示例中的第一行是上下文提示,那么将第二行代码添加到你的 Player 类中:

        self.frame  = 0
        self.health = 10

在你 Player 类的 update 方法中,添加如下代码块:

        hit_list = pygame.sprite.spritecollide(self, enemy_list, False)
        for enemy in hit_list:
            self.health -= 1
            print(self.health)

这段代码使用 Pygame 的 sprite.spritecollide 方法,建立了一个碰撞检测器,称作 enemy_hit。每当它的父类妖精(生成检测器的玩家妖精)的碰撞区触碰到 enemy_list 中的任一妖精的碰撞区时,碰撞检测器都会发出一个信号。当这个信号被接收,for 循环就会被触发,同时扣除一点玩家生命值。

一旦这段代码出现在你 Player 类的 update 方法,并且 update 方法在你的主循环中被调用,Pygame 会在每个时钟滴答中检测一次碰撞。

移动敌人

如果你愿意,静止不动的敌人也可以很有用,比如能够对你的玩家造成伤害的尖刺和陷阱。但如果敌人能够四处徘徊,那么游戏将更富有挑战。

与玩家妖精不同,敌方妖精不是由玩家控制,因此它必须自动移动。

最终,你的游戏世界将会滚动。那么,如何在游戏世界自身滚动的情况下,使游戏世界中的敌人前后移动呢?

举个例子,你告诉你的敌方妖精向右移动 10 步,向左移动 10 步。但敌方妖精不会计数,因此你需要创建一个变量来跟踪你的敌人已经移动了多少步,并根据计数变量的值来向左或向右移动你的敌人。

首先,在你的 Enemy 类中创建计数变量。添加以下代码示例中的最后一行代码:

        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.counter = 0 # 计数变量

然后,在你的 Enemy 类中创建一个 move 方法。使用 if-else 循环来创建一个所谓的死循环:

  • 如果计数在 0 到 100 之间,向右移动;
  • 如果计数在 100 到 200 之间,向左移动;
  • 如果计数大于 200,则将计数重置为 0。

死循环没有终点,因为循环判断条件永远为真,所以它将永远循环下去。在此情况下,计数器总是介于 0 到 100 或 100 到 200 之间,因此敌人会永远地从左向右再从右向左移动。

你用于敌人在每个方向上移动距离的具体值,取决于你的屏幕尺寸,更确切地说,取决于你的敌人移动的平台大小。从较小的值开始,依据习惯逐步提高数值。首先进行如下尝试:

    def move(self):
        '''
        敌人移动
        '''
        distance = 80
        speed = 8

        if self.counter >= 0 and self.counter <= distance:
            self.rect.x += speed
        elif self.counter >= distance and self.counter <= distance*2:
            self.rect.x -= speed
        else:
            self.counter = 0

        self.counter += 1

你可以根据需要调整距离和速度。

当你现在启动游戏,这段代码有效果吗?

当然不,你应该也知道原因。你必须在主循环中调用 move 方法。如下示例代码中的第一行是上下文提示,那么添加最后两行代码:

    enemy_list.draw(world) #refresh enemy
    for e in enemy_list:
        e.move()

启动你的游戏看看当你打击敌人时发生了什么。你可能需要调整妖精的生成地点,使得你的玩家和敌人能够碰撞。当他们发生碰撞时,查看 IDLENinja-IDE 的控制台,你可以看到生命值正在被扣除。

你应该已经注意到,在你的玩家和敌人接触时,生命值在时刻被扣除。这是一个问题,但你将在对 Python 进行更多练习以后解决它。

现在,尝试添加更多敌人。记得将每个敌人加入 enemy_list。作为一个练习,看看你能否想到如何改变不同敌方妖精的移动距离。


via: https://opensource.com/article/18/5/pygame-enemy

作者:Seth Kenlon 选题:lujun9972 译者:cycoe 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在本系列的第四部分,学习如何编写移动游戏角色的控制代码。

在这个系列的第一篇文章中,我解释了如何使用 Python 创建一个简单的基于文本的骰子游戏。在第二部分中,我向你们展示了如何从头开始构建游戏,即从 创建游戏的环境 开始。然后在第三部分,我们创建了一个玩家妖精,并且使它在你的(而不是空的)游戏世界内生成。你可能已经注意到,如果你不能移动你的角色,那么游戏不是那么有趣。在本篇文章中,我们将使用 Pygame 来添加键盘控制,如此一来你就可以控制你的角色的移动。

在 Pygame 中有许多函数可以用来添加(除键盘外的)其他控制,但如果你正在敲击 Python 代码,那么你一定是有一个键盘的,这将成为我们接下来会使用的控制方式。一旦你理解了键盘控制,你可以自己去探索其他选项。

在本系列的第二篇文章中,你已经为退出游戏创建了一个按键,移动角色的(按键)原则也是相同的。但是,使你的角色移动起来要稍微复杂一点。

让我们从简单的部分入手:设置控制器按键。

为控制你的玩家妖精设置按键

在 IDLE、Ninja-IDE 或文本编辑器中打开你的 Python 游戏脚本。

因为游戏需要时刻“监听”键盘事件,所以你写的代码需要连续运行。你知道应该把需要在游戏周期中持续运行的代码放在哪里吗?

如果你回答“放在主循环中”,那么你是正确的!记住除非代码在循环中,否则(大多数情况下)它只会运行仅一次。如果它被写在一个从未被使用的类或函数中,它可能根本不会运行。

要使 Python 监听传入的按键,将如下代码添加到主循环。目前的代码还不能产生任何的效果,所以使用 print 语句来表示成功的信号。这是一种常见的调试技术。

while main == True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit(); sys.exit()
            main = False

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                print('left')
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                print('right')
            if event.key == pygame.K_UP or event.key == ord('w'):
                print('jump')

        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                print('left stop')
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                print('right stop')
            if event.key == ord('q'):
                pygame.quit()
                sys.exit()
                main = False    

一些人偏好使用键盘字母 WASD 来控制玩家角色,而另一些偏好使用方向键。因此确保你包含了两种选项。

注意:当你在编程时,同时考虑所有用户是非常重要的。如果你写代码只是为了自己运行,那么很可能你会成为你写的程序的唯一用户。更重要的是,如果你想找一个通过写代码赚钱的工作,你写的代码就应该让所有人都能运行。给你的用户选择权,比如提供使用方向键或 WASD 的选项,是一个优秀程序员的标志。

使用 Python 启动你的游戏,并在你按下“上下左右”方向键或 ADW 键的时候查看控制台窗口的输出。

$ python ./your-name_game.py
  left
  left stop
  right
  right stop
  jump

这验证了 Pygame 可以正确地检测按键。现在是时候来完成使妖精移动的艰巨任务了。

编写玩家移动函数

为了使你的妖精移动起来,你必须为你的妖精创建一个属性代表移动。当你的妖精没有在移动时,这个变量被设为 0

如果你正在为你的妖精设置动画,或者你决定在将来为它设置动画,你还必须跟踪帧来使走路循环保持在轨迹上。

Player 类中创建如下变量。开头两行作为上下文对照(如果你一直跟着做,你的代码中就已经有这两行),因此只需要添加最后三行:

    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.movex = 0 # 沿 X 方向移动
        self.movey = 0 # 沿 Y 方向移动
        self.frame = 0 # 帧计数

设置好了这些变量,是时候去为妖精移动编写代码了。

玩家妖精不需要时刻响应控制,有时它并没有在移动。控制妖精的代码,仅仅只是玩家妖精所有能做的事情中的一小部分。在 Python 中,当你想要使一个对象做某件事并独立于剩余其他代码时,你可以将你的新代码放入一个函数。Python 的函数以关键词 def 开头,(该关键词)代表了定义函数。

在你的 Player 类中创建如下函数,来为你的妖精在屏幕上的位置增加几个像素。现在先不要担心你增加几个像素,这将在后续的代码中确定。

    def control(self,x,y):
        '''
        控制玩家移动
        '''
        self.movex += x
        self.movey += y

为了在 Pygame 中移动妖精,你需要告诉 Python 在新的位置重绘妖精,以及这个新位置在哪里。

因为玩家妖精并不总是在移动,所以更新只需要是 Player 类中的一个函数。将此函数添加前面创建的 control 函数之后。

要使妖精看起来像是在行走(或者飞行,或是你的妖精应该做的任何事),你需要在按下适当的键时改变它在屏幕上的位置。要让它在屏幕上移动,你需要将它的位置(由 self.rect.xself.rect.y 属性指定)重新定义为当前位置加上已应用的任意 movexmovey。(移动的像素数量将在后续进行设置。)

    def update(self):
        '''
        更新妖精位置
        '''
        self.rect.x = self.rect.x + self.movex        

对 Y 方向做同样的处理:

        self.rect.y = self.rect.y + self.movey

对于动画,在妖精移动时推进动画帧,并使用相应的动画帧作为玩家的图像:

        # 向左移动
        if self.movex < 0:
            self.frame += 1
            if self.frame > 3*ani:
                self.frame = 0
            self.image = self.images[self.frame//ani]

        # 向右移动
        if self.movex > 0:
            self.frame += 1
            if self.frame > 3*ani:
                self.frame = 0
            self.image = self.images[(self.frame//ani)+4]

通过设置一个变量来告诉代码为你的妖精位置增加多少像素,然后在触发你的玩家妖精的函数时使用这个变量。

首先,在你的设置部分创建这个变量。在如下代码中,开头两行是上下文对照,因此只需要在你的脚本中增加第三行代码:

player_list = pygame.sprite.Group()
player_list.add(player)
steps = 10  # 移动多少个像素

现在你已经有了适当的函数和变量,使用你的按键来触发函数并将变量传递给你的妖精。

为此,将主循环中的 print 语句替换为玩家妖精的名字(player)、函数(.control)以及你希望玩家妖精在每个循环中沿 X 轴和 Y 轴移动的步数。

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                player.control(-steps,0)
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                player.control(steps,0)
            if event.key == pygame.K_UP or event.key == ord('w'):
                print('jump')

        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                player.control(steps,0)
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                player.control(-steps,0)
            if event.key == ord('q'):
                pygame.quit()
                sys.exit()
                main = False

记住,steps 变量代表了当一个按键被按下时,你的妖精会移动多少个像素。如果当你按下 D 或右方向键时,你的妖精的位置增加了 10 个像素。那么当你停止按下这个键时,你必须(将 step)减 10(-steps)来使你的妖精的动量回到 0。

现在尝试你的游戏。注意:它不会像你预想的那样运行。

为什么你的妖精仍无法移动?因为主循环还没有调用 update 函数。

将如下代码加入到你的主循环中来告诉 Python 更新你的玩家妖精的位置。增加带注释的那行:

    player.update()  # 更新玩家位置
    player_list.draw(world)
    pygame.display.flip()
    clock.tick(fps)

再次启动你的游戏来见证你的玩家妖精在你的命令下在屏幕上来回移动。现在还没有垂直方向的移动,因为这部分函数会被重力控制,不过这是另一篇文章中的课程了。

与此同时,如果你拥有一个摇杆,你可以尝试阅读 Pygame 中 joystick 模块相关的文档,看看你是否能通过这种方式让你的妖精移动起来。或者,看看你是否能通过鼠标与你的妖精互动。

最重要的是,玩的开心!

本教程中用到的所有代码

为了方便查阅,以下是目前本系列文章用到的所有代码。

#!/usr/bin/env python3
# 绘制世界
# 添加玩家和玩家控制
# 添加玩家移动控制

# GNU All-Permissive License
# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided the copyright
# notice and this notice are preserved.  This file is offered as-is,
# without any warranty.

import pygame
import sys
import os

'''
Objects
'''

class Player(pygame.sprite.Sprite):
    '''
    生成玩家
    '''
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.movex = 0
        self.movey = 0
        self.frame = 0
        self.images = []
        for i in range(1,5):
            img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert()
            img.convert_alpha()
            img.set_colorkey(ALPHA)
            self.images.append(img)
            self.image = self.images[0]
            self.rect  = self.image.get_rect()

    def control(self,x,y):
        '''
        控制玩家移动
        '''
        self.movex += x
        self.movey += y

    def update(self):
        '''
        更新妖精位置
        '''

        self.rect.x = self.rect.x + self.movex
        self.rect.y = self.rect.y + self.movey

        # 向左移动
        if self.movex < 0:
            self.frame += 1
            if self.frame > 3*ani:
                self.frame = 0
            self.image = self.images[self.frame//ani]

        # 向右移动
        if self.movex > 0:
            self.frame += 1
            if self.frame > 3*ani:
                self.frame = 0
            self.image = self.images[(self.frame//ani)+4]


'''
设置
'''
worldx = 960
worldy = 720

fps = 40        # 帧刷新率
ani = 4        # 动画循环
clock = pygame.time.Clock()
pygame.init()
main = True

BLUE  = (25,25,200)
BLACK = (23,23,23 )
WHITE = (254,254,254)
ALPHA = (0,255,0)

world = pygame.display.set_mode([worldx,worldy])
backdrop = pygame.image.load(os.path.join('images','stage.png')).convert()
backdropbox = world.get_rect()
player = Player()   # 生成玩家
player.rect.x = 0
player.rect.y = 0
player_list = pygame.sprite.Group()
player_list.add(player)
steps = 10      # 移动速度

'''
主循环
'''
while main == True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit(); sys.exit()
            main = False

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                player.control(-steps,0)
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                player.control(steps,0)
            if event.key == pygame.K_UP or event.key == ord('w'):
                print('jump')

        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                player.control(steps,0)
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                player.control(-steps,0)
            if event.key == ord('q'):
                pygame.quit()
                sys.exit()
                main = False

#    world.fill(BLACK)
    world.blit(backdrop, backdropbox)
    player.update()
    player_list.draw(world) # 更新玩家位置
    pygame.display.flip()
    clock.tick(fps)

你已经学了很多,但还仍有许多可以做。在接下来的几篇文章中,你将实现添加敌方妖精、模拟重力等等。与此同时,练习 Python 吧!


via: https://opensource.com/article/17/12/game-python-moving-player

作者:Seth Kenlon 选题:lujun9972 译者:cycoe 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

这是用 Python 从头开始构建游戏的系列文章的第三部分。

这个系列的第一篇文章 中,我解释了如何使用 Python 创建一个简单的基于文本的骰子游戏。在第二部分中,我向你们展示了如何从头开始构建游戏,即从 创建游戏的环境 开始。但是每个游戏都需要一名玩家,并且每个玩家都需要一个可操控的角色,这也就是我们接下来要在这个系列的第三部分中需要做的。

在 Pygame 中,玩家操控的图标或者化身被称作 妖精 sprite 。如果你现在还没有任何可用于玩家妖精的图像,你可以使用 KritaInkscape 来自己创建一些图像。如果你对自己的艺术细胞缺乏自信,你也可以在 OpenClipArt.orgOpenGameArt.org 搜索一些现成的图像。如果你还未按照上一篇文章所说的单独创建一个 images 文件夹,那么你需要在你的 Python 项目目录中创建它。将你想要在游戏中使用的图片都放 images 文件夹中。

为了使你的游戏真正的刺激,你应该为你的英雄使用一张动态的妖精图片。这意味着你需要绘制更多的素材,并且它们要大不相同。最常见的动画就是走路循环,通过一系列的图像让你的妖精看起来像是在走路。走路循环最快捷粗糙的版本需要四张图像。

注意:这篇文章中的代码示例同时兼容静止的和动态的玩家妖精。

将你的玩家妖精命名为 hero.png。如果你正在创建一个动态的妖精,则需要在名字后面加上一个数字,从 hero1.png 开始。

创建一个 Python 类

在 Python 中,当你在创建一个你想要显示在屏幕上的对象时,你需要创建一个类。

在你的 Python 脚本靠近顶端的位置,加入如下代码来创建一个玩家。在以下的代码示例中,前三行已经在你正在处理的 Python 脚本中:

import pygame
import sys
import os # 以下是新代码

class Player(pygame.sprite.Sprite):
    '''
    生成一个玩家
    '''
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.images = []
    img = pygame.image.load(os.path.join('images','hero.png')).convert()
    self.images.append(img)
    self.image = self.images[0]
    self.rect  = self.image.get_rect()

如果你的可操控角色拥有一个走路循环,在 images 文件夹中将对应图片保存为 hero1.pnghero4.png 的独立文件。

使用一个循环来告诉 Python 遍历每个文件。

'''
对象
'''

class Player(pygame.sprite.Sprite):
    '''
    生成一个玩家
    '''
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.images = []
        for i in range(1,5):
            img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert()
            self.images.append(img)
            self.image = self.images[0]
            self.rect  = self.image.get_rect()

将玩家带入游戏世界

现在已经创建好了一个 Player 类,你需要使用它在你的游戏世界中生成一个玩家妖精。如果你不调用 Player 类,那它永远不会起作用,(游戏世界中)也就不会有玩家。你可以通过立马运行你的游戏来验证一下。游戏会像上一篇文章末尾看到的那样运行,并得到明确的结果:一个空荡荡的游戏世界。

为了将一个玩家妖精带到你的游戏世界,你必须通过调用 Player 类来生成一个妖精,并将它加入到 Pygame 的妖精组中。在如下的代码示例中,前三行是已经存在的代码,你需要在其后添加代码:

world       = pygame.display.set_mode([worldx,worldy])
backdrop    = pygame.image.load(os.path.join('images','stage.png')).convert()
backdropbox = screen.get_rect()

# 以下是新代码

player = Player()   # 生成玩家
player.rect.x = 0   # 移动 x 坐标
player.rect.y = 0   # 移动 y 坐标
player_list = pygame.sprite.Group()
player_list.add(player)

尝试启动你的游戏来看看发生了什么。高能预警:它不会像你预期的那样工作,当你启动你的项目,玩家妖精没有出现。事实上它生成了,只不过只出现了一毫秒。你要如何修复一个只出现了一毫秒的东西呢?你可能回想起上一篇文章中,你需要在主循环中添加一些东西。为了使玩家的存在时间超过一毫秒,你需要告诉 Python 在每次循环中都绘制一次。

将你的循环底部的语句更改如下:

    world.blit(backdrop, backdropbox)
    player_list.draw(screen) # 绘制玩家
    pygame.display.flip()
    clock.tick(fps)

现在启动你的游戏,你的玩家出现了!

设置 alpha 通道

根据你如何创建你的玩家妖精,在它周围可能会有一个色块。你所看到的是 alpha 通道应该占据的空间。它本来是不可见的“颜色”,但 Python 现在还不知道要使它不可见。那么你所看到的,是围绕在妖精周围的边界区(或现代游戏术语中的“ 命中区 hit box ”)内的空间。

你可以通过设置一个 alpha 通道和 RGB 值来告诉 Python 使哪种颜色不可见。如果你不知道你使用 alpha 通道的图像的 RGB 值,你可以使用 Krita 或 Inkscape 打开它,并使用一种独特的颜色,比如 #00ff00(差不多是“绿屏绿”)来填充图像周围的空白区域。记下颜色对应的十六进制值(此处为 #00ff00,绿屏绿)并将其作为 alpha 通道用于你的 Python 脚本。

使用 alpha 通道需要在你的妖精生成相关代码中添加如下两行。类似第一行的代码已经存在于你的脚本中,你只需要添加另外两行:

            img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert()
            img.convert_alpha()     # 优化 alpha
            img.set_colorkey(ALPHA) # 设置 alpha

除非你告诉它,否则 Python 不知道将哪种颜色作为 alpha 通道。在你代码的设置相关区域,添加一些颜色定义。将如下的变量定义添加于你的设置相关区域的任意位置:

ALPHA = (0, 255, 0)

在以上示例代码中,0,255,0 被我们使用,它在 RGB 中所代表的值与 #00ff00 在十六进制中所代表的值相同。你可以通过一个优秀的图像应用程序,如 GIMP、Krita 或 Inkscape,来获取所有这些颜色值。或者,你可以使用一个优秀的系统级颜色选择器,如 KColorChooser,来检测颜色。

如果你的图像应用程序将你的妖精背景渲染成了其他的值,你可以按需调整 ALPHA 变量的值。不论你将 alpha 设为多少,最后它都将“不可见”。RGB 颜色值是非常严格的,因此如果你需要将 alpha 设为 000,但你又想将 000 用于你图像中的黑线,你只需要将图像中线的颜色设为 111。这样一来,(图像中的黑线)就足够接近黑色,但除了电脑以外没有人能看出区别。

运行你的游戏查看结果。

这个系列的第四篇文章 中,我会向你们展示如何使你的妖精动起来。多么的激动人心啊!


via: https://opensource.com/article/17/12/game-python-add-a-player

作者:Seth Kenlon 选题:lujun9972 译者:cycoe 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

这系列的第一篇通过创建一个简单的骰子游戏来探究 Python。现在是来从零制作你自己的游戏的时间。

在我的这系列的第一篇文章 中, 我已经讲解如何使用 Python 创建一个简单的、基于文本的骰子游戏。这次,我将展示如何使用 Python 模块 Pygame 来创建一个图形化游戏。它将需要几篇文章才能来得到一个确实做成一些东西的游戏,但是到这系列的结尾,你将更好地理解如何查找和学习新的 Python 模块和如何从其基础上构建一个应用程序。

在开始前,你必须安装 Pygame

安装新的 Python 模块

有几种方法来安装 Python 模块,但是最通用的两个是:

  • 从你的发行版的软件存储库
  • 使用 Python 的软件包管理器 pip

两个方法都工作的很好,并且每一个都有它自己的一套优势。如果你是在 Linux 或 BSD 上开发,可以利用你的发行版的软件存储库来自动和及时地更新。

然而,使用 Python 的内置软件包管理器可以给予你控制更新模块时间的能力。而且,它不是特定于操作系统的,这意味着,即使当你不是在你常用的开发机器上时,你也可以使用它。pip 的其它的优势是允许本地安装模块,如果你没有正在使用的计算机的管理权限,这是有用的。

使用 pip

如果 Python 和 Python3 都安装在你的系统上,你想使用的命令很可能是 pip3,它用来区分 Python 2.x 的 pip 的命令。如果你不确定,先尝试 pip3

pip 命令有些像大多数 Linux 软件包管理器一样工作。你可以使用 search 搜索 Python 模块,然后使用 install 安装它们。如果你没有你正在使用的计算机的管理权限来安装软件,你可以使用 --user 选项来仅仅安装模块到你的家目录。

$ pip3 search pygame
[...]
Pygame (1.9.3)                 - Python Game Development
sge-pygame (1.5)               - A 2-D game engine for Python
pygame_camera (0.1.1)          - A Camera lib for PyGame
pygame_cffi (0.2.1)            - A cffi-based SDL wrapper that copies the pygame API.
[...]
$ pip3 install Pygame --user

Pygame 是一个 Python 模块,这意味着它仅仅是一套可以使用在你的 Python 程序中的库。换句话说,它不是一个像 IDLENinja-IDE 一样可以让你启动的程序。

Pygame 新手入门

一个电子游戏需要一个背景设定:故事发生的地点。在 Python 中,有两种不同的方法来创建你的故事背景:

  • 设置一种背景颜色
  • 设置一张背景图片

你的背景仅是一张图片或一种颜色。你的电子游戏人物不能与在背景中的东西相互作用,因此,不要在后面放置一些太重要的东西。它仅仅是设置装饰。

设置你的 Pygame 脚本

要开始一个新的 Pygame 工程,先在计算机上创建一个文件夹。游戏的全部文件被放在这个目录中。在你的工程文件夹内部保持所需要的所有的文件来运行游戏是极其重要的。

一个 Python 脚本以文件类型、你的姓名,和你想使用的许可证开始。使用一个开放源码许可证,以便你的朋友可以改善你的游戏并与你一起分享他们的更改:

#!/usr/bin/env python3
# by Seth Kenlon

## GPLv3
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

然后,你告诉 Python 你想使用的模块。一些模块是常见的 Python 库,当然,你想包括一个你刚刚安装的 Pygame 模块。

import pygame  # 加载 pygame 关键字
import sys     # 让 python 使用你的文件系统
import os      # 帮助 python 识别你的操作系统

由于你将用这个脚本文件做很多工作,在文件中分成段落是有帮助的,以便你知道在哪里放代码。你可以使用块注释来做这些,这些注释仅在看你的源文件代码时是可见的。在你的代码中创建三个块。

'''
Objects
'''

# 在这里放置 Python 类和函数

'''
Setup
'''

# 在这里放置一次性的运行代码

'''
Main Loop
'''

# 在这里放置游戏的循环代码指令

接下来,为你的游戏设置窗口大小。注意,不是每一个人都有大计算机屏幕,所以,最好使用一个适合大多数人的计算机的屏幕大小。

这里有一个方法来切换全屏模式,很多现代电子游戏都会这样做,但是,由于你刚刚开始,简单起见仅设置一个大小即可。

'''
Setup
'''
worldx = 960
worldy = 720

在脚本中使用 Pygame 引擎前,你需要一些基本的设置。你必须设置帧频,启动它的内部时钟,然后开始 (init)Pygame 。

fps   = 40  # 帧频
ani   = 4   # 动画循环
clock = pygame.time.Clock()
pygame.init()

现在你可以设置你的背景。

设置背景

在你继续前,打开一个图形应用程序,为你的游戏世界创建一个背景。在你的工程目录中的 images 文件夹内部保存它为 stage.png

这里有一些你可以使用的自由图形应用程序。

  • Krita 是一个专业级绘图素材模拟器,它可以被用于创建漂亮的图片。如果你对创建电子游戏艺术作品非常感兴趣,你甚至可以购买一系列的游戏艺术作品教程
  • Pinta 是一个基本的,易于学习的绘图应用程序。
  • Inkscape 是一个矢量图形应用程序。使用它来绘制形状、线、样条曲线和贝塞尔曲线。

你的图像不必很复杂,你可以以后回去更改它。一旦有了它,在你文件的 Setup 部分添加这些代码:

world    = pygame.display.set_mode([worldx,worldy])
backdrop = pygame.image.load(os.path.join('images','stage.png').convert())
backdropbox = world.get_rect()

如果你仅仅用一种颜色来填充你的游戏的背景,你需要做的就是:

world = pygame.display.set_mode([worldx,worldy])

你也必须定义颜色以使用。在你的 Setup 部分,使用红、绿、蓝 (RGB) 的值来创建一些颜色的定义。

'''
Setup
'''

BLUE  = (25,25,200)
BLACK = (23,23,23 )
WHITE = (254,254,254)

至此,你理论上可以启动你的游戏了。问题是,它可能仅持续了一毫秒。

为证明这一点,保存你的文件为 your-name_game.py(用你真实的名称替换 your-name)。然后启动你的游戏。

如果你正在使用 IDLE,通过选择来自 “Run” 菜单的 “Run Module” 来运行你的游戏。

如果你正在使用 Ninja,在左侧按钮条中单击 “Run file” 按钮。

你也可以直接从一个 Unix 终端或一个 Windows 命令提示符中运行一个 Python 脚本。

$ python3 ./your-name_game.py

如果你正在使用 Windows,使用这命令:

py.exe your-name_game.py

启动它,不过不要期望很多,因为你的游戏现在仅仅持续几毫秒。你可以在下一部分中修复它。

循环

除非另有说明,一个 Python 脚本运行一次并仅一次。近来计算机的运行速度是非常快的,所以你的 Python 脚本运行时间会少于 1 秒钟。

为强制你的游戏来处于足够长的打开和活跃状态来让人看到它(更不要说玩它),使用一个 while 循环。为使你的游戏保存打开,你可以设置一个变量为一些值,然后告诉一个 while 循环只要变量保持未更改则一直保存循环。

这经常被称为一个“主循环”,你可以使用术语 main 作为你的变量。在你的 Setup 部分的任意位置添加代码:

main = True

在主循环期间,使用 Pygame 关键字来检查键盘上的按键是否已经被按下或释放。添加这些代码到你的主循环部分:

'''
Main loop
'''
while main == True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit(); sys.exit()
            main = False

        if event.type == pygame.KEYDOWN:
            if event.key == ord('q'):
                pygame.quit()
                sys.exit()
                main = False

也是在你的循环中,刷新你世界的背景。

如果你使用一个图片作为背景:

world.blit(backdrop, backdropbox)

如果你使用一种颜色作为背景:

world.fill(BLUE)

最后,告诉 Pygame 来重新刷新屏幕上的所有内容,并推进游戏的内部时钟。

    pygame.display.flip()
    clock.tick(fps)

保存你的文件,再次运行它来查看你曾经创建的最无趣的游戏。

退出游戏,在你的键盘上按 q 键。

在这系列的 下一篇文章 中,我将向你演示,如何加强你当前空空如也的游戏世界,所以,继续学习并创建一些将要使用的图形!


via: https://opensource.com/article/17/12/game-framework-python

作者:Seth Kenlon 选题:lujun9972 译者:robsean 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出