分类 技术 下的文章

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

在本系列的前几篇文章中(参见 第一部分第二部分第三部分 以及 第四部分),你已经学习了如何使用 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中国 荣誉推出

和绝地武士的原力一样,-f 参数是很强大的,并伴随着潜在的毁灭性,在你能用好的时候又很便利。

近些年来,科幻发烧友开始在每年的 5 月 4 日庆祝星战节,其口号是绝地武士的祝福语”愿 原力 Force 和你同在“。虽然大多数 Linux 用户可能不是绝地武士,但我们依然可以使用 原力 Force 。自然,如果尤达大师只是叫天行者卢克输入什么 “man X-Wing 战机“、“man 原力”,或者 RTFM(去读原力手册,肯定是这个意思对不对),那这电影肯定没啥意思。(LCTT 译注:RTFM 是 “Read The Fucking Manual” 的缩写 —— 读读该死的手册吧)。

很多 Linux 命令都有 -f 选项,意思你现在肯定也知道了,原力(LCTT 译注:force 选项原意是“强制”)!很多时候你先尝试执行命令然后失败了,或者提示你需要补充输入更多选项。通常这都是为了保护你试着改变的文件,或者告诉用户该设备正忙或文件已经存在之类的。

如果你不想被这些提醒打扰或者压根就不在乎,就使用原力吧!

不过要小心,通常使用原力选项是摧毁性的。所以用户一定要格外注意!并且确保你知道自己在做什么!用原力就要承担后果!

以下是一些常见 Linux 命令的原力选项和它们的效果,以及常见使用场景。

cp

cp 是 “copy” 的缩写,这是个被用来复制文件或者目录的命令。其 man 页面 说:

-f, –force

如果已经存在的目标文件无法被打开,删除它并重试

你可能会用它来处理只读状态的文件:

[alan@workstation ~]$ ls -l
total 8
-rw-rw---- 1 alan alan 13 May 1 12:24 Hoth
-r--r----- 1 alan alan 14 May 1 12:23 Naboo
[alan@workstation ~]$ cat Hoth Naboo
Icy Planet

Green Planet

如果你想要复制一个叫做 Hoth 的文件到 Naboo,但因为 Naboo 目前是只读状态,cp 命令不会执行:

[alan@workstation ~]$ cp Hoth Naboo
cp: cannot create regular file 'Naboo': Permission denied

但通过使用原力,cp 会强制执行。Hoth 的内容和文件权限会直接被复制到 Naboo

[alan@workstation ~]$ cp -f Hoth Naboo
[alan@workstation ~]$ cat Hoth Naboo
Icy Planet

Icy Planet

[alan@workstation ~]$ ls -l
total 8
-rw-rw---- 1 alan alan 12 May 1 12:32 Hoth
-rw-rw---- 1 alan alan 12 May 1 12:38 Naboo

ln

ln 命令是用来在文件之间建立链接的,其 man 页面 描述的原力选项如下:

-f, –force

移除当前存在的文件

假设莱娅公主在维护一个 Java 应用服务器,并且她又一个存放这所有 Java 版本的目录,比如:

leia@workstation:/usr/lib/java$ ls -lt
total 28
lrwxrwxrwx 1 leia leia   12 Mar  5  2018 jdk -> jdk1.8.0_162
drwxr-xr-x 8 leia leia 4096 Mar  5  2018 jdk1.8.0_162
drwxr-xr-x 8 leia leia 4096 Aug 28  2017 jdk1.8.0_144

正如你所看到的,这里有很多个版本的 JDK,并有一个符号链接指向最新版的 JDK。她接着用一个脚本来安装最新版本的 JDK。但是如果没有原力选项的话以下命令是不会成功的:

tar xvzmf jdk1.8.0_181.tar.gz -C jdk1.8.0_181/
ln -vs jdk1.8.0_181 jdk

tar 命令会解压 .gz 文件到一个特定的目标目录,但 ln 命令会失败,因为这个链接已经存在了。这样的结果是该符号链接不会指向最新版本的 JDK:

leia@workstation:/usr/lib/java$ ln -vs jdk1.8.0_181 jdk
ln: failed to create symbolic link 'jdk/jdk1.8.0_181': File exists
leia@workstation:/usr/lib/java$ ls -lt
total 28
drwxr-x--- 2 leia leia 4096 May  1 15:44 jdk1.8.0_181
lrwxrwxrwx 1 leia leia   12 Mar  5  2018 jdk -> jdk1.8.0_162
drwxr-xr-x 8 leia leia 4096 Mar  5  2018 jdk1.8.0_162
drwxr-xr-x 8 leia leia 4096 Aug 28  2017 jdk1.8.0_144

她可以通过使用原力选项强制 ln 更新链接,但这里她还需要使用 -n-n 是因为这个情况下链接其实指向一个目录而非文件。这样的话,链接就会正确指向最新版本的JDK了。

leia@workstation:/usr/lib/java$ ln -vsnf jdk1.8.0_181 jdk
'jdk' -> 'jdk1.8.0_181'
leia@workstation:/usr/lib/java$ ls -lt
total 28
lrwxrwxrwx 1 leia leia 12 May 1 16:13 jdk -> jdk1.8.0_181
drwxr-x--- 2 leia leia 4096 May 1 15:44 jdk1.8.0_181
drwxr-xr-x 8 leia leia 4096 Mar 5 2018 jdk1.8.0_162
drwxr-xr-x 8 leia leia 4096 Aug 28 2017 jdk1.8.0_144

你可以配置 Java 应用使其一直使用在 /usr/lib/java/jdk 处的 JDK,而不用每次升级都更新。

rm

rm 命令是 “remove” 的缩写(也叫做删除,因为某些系统 del 命令也干这事)。其 man 页面 对原力选项的描述如下:

-f, –force

无视不存在的文件或者参数,不向用户确认

如果你尝试删除一个只读的文件,rm 会寻求用户的确认:

[alan@workstation ~]$ ls -l
total 4
-r--r----- 1 alan alan 16 May  1 11:38 B-wing
[alan@workstation ~]$ rm B-wing 
rm: remove write-protected regular file 'B-wing'?

你一定要输入 y 或者 n 来回答确认才能让 rm 命令继续。如果你使用原力选项,rm 就不会寻求你的确认而直接删除文件:

[alan@workstation ~]$ rm -f B-wing
[alan@workstation ~]$ ls -l
total 0
[alan@workstation ~]$

最常见的 rm 原力选项用法是用来删除目录。 -r(递归)选项会让 rm 删除目录,当和原力选项结合起来,它会删除这个文件夹及其内容而无需用户确认。

rm 命令和一些选项结合起来是致命的,一直以来互联网上都有关于误用 rm 删除整个系统之类的玩笑和鬼故事。比如最出名的一不当心执行 rm -rf . 会直接删除目录和文件(没有用户确认)。(LCTT 译注:真的这么干过的校对飘过~~请按下回车前再三确认:我是谁,我在哪里,我在干什么)

userdel

userdel 命令使用来删除用户的。其 man 页面 是这样描述它的原力选项的:

-f, –force

这个选项会强制移除用户,即便用户当前处于登入状态。它同时还会强制 删除用户的目录和邮件存储,即便这个用户目录被别人共享或者邮件存储并不 属于这个用户。如果 USERGROUPS_ENAB/etc/login.defs 里是 yes 并且有一个组和此用户同名的话,这个组也会被移除,即便这个组还是别 的用户的主要用户组也一样。

注意:这个选项有风险并可能让系统处于不稳定状态。

当欧比旺抵达穆斯塔法星的时候,他知道自己的使命。他需要删掉达斯·维达的用户账户——而达斯还在里面呢。

[root@workstation ~]# ps -fu darth
UID        PID  PPID  C STIME TTY          TIME CMD
darth     7663  7655  0 13:28 pts/3    00:00:00 -bash
[root@workstation ~]# userdel darth
userdel: user darth is currently used by process 7663

因为达斯还登在系统里,欧比旺需要使用原力选项操作 userdel。这能强制删除当前登入的用户。

[root@workstation ~]# userdel -f darth
userdel: user darth is currently used by process 7663
[root@workstation ~]# finger darth
finger: darth: no such user.
[root@workstation ~]# ps -fu darth
error: user name does not exist

正如我们所见到的一样,fingerps 命令让我们确认了达斯已经被删除了。

在 Shell 脚本里使用原力

很多命令都有原力选项,而在 shell 脚本里他们特别有用。因为我们经常使用脚本完成定期或者自动化的任务,避免用户输入至关重要,不然的话自动任务就无法完成了

我希望上面的几个例子能帮你理解一些需要使用原力的情况。你在命令行使用原力或把它们写入脚本之前应当完全理解它们的作用。误用原力会有毁灭性的后果——时常是对整个系统,甚至不仅限于一台设备。


via: https://opensource.com/article/19/5/may-the-force-linux

作者:Alan Formy-Duval 选题:lujun9972 译者:Jerry Ling 校对:wxy

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

及时备份很重要。即使在 Fedora Magazine 中,备份软件 也是一个常见的讨论话题。本文演示了如何仅使用 systemd 以及 restic 来自动备份。

有关 restic 的介绍,请查看我们的文章在 Fedora 上使用 restic 进行加密备份。然后继续阅读以了解更多详情。

为了自动创建快照以及清理数据,需要运行两个 systemd 服务。第一个运行备份命令的服务需要以常规频率运行。第二个服务负责数据清理。

如果你根本不熟悉 systemd,那么这是个很好的学习机会。查看 Magazine 上关于 systemd 的系列文章,从单元文件的这个入门开始:

如果你还没有安装 restic,请注意它在官方的 Fedora 仓库中。要安装它,请带上 sudo 运行此命令:

$ sudo dnf install restic

备份

首先,创建 ~/.config/systemd/user/restic-backup.service。将下面的文本复制并粘贴到文件中以获得最佳效果。

[Unit]
Description=Restic backup service
[Service]
Type=oneshot
ExecStart=restic backup --verbose --one-file-system --tag systemd.timer $BACKUP_EXCLUDES $BACKUP_PATHS
ExecStartPost=restic forget --verbose --tag systemd.timer --group-by "paths,tags" --keep-daily $RETENTION_DAYS --keep-weekly $RETENTION_WEEKS --keep-monthly $RETENTION_MONTHS --keep-yearly $RETENTION_YEARS
EnvironmentFile=%h/.config/restic-backup.conf

此服务引用环境文件来加载密钥(例如 RESTIC_PASSWORD)。创建 ~/.config/restic-backup.conf。复制并粘贴以下内容以获得最佳效果。此示例使用 BackBlaze B2 存储。请相应地调整 ID、密钥、仓库和密码值。

BACKUP_PATHS="/home/rupert"
BACKUP_EXCLUDES="--exclude-file /home/rupert/.restic_excludes --exclude-if-present .exclude_from_backup"
RETENTION_DAYS=7
RETENTION_WEEKS=4
RETENTION_MONTHS=6
RETENTION_YEARS=3
B2_ACCOUNT_ID=XXXXXXXXXXXXXXXXXXXXXXXXX
B2_ACCOUNT_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
RESTIC_REPOSITORY=b2:XXXXXXXXXXXXXXXXXX:/
RESTIC_PASSWORD=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

现在已安装该服务,请重新加载 systemd:systemctl -user daemon-reload。尝试手动运行该服务以创建备份:systemctl -user start restic-backup

因为该服务类型是一次性,它将运行一次并退出。验证服务运行并根据需要创建快照后,设置计时器以定期运行此服务。例如,要每天运行 restic-backup.service,请按如下所示创建 ~/.config/systemd/user/restic-backup.timer。再次复制并粘贴此文本:

[Unit]
Description=Backup with restic daily
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target

运行以下命令启用:

$ systemctl --user enable --now restic-backup.timer

清理

虽然主服务运行 forget 命令仅保留保留策略中的快照,但实际上并未从 restic 仓库中删除数据。 prune 命令检查仓库和当前快照,并删除与快照无关的所有数据。由于 prune 可能是一个耗时的过程,因此无需在每次运行备份时运行。这是第二个服务和计时器的场景。首先,通过复制和粘贴此文本来创建文件 ~/.config/systemd/user/restic-prune.service

[Unit]
Description=Restic backup service (data pruning)
[Service]
Type=oneshot
ExecStart=restic prune
EnvironmentFile=%h/.config/restic-backup.conf

与主 restic-backup.service 服务类似,restic-prune 也是一次性服务,并且可以手动运行。设置完服务后,创建 ~/.config/systemd/user/restic-prune.timer 并启用相应的计时器:

[Unit]
Description=Prune data from the restic repository monthly
[Timer]
OnCalendar=monthly
Persistent=true
[Install]
WantedBy=timers.target

就是这些了!restic 将会每日运行并按月清理数据。


图片来自 UnsplashSamuel Zeller 拍摄。


via: https://fedoramagazine.org/automate-backups-with-restic-and-systemd/

作者:Link Dupont 选题:lujun9972 译者:geekpi 校对: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中国 荣誉推出

在这篇快速指南中,你将学到如何在 Ubuntu 桌面和其他使用 GNOME 桌面的发行版中添加应用图标。

一个经典的桌面操作系统在“桌面屏”上总是有图标的。这些桌面图标包括文件管理器、回收站和应用图标。

当在 Windows 中安装应用时,一些程序会询问你是否在桌面创建一个快捷方式。但在 Linux 系统中不是这样。

但是如果你热衷于这个特点,让我给你展示如何在 Ubuntu 桌面和其他使用 GNOME 桌面的发行版中创建应用的快捷方式。

Application Shortcuts on Desktop in Ubuntu with GNOME desktop

如果你想知道我的桌面外观,我正在使用 Ant 主题和 Tela 图标集。你可以获取一些 GTK 主题为 Ubuntu 准备的图标集并换成你喜欢的。

在 Ubuntu 中添加桌面快捷方式

个人来讲,我更喜欢为应用图标准备的 Ubuntu 启动器方式。如果我经常使用一个程序,我会添加到启动器。但是我知道不是每个人都有相同的偏好,可能少数人更喜欢桌面的快捷方式。

让我们看在桌面中创建应用快捷方式的最简单方式。

免责声明

这篇指南已经在 Ubuntu 18.04 LTS 的 GNOME 桌面上测试过了。它可能在其他发行版和桌面环境上也能发挥作用,但你必须自己尝试。一些 GNOME 特定步骤可能会变,所以请在其他桌面环境尝试时注意。

准备

首先最重要的事是确保你有 GNOME 桌面的图标权限。

如果你跟随 Ubuntu 18.04 自定义提示,你会知道如何安装 GNOME Tweaks 工具。在这个工具中,确保你设置“Show Icons”选项为启用。

Allow icons on desktop in GNOME

一旦你确保已经设置,是时候在桌面添加应用快捷方式了。

第一步:定位应用的 .desktop 文件

到 “Files -> Other Location -> Computer”。

Go to Other Locations -&gt; Computer

从这里,到目录 “usr -> share -> applications”。你会在这里看到几个你已经安装的 Ubuntu 应用。即使你没有看到图标,你应该看到被命名为“应用名.desktop”形式的文件。

Application Shortcuts

第二步:拷贝 .desktop 文件到桌面

现在你要做的只是查找应用图标(或者它的 desktop 文件)。当你找到后,拖文件到桌面或者拷贝文件(使用 Ctrl+C 快捷方式)并在桌面粘贴(使用 Ctrl+V 快捷方式)。

Add .desktop file to the desktop

第三步:运行 desktop 文件

当你这么做,你应该在桌面上看到一个图标的文本文件而不是应用 logo。别担心,一会就不一样了。

你要做的就是双击桌面的那个文件。它将警告你它是一个“未信任的应用启动器’,点击“信任并启动”。

Launch Desktop Shortcut

这个应用像往常一样启动,好事是你会察觉到 .desktop 文件现在已经变成应用图标了。我相信你喜欢应用图标的方式,不是吗?

Application shortcut on the desktop

Ubuntu 19.04 或者 GNOME 3.32 用户的疑难杂症

如果你使用 Ubuntu 19.04 或者 GNOME 3.32,你的 .desktop 文件可能根本不会启动。你应该右击 .desktop 文件并选择 “允许启动”。

在这之后,你应该能够启动应用并且桌面上的应用快捷方式能够正常显示了。

总结

如果你不喜欢桌面的某个应用启动器,选择删除就是了。它会删除应用快捷方式,但是应用仍安全的保留在你的系统中。

我希望你发现这篇快速指南有帮助并喜欢在 Ubuntu 桌面上的应用快捷方式。

如果你有问题或建议,请在下方评论让我知道。


via: https://itsfoss.com/ubuntu-desktop-shortcut/

作者:Abhishek Prakash 选题:lujun9972 译者:warmfrog 校对:wxy

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

在我们覆盖 7 个 PyPI 库的系列文章中了解更多解决 Python 问题的信息。

Python是当今使用最多流行的编程语言之一,因为:它是开源的,它具有广泛的用途(例如 Web 编程、业务应用、游戏、科学编程等等),它有一个充满活力和专注的社区支持它。这个社区是我们在 Python Package Index(PyPI)中提供如此庞大、多样化的软件包的原因,用以扩展和改进 Python。并解决不可避免的问题。

在本系列中,我们将介绍七个可以帮助你解决常见 Python 问题的 PyPI 库。今天,我们将研究 attrs,这是一个帮助你快速编写简洁、正确的代码的 Python 包。

attrs

如果你已经写过一段时间的 Python,那么你可能习惯这样写代码:

class Book(object):

    def __init__(self, isbn, name, author):
        self.isbn = isbn
        self.name = name
        self.author = author

接着写一个 __repr__ 函数。否则,很难记录 Book 的实例:

def __repr__(self):
    return f"Book({self.isbn}, {self.name}, {self.author})"

接下来你会写一个好看的 docstring 来记录期望的类型。但是你注意到你忘了添加 editionpublished_year 属性,所以你必须在五个地方修改它们。

如果你不必这么做如何?

@attr.s(auto_attribs=True)
class Book(object):
    isbn: str
    name: str
    author: str
    published_year: int
    edition: int

使用新的类型注释语法注释类型属性,attrs 会检测注释并创建一个类。

ISBN 有特定格式。如果我们想强行使用该格式怎么办?

@attr.s(auto_attribs=True)
class Book(object):
    isbn: str = attr.ib()
    @isbn.validator
    def pattern_match(self, attribute, value):
        m = re.match(r"^(\d{3}-)\d{1,3}-\d{2,3}-\d{1,7}-\d$", value)
        if not m:
            raise ValueError("incorrect format for isbn", value)
    name: str 
    author: str
    published_year: int
    edition: int

attrs 库也对不可变式编程支持良好。将第一行改成 @attr.s(auto_attribs=True, frozen=True) 意味着 Book 现在是不可变的:尝试修改一个属性将会引发一个异常。相反,比如,如果希望将发布日期向后一年,我们可以修改成 attr.evolve(old_book, published_year=old_book.published_year+1) 来得到一个新的实例。

本系列的下一篇文章我们将来看下 singledispatch,一个能让你向 Python 库添加方法的库。

查看本系列先前的文章:


via: https://opensource.com/article/19/5/python-attrs

作者:Moshe Zadka 选题:lujun9972 译者:geekpi 校对:wxy

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