mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-01-13 22:30:37 +08:00
Merge remote-tracking branch 'LCTT/master'
This commit is contained in:
commit
49ebbc3696
@ -1,20 +1,20 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (wxy)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: reviewer: (wxy)
|
||||
[#]: publisher: (wxy)
|
||||
[#]: url: (https://linux.cn/article-12865-1.html)
|
||||
[#]: subject: (5 new sudo features you need to know in 2020)
|
||||
[#]: via: (https://opensource.com/article/20/10/sudo-19)
|
||||
[#]: author: (Peter Czanik https://opensource.com/users/czanik)
|
||||
|
||||
2020 年的 5 个 新 sudo 功能
|
||||
2020 年 5 个新 sudo 功能
|
||||
======
|
||||
|
||||
> 从通过 chroot 支持集中会话录制到 Python API,sudo 1.9 提供了许多新功能。
|
||||
> 从集中会话记录、chroot 支持到 Python API,sudo 1.9 提供了许多新功能。
|
||||
|
||||
![Wratchet set tools][1] 。
|
||||
![](https://img.linux.net.cn/data/attachment/album/202011/28/143544x5cdcxzf9dcujdng.jpg)
|
||||
|
||||
当你想在 [POSIX 系统][2]上执行一个操作时,最安全的方法之一就是使用 `sudo` 命令。与以 root 用户身份登录并执行命令可能是个危险的操作不同,`sudo` 授予任何被系统管理员[指定为 “sudoer”][3]的用户临时权限来执行通常受限制的活动。
|
||||
当你想在 [POSIX 系统][2]上执行一个操作时,最安全的方法之一就是使用 `sudo` 命令。与以 root 用户身份登录并执行命令可能是个危险的操作不同,`sudo` 授予任何被系统管理员[指定为 “sudoer”][3]的用户临时权限,来执行通常受限制的活动。
|
||||
|
||||
几十年来,这个系统帮助 Linux、Unix 和 macOS 系统免受愚蠢的错误和恶意攻击,它是当今所有主要 Linux 发行版的默认管理机制。
|
||||
|
||||
@ -32,11 +32,11 @@
|
||||
|
||||
### 哪里可以得到 sudo 1.9?
|
||||
|
||||
大多数的 Linux 发行版仍然封装了上一代的 `sudo`(1.8 版本),并且在长期支持(LTS)的发行版中会保持这种方式数年。据我所知,提供了最完整的 sudo 1.9 包的 Linux 发行版是 openSUSE[Tumbleweed][5],它是一个滚动发行版,而且该 `sudo` 包的子包中有 Python 支持。最近的 [Fedora][6] 版本包含了 sudo 1.9,但没有 Python。[FreeBSD Ports][7] 有最新的 `sudo` 版本,如果你自己编译 `sudo` 而不是使用软件包,你可以启用 Python 支持。
|
||||
大多数的 Linux 发行版仍然封装了上一代的 `sudo`(1.8 版本),并且在长期支持(LTS)的发行版中会保持这个版本数年。据我所知,提供了最完整的 sudo 1.9 包的 Linux 发行版是 openSUSE[Tumbleweed][5],它是一个滚动发行版,而且该 `sudo` 包的子包中有 Python 支持。最近的 [Fedora][6] 版本包含了 sudo 1.9,但没有 Python 支持。[FreeBSD Ports][7] 有最新的 `sudo` 版本,如果你自己编译 `sudo` 而不是使用软件包,你可以启用 Python 支持。
|
||||
|
||||
如果你喜欢的 Linux 发行版还没有包含 sudo 1.9,请查看 [sudo 二进制页面][8]来查看是否有现成的包可以用于你的系统。这个页面还提供了一些商用 Unix 变种的软件包。
|
||||
|
||||
像往常一样,在你开始试验 `sudo` 设置之前,*确保你知道 root 密码*。是的,即使在 Ubuntu 上也是如此。有一个临时的“后门”是很重要的;如果没有这个后门,如果出了问题,你就必须得黑掉自己的系统。记住:语法正确的配置并不意味着任何人都可以在该系统上通过 `sudo` 做任何事情!
|
||||
像往常一样,在你开始试验 `sudo` 设置之前,*确保你知道 root 密码*。是的,即使在 Ubuntu 上也是如此。有一个临时的“后门”是很重要的;如果没有这个后门,如果出了问题,你就必须得黑掉自己的系统。记住:语法正确的配置并不意味着每个人都可以在该系统上通过 `sudo` 做任何事情!
|
||||
|
||||
### 记录服务
|
||||
|
||||
@ -46,11 +46,11 @@
|
||||
* 即使在发送机器停机的情况下也可以进行记录
|
||||
* 本地用户若想掩盖其轨迹,不能删除记录
|
||||
|
||||
为了快速测试,你可以通过非加密连接向记录服务发送会话。我的博客中包含了[说明][9],可以在几分钟内完成设置。对于生产环境,我建议使用加密连接。有很多可能性,所以阅读最适合你的环境的[文档][10]。
|
||||
为了快速测试,你可以通过非加密连接向记录服务发送会话。我的博客中包含了[说明][9],可以在几分钟内完成设置。对于生产环境,我建议使用加密连接。有很多可能性,所以请阅读最适合你的环境的[文档][10]。
|
||||
|
||||
### 审计插件 API
|
||||
|
||||
新的审计插件 API 不是一个用户可见的功能。换句话说,你不能从 `sudoers` 文件中配置它。它是一个 API,意味着你可以从插件中访问审计信息,包括用 Python 编写的插件。你可以用很多不同的方式来使用它,比如当一些有趣的事情发生时,从 `sudo` 直接发送事件到 Elasticsearch 或日志即服务(LaaS)。你也可以用它来进行调试,并以任何你喜欢的格式将其他难以访问的信息打印到屏幕上。
|
||||
新的审计插件 API 不是一个用户可见的功能。换句话说,你不能从 `sudoers` 文件中配置它。它是一个 API,意味着你可以从插件中访问审计信息,包括用 Python 编写的插件。你可以用很多不同的方式来使用它,比如当一些有趣的事情发生时,从 `sudo` 直接发送事件到 Elasticsearch 或日志即服务(LaaS)上。你也可以用它来进行调试,并以任何你喜欢的格式将其他难以访问的信息打印到屏幕上。
|
||||
|
||||
根据你使用它的方式,你可以在 `sudo` 插件手册页(针对 C 语言)和 `sudo` Python 插件手册中找到它的文档。在 `sudo` 源代码中可以找到 [Python 代码示例][11],在我的博客上也有一个[简化的例子][12]。
|
||||
|
||||
@ -58,7 +58,7 @@
|
||||
|
||||
审批插件 API 可以在命令执行之前加入额外的限制。这些限制只有在策略插件成功后才会运行,因此你可以有效地添加额外的策略层,而无需更换策略插件,进而无需更换 `sudoers`。可以定义多个审批插件,而且所有插件都必须成功,命令才能执行。
|
||||
|
||||
与审计插件 API 一样,你可以从 C 和 Python 中使用它。我博客上记录的[示例 Python 代码][13]是对该 API 的一个很好的介绍。一旦你理解了它是如何工作的,你就可以扩展它来连接 `sudo` 到工单系统,并且只批准有相关开放工单的会话。你也可以连接到人力资源数据库,这样只有当班的工程师才能获得管理权限。
|
||||
与审计插件 API 一样,你可以从 C 和 Python 中使用它。我博客上记录的[示例 Python 代码][13]是对该 API 的一个很好的介绍。一旦你理解了它是如何工作的,你就可以扩展它来将 `sudo` 连接到工单系统,并且只批准有相关开放工单的会话。你也可以连接到人力资源数据库,这样只有当班的工程师才能获得管理权限。
|
||||
|
||||
### Python 对插件的支持
|
||||
|
||||
@ -70,7 +70,7 @@
|
||||
|
||||
除了审计和审批插件 API 之外,还有一些其他的 API,你可以用它们做一些非常有趣的事情。
|
||||
|
||||
通过使用策略插件 API,你可以取代 `sudo` 策略引擎。请注意,你将失去大部分的 `sudo` 功能,而且没有更多基于 `sudoers` 的配置。这在小众情况下还是很有用的,但大多数时候,最好还是继续使用 `sudoers`,并使用审批插件 API 创建额外的策略。如果你想尝试一下,我的 [Python 插件介绍][14]提供了一个非常简单的策略:只允许使用 `id` 命令。再次确认你知道 root 密码,因为一旦启用这个策略,它就会阻止任何实际使用 `sudo` 的行为。
|
||||
通过使用策略插件 API,你可以取代 `sudo` 策略引擎。请注意,你将失去大部分的 `sudo` 功能,而且没有基于 `sudoers` 的配置。这在小众情况下还是很有用的,但大多数时候,最好还是继续使用 `sudoers`,并使用审批插件 API 创建额外的策略。如果你想尝试一下,我的 [Python 插件介绍][14]提供了一个非常简单的策略:只允许使用 `id` 命令。再次确认你知道 root 密码,因为一旦启用这个策略,它就会阻止任何实际使用 `sudo` 的行为。
|
||||
|
||||
使用 I/O 日志 API,你可以访问用户会话的输入和输出。这意味着你可以分析会话中发生了什么,甚至在发现可疑情况时终止会话。这个 API 有很多可能的用途,比如防止数据泄露。你可以监控屏幕上的关键字,如果数据流中出现任何关键字,你可以在关键字出现在用户的屏幕上之前中断连接。另一种可能是检查用户正在输入的内容,并使用这些数据来重建用户正在输入的命令行。例如,如果用户输入 `rm -fr /`,你可以在按下回车键之前就断开用户的连接。
|
||||
|
||||
@ -78,11 +78,11 @@
|
||||
|
||||
### chroot 和 CWD 支持
|
||||
|
||||
`sudo` 的最新功能是支持 chroot 和改变工作目录(CWD),这两个选项都不是默认启用的,你需要在 `sudoers` 文件中明确启用它们。当它们被启用时,你可以微调目标目录或允许用户指定使用哪个目录。日志反映了这些设置何时被使用。
|
||||
`sudo` 的最新功能是支持 chroot 和改变工作目录(CWD),这两个选项都不是默认启用的,你需要在 `sudoers` 文件中明确启用它们。当它们被启用时,你可以调整目标目录或允许用户指定使用哪个目录。日志反映了这些设置何时被使用。
|
||||
|
||||
在大多数系统中,chroot 只对 root 用户开放。如果你的某个用户需要 chroot,你需要给他们 root 权限,这比仅仅给他们 chroot 权限要大得多。另外,你可以通过 `sudo` 允许访问 chroot 命令,但它仍然允许漏洞,他们可以获得完全的权限。当你使用 `sudo` 内置的 chroot 支持时,你可以轻松地限制对单个目录的访问。你也可以让用户灵活地指定根目录。当然,这可能会导致灾难(例如,`sudo --chroot / -s`),但至少事件会被记录下来。
|
||||
|
||||
当你通过 `sudo` 运行一个命令时,它会将工作目录设置为当前目录。这是预期的行为,但可能有一些情况下,命令需要在不同的目录下运行。例如,我记得使用一个应用程序,它通过检查我的工作目录是否是 `/root` 来检查我的权限。
|
||||
当你通过 `sudo` 运行一个命令时,它会将工作目录设置为当前目录。这是预期的行为,但可能有一些情况下,命令需要在不同的目录下运行。例如,我记得使用过一个应用程序,它通过检查我的工作目录是否是 `/root` 来检查我的权限。
|
||||
|
||||
### 尝试新功能
|
||||
|
||||
@ -101,7 +101,7 @@ via: https://opensource.com/article/20/10/sudo-19
|
||||
作者:[Peter Czanik][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[wxy](https://github.com/wxy)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
@ -1,8 +1,8 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (geekpi)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: reviewer: (wxy)
|
||||
[#]: publisher: (wxy)
|
||||
[#]: url: (https://linux.cn/article-12866-1.html)
|
||||
[#]: subject: (Compare Files and Folders Graphically in Linux With Meld)
|
||||
[#]: via: (https://itsfoss.com/meld-gui-diff/)
|
||||
[#]: author: (Abhishek Prakash https://itsfoss.com/author/abhishek/)
|
||||
@ -10,19 +10,21 @@
|
||||
使用 Meld 在 Linux 中以图形方式比较文件和文件夹
|
||||
======
|
||||
|
||||
![](https://img.linux.net.cn/data/attachment/album/202011/28/145914mr5zcl2wnns8rj5j.jpg)
|
||||
|
||||
如何比较两个相似的文件来检查差异?答案显而易见,就是[使用 Linux 中的 diff 命令][1]。
|
||||
|
||||
问题是,并不是每个人都能自如地在 Linux 终端中比较文件,而且 diff 命令的输出可能会让一些人感到困惑。
|
||||
问题是,并不是每个人都能自如地在 Linux 终端中比较文件,而且 `diff` 命令的输出可能会让一些人感到困惑。
|
||||
|
||||
以这个 diff 命令的输出为例:
|
||||
以这个 `diff` 命令的输出为例:
|
||||
|
||||
![][2]
|
||||
|
||||
这里肯定涉及到一个学习曲线。然而,如果你使用的是桌面 Linux,你可以使用 [GUI][3] 应用来轻松比较两个文件是否有任何差异。
|
||||
|
||||
有几个 Linux 中的 GUI diff 工具。我将在本周的 Linux 应用亮点中重点介绍我最喜欢的工具 Meld。
|
||||
有几个 Linux 中的 GUI 差异比较工具。我将在本周的 Linux 应用亮点中重点介绍我最喜欢的工具 Meld。
|
||||
|
||||
### Meld:Linux (及 Windows) 下的可视化比较和合并工具
|
||||
### Meld:Linux(及 Windows)下的可视化比较和合并工具
|
||||
|
||||
通过 [Meld][4],你可以将两个文件并排比较。不仅如此,你还可以对文件进行相应的修改。这是你在大多数情况下想做的事情,对吗?
|
||||
|
||||
@ -45,19 +47,17 @@ Meld 还能够比较目录,并显示哪些文件是不同的。它还会显示
|
||||
开源的 Meld 工具具有以下主要功能:
|
||||
|
||||
* 进行双向和三向差异比较
|
||||
* 就地编辑文件,差异比较立即更新。
|
||||
* 就地编辑文件,差异比较立即更新
|
||||
* 在差异和冲突之间进行导航
|
||||
* 通过插入、更改和冲突相应地标示出全局和局部差异,使其可视化
|
||||
* 使用正则文本过滤来忽略某些差异。
|
||||
* 使用正则文本过滤来忽略某些差异
|
||||
* 语法高亮显示
|
||||
* 比较两个或三个目录,看是否有新增加、缺失和更改的文件。
|
||||
* 比较两个或三个目录,看是否有新增加、缺失和更改的文件
|
||||
* 将一些文件排除在比较之外
|
||||
* 支持流行的版本控制系统,如 Git、Mercurial、Bazaar 和 SVN。
|
||||
* 支持流行的版本控制系统,如 Git、Mercurial、Bazaar 和 SVN
|
||||
* 支持多种国际语言
|
||||
* 开源 GPL v2 许可证
|
||||
* 既可用于 Linux,也可用于 Windows。
|
||||
|
||||
|
||||
* 既可用于 Linux,也可用于 Windows
|
||||
|
||||
### 在 Linux 上安装 Meld
|
||||
|
||||
@ -77,7 +77,7 @@ sudo apt install meld
|
||||
|
||||
[Meld Source Code][14]
|
||||
|
||||
### 它值得吗?
|
||||
### 它值得使用吗?
|
||||
|
||||
我知道[大多数现代开源编辑器][15]都有这个功能,但有时你只是想要一个简单的界面,而不需要安装额外的附加软件来比较文件。Meld 就为你提供了这样的功能。
|
||||
|
||||
@ -90,7 +90,7 @@ via: https://itsfoss.com/meld-gui-diff/
|
||||
作者:[Abhishek Prakash][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
@ -0,0 +1,90 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Why failure should be normalized and how to do it)
|
||||
[#]: via: (https://opensource.com/article/20/11/normalize-failure)
|
||||
[#]: author: (Lisa Seelye https://opensource.com/users/lisa)
|
||||
|
||||
Why failure should be normalized and how to do it
|
||||
======
|
||||
"Everybody is perfect, except you." This toxic thought can creep in and
|
||||
ruin your confidence. Here's how to normalize failure during the
|
||||
learning process and remember that everyone makes mistakes.
|
||||
![failure sign at a party, celebrating failure][1]
|
||||
|
||||
All of your heroes have failures under their belts—from minor mistakes to major disasters. Nobody knows how to do everything automatically, and the process of learning is usually a messy one. So why is the perception that everyone but you knows what they’re doing so common? Why do we externalize our successes but internalize our failures?
|
||||
|
||||
How does it make you feel when you struggle to learn something new, then see another person take their Jira card away and return at the end of the sprint with something fully fleshed out and working, gushing about it at the demo? Sure, you closed your card too, but it was really hard! There was a new algorithm, a new programming language, a new system all to be learned. How did she make it look so effortless?
|
||||
|
||||
The truth is, she might have struggled with the same issues you did and wondered how you made it look so effortless!
|
||||
|
||||
### Failure is normal, healthy, and invisible
|
||||
|
||||
Whether we call them mistakes, bad assumptions, or some other euphemism, it's hard not to judge ourselves or expect better of ourselves, especially compared to other people.
|
||||
|
||||
My background is largely in Linux systems administration, and in this line of work, it is often a matter of "when" and not "if" we will have a production service disruption. Those service disruptions can happen for various reasons—sometimes it's because a person made a mistake. Setting aside the discussion around controls to mitigate human error, we can see plain as day that I, Lisa Seelye, made a mistake that directly caused a production problem.
|
||||
|
||||
Whenever a group of sysadmins gets together, we usually end up talking about our work, and inevitably, we get around to stories of production service disruptions that we’ve been a part of (or caused). It’s cathartic to hear how badly other people have messed up and then look around and see that we are all human and making mistakes is part of that.
|
||||
|
||||
I feel that this kind of sharing is vital to the success of people in the information technology sector.
|
||||
|
||||
### Why should we share?
|
||||
|
||||
In addition to sharing our mistakes in order to normalize them, I also believe that it is equally important to share our learning processes—this is both to drive home the idea that we all start somewhere and that learning is often filled with failures and misconceptions.
|
||||
|
||||
As an individual, I need to remind myself that it may only appear that my peers return from a week working on a card with a fully fleshed-out solution. Reality may, in fact, be that they don’t understand the requirements, the codebase, the language, the algorithms needed, etc. Either way, it’s a logical fallacy to believe they do not face these challenges because of the appearance of the final product.
|
||||
|
||||
### But why should we share?
|
||||
|
||||
We should share our learning experiences because we all benefit from hearing about the challenges other people face and how they overcome them. If the Jira card wasn’t clear, then we can do better. If the algorithm wasn’t clear, then maybe education can be done around it.
|
||||
|
||||
Most importantly, we need to normalize that it’s okay not to know everything, that it’s okay to still be learning, and to ask for help. Setting an example for new or more junior engineers is important. In our industry, we deal with extremely complex systems that can interact with one another in strange or unexpected ways. In many cases, it is simply not possible for one person to know everything. Being open about our learning processes and our mistakes can lead to tighter bonding.
|
||||
|
||||
Do new engineers on your team have the set expectation that it’s okay to interrupt and ask questions? Saying it on day one is easy, but practicing the value is another thing. How are approachability and openness demonstrated in your team?
|
||||
|
||||
### My learning opportunities
|
||||
|
||||
It could be very easy to title this section "my mistakes" and then rattle off all the times I’ve made mistakes, but that doesn’t quite illustrate the point. I recognize these mistakes, but they’re also events that expanded the understanding of my craft. While I didn’t set out to intentionally do any of these things, I certainly learned from them.
|
||||
|
||||
I have accidentally dropped (deleted) a customer’s database. It was lucky for everyone that it was a beta-phase database and no further harm was done. I learned a valuable lesson that day: be very watchful of what code is doing, and be careful about what environment you are working in.
|
||||
|
||||
One day, while performing routine maintenance with an odd DNS setup, I accidentally broke the ability for customers to provide credit card information to the secure site. We had two "payments" DNS records that served to override a wildcard DNS record, and I assumed that the second "payments" record was still present. It wasn’t. And then the wildcard record took over, and the DNS started behaving like "payments" wasn’t special at all anymore. Of course, I had no idea this was happening at all—it wasn’t until my maintenance was over that I learned of the folly.
|
||||
|
||||
Customers weren’t able to provide payment information for almost two hours! I learned my lesson, though: when there is something special about a particular configuration, be sure to make sure it stays special throughout its lifetime. When DNS gets involved, all kinds of things can break.
|
||||
|
||||
Before I started speaking at conferences, I was an attendee, and I watched talks online. Pivoting to speaking myself, I was worried that I’d say too many "umms" and "uhs" and that my jokes would fall flat. The speakers I enjoyed over the years seemed to not have those problems at all, while I was unpolished.
|
||||
|
||||
But once I got up on stage, I found that my perception had changed. I had practiced my talk with an audience and listened to their feedback—turns out I had a little polish. In front of the audience, I did misspeak and not say something exactly how I wanted, but it didn’t matter. What I didn’t realize from my vantage point in the audience is that the audience wants the speaker to succeed, and the speaker can shift directions in their talk without the audience knowing.
|
||||
|
||||
I certainly have made and continue to make those sounds, and I have even had to correct major factual information the night before a talk, but the audience never knows. The audience sees what I show them and, because they want me to succeed, they forgive my "umms" and "uhs."
|
||||
|
||||
I admit that I am not perfect. I ship bugs, and I try to learn from them.
|
||||
|
||||
### How to share
|
||||
|
||||
Sharing the difficulties we’ve encountered along the learning process or in our day to day career is important, though just as important is how they are shared. I share the things I’ve learned (the hard way) with frankness and no self-judgment. It is in this spirit that I think we should all share. I’m not a bad person because I’ve made mistakes, and neither are you.
|
||||
|
||||
How, then, should we share? Who is the audience?
|
||||
|
||||
In the midst of a production service disruption, like with the DNS payment situation above, there’s no room for coyness or hiding anything. The most important thing is to make key stakeholders aware and then rally to fix the situation. The audience is first internal—your team, business leaders, the support team. Next, the audience is outward-facing—the customers. It is wise to involve communications experts when crafting that outward message.
|
||||
|
||||
When sharing with a junior engineer, we need to normalize the learning process. People aren’t born knowing how pointers in C work, so we all need to learn what the pitfalls of pointers are. It’s okay to need to learn new skills at any skill level, and it’s also okay to need to reinforce those skills. The message we send should be free of judgment.
|
||||
|
||||
So write a blog post, make a Twitter thread, share frustrations in Slack channels, ask for help. Together we can dismiss the myth that everybody's perfect, except you.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/20/11/normalize-failure
|
||||
|
||||
作者:[Lisa Seelye][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/lisa
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/fail_failure_celebrate.png?itok=LbvDAEZF (failure sign at a party, celebrating failure)
|
@ -1,682 +0,0 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (robsean)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Add throwing mechanics to your Python game)
|
||||
[#]: via: (https://opensource.com/article/20/9/add-throwing-python-game)
|
||||
[#]: author: (Seth Kenlon https://opensource.com/users/seth)
|
||||
|
||||
Add throwing mechanics to your Python game
|
||||
======
|
||||
Running around avoiding enemies is one thing. Fighting back is another.
|
||||
Learn how in the 12th article in this series on creating a platformer in
|
||||
Pygame.
|
||||
![Gaming on a grid with penguin pawns][1]
|
||||
|
||||
This is part 12 in an ongoing series about creating video games in [Python 3][2] using the [Pygame][3] module. Previous articles are:
|
||||
|
||||
1. [Learn how to program in Python by building a simple dice game][4]
|
||||
2. [Build a game framework with Python using the Pygame module][5]
|
||||
3. [How to add a player to your Python game][6]
|
||||
4. [Using Pygame to move your game character around][7]
|
||||
5. [What's a hero without a villain? How to add one to your Python game][8]
|
||||
6. [Put platforms in a Python game with Pygame][9]
|
||||
7. [Simulate gravity in your Python game][10]
|
||||
8. [Add jumping to your Python platformer game][11]
|
||||
9. [Enable your Python game player to run forward and backward][12]
|
||||
10. [Put some loot in your Python platformer game][13]
|
||||
11. [Add scorekeeping to your Python game][14]
|
||||
|
||||
|
||||
|
||||
My previous article was meant to be the final article in this series, and it encouraged you to go program your own additions to this game. Many of you did! I got emails asking for help with a common mechanic that I hadn't yet covered: combat. After all, jumping to avoid baddies is one thing, but sometimes it's awfully satisfying to just make them go away. It's common in video games to throw something at your enemies, whether it's a ball of fire, an arrow, a bolt of lightning, or whatever else fits the game.
|
||||
|
||||
Unlike anything you have programmed for your platformer game in this series so far, throwable items have a _time to live_. Once you throw an object, it's expected to travel some distance and then disappear. If it's an arrow or something like that, it may disappear when it passes the edge of the screen. If it's a fireball or a bolt of lightning, it might fizzle out after some amount of time.
|
||||
|
||||
That means each time a throwable item is spawned, a unique measure of its lifespan must also be spawned. To introduce this concept, this article demonstrates how to throw only one item at a time. (In other words, only one throwable item may exist at a time.) On the one hand, this is a game limitation, but on the other hand, it is a game mechanic in itself. Your player won't be able to throw 50 fireballs at once, since you only allow one at a time, so it becomes a challenge for your player to time when they release a fireball to try to hit an enemy. And behind the scenes, this also keeps your code simple.
|
||||
|
||||
If you want to enable more throwable items at once, challenge yourself after you finish this tutorial by building on the knowledge you gain.
|
||||
|
||||
### Create the throwable class
|
||||
|
||||
If you followed along with the other articles in this series, you should be familiar with the basic `__init__` function when spawning a new object on the screen. It's the same function you used for spawning your [player][6] and your [enemies][8]. Here's an `__init__` function to spawn a throwable object:
|
||||
|
||||
|
||||
```
|
||||
class Throwable(pygame.sprite.Sprite):
|
||||
"""
|
||||
Spawn a throwable object
|
||||
"""
|
||||
def __init__(self, x, y, img, throw):
|
||||
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.firing = throw
|
||||
```
|
||||
|
||||
The primary difference in this function compared to your `Player` class or `Enemy` class `__init__` function is that it has a `self.firing` variable. This variable keeps track of whether or not a throwable object is currently alive on screen, so it stands to reason that when a throwable object is created, the variable is set to `1`.
|
||||
|
||||
### Measure time to live
|
||||
|
||||
Next, just as with `Player` and `Enemy`, you need an `update` function so that the throwable object moves on its own once it's thrown into the air toward an enemy.
|
||||
|
||||
The easiest way to determine the lifespan of a throwable object is to detect when it goes off-screen. Which screen edge you need to monitor depends on the physics of your throwable object.
|
||||
|
||||
* If your player is throwing something that travels quickly along the horizontal axis, like a crossbow bolt or arrow or a very fast magical force, then you want to monitor the horizontal limit of your game screen. This is defined by `worldx`.
|
||||
* If your player is throwing something that travels vertically or both horizontally and vertically, then you must monitor the vertical limit of your game screen. This is defined by `worldy`.
|
||||
|
||||
|
||||
|
||||
This example assumes your throwable object goes a little forward and eventually falls to the ground. The object does not bounce off the ground, though, and continues to fall off the screen. You can try different settings to see what fits your game best:
|
||||
|
||||
|
||||
```
|
||||
def update(self,worldy):
|
||||
'''
|
||||
throw physics
|
||||
'''
|
||||
if self.rect.y < worldy: #vertical axis
|
||||
self.rect.x += 15 #how fast it moves forward
|
||||
self.rect.y += 5 #how fast it falls
|
||||
else:
|
||||
self.kill() #remove throwable object
|
||||
self.firing = 0 #free up firing slot
|
||||
```
|
||||
|
||||
To make your throwable object move faster, increase the momentum of the `self.rect` values.
|
||||
|
||||
If the throwable object is off-screen, then the object is destroyed, freeing up the RAM that it had occupied. In addition, `self.firing` is set back to `0` to allow your player to take another shot.
|
||||
|
||||
### Set up your throwable object
|
||||
|
||||
Just like with your player and enemies, you must create a sprite group in your setup section to hold the throwable object.
|
||||
|
||||
Additionally, you must create an inactive throwable object to start the game with. If there isn't a throwable object when the game starts, the first time a player attempts to throw a weapon, it will fail.
|
||||
|
||||
This example assumes your player starts with a fireball as a weapon, so each instance of a throwable object is designated by the `fire` variable. In later levels, as the player acquires new skills, you could introduce a new variable using a different image but leveraging the same `Throwable` class.
|
||||
|
||||
In this block of code, the first two lines are already in your code, so don't retype them:
|
||||
|
||||
|
||||
```
|
||||
player_list = pygame.sprite.Group() #context
|
||||
player_list.add(player) #context
|
||||
fire = Throwable(player.rect.x,player.rect.y,'fire.png',0)
|
||||
firepower = pygame.sprite.Group()
|
||||
```
|
||||
|
||||
Notice that a throwable item starts at the same location as the player. That makes it look like the throwable item is coming from the player. The first time the fireball is generated, a `0` is used so that `self.firing` shows as available.
|
||||
|
||||
### Get throwing in the main loop
|
||||
|
||||
Code that doesn't appear in the main loop will not be used in the game, so you need to add a few things in your main loop to get your throwable object into your game world.
|
||||
|
||||
First, add player controls. Currently, you have no firepower trigger. There are two states for a key on a keyboard: the key can be down, or the key can be up. For movement, you use both: pressing down starts the player moving, and releasing the key (the key is up) stops the player. Firing needs only one signal. It's a matter of taste as to which key event (a key press or a key release) you use to trigger your throwable object.
|
||||
|
||||
In this code block, the first two lines are for context:
|
||||
|
||||
|
||||
```
|
||||
if event.key == pygame.K_UP or event.key == ord('w'):
|
||||
player.jump(platform_list)
|
||||
if event.key == pygame.K_SPACE:
|
||||
if not fire.firing:
|
||||
fire = Throwable(player.rect.x,player.rect.y,'fire.png',1)
|
||||
firepower.add(fire)
|
||||
```
|
||||
|
||||
Unlike the fireball you created in your setup section, you use a `1` to set `self.firing` as unavailable.
|
||||
|
||||
Finally, you must update and draw your throwable object. The order of this matters, so put this code between your existing `enemy.move` and `player_list.draw` lines:
|
||||
|
||||
|
||||
```
|
||||
enemy.move() # context
|
||||
|
||||
if fire.firing:
|
||||
fire.update(worldy)
|
||||
firepower.draw(world)
|
||||
player_list.draw(screen) # context
|
||||
enemy_list.draw(screen) # context
|
||||
```
|
||||
|
||||
Notice that these updates are performed only if the `self.firing` variable is set to 1. If it is set to 0, then `fire.firing` is not true, and the updates are skipped. If you tried to do these updates, no matter what, your game would crash because there wouldn't be a `fire` object to update or draw.
|
||||
|
||||
Launch your game and try to throw your weapon.
|
||||
|
||||
### Detect collisions
|
||||
|
||||
If you played your game with the new throwing mechanic, you probably noticed that you can throw objects, but it doesn't have any effect on your foes.
|
||||
|
||||
The reason is that your enemies do not check for a collision. An enemy can be hit by your throwable object and never know about it.
|
||||
|
||||
You've already done collision detection in your `Player` class, and this is very similar. In your `Enemy` class, add a new `update` function:
|
||||
|
||||
|
||||
```
|
||||
def update(self,firepower, enemy_list):
|
||||
"""
|
||||
detect firepower collision
|
||||
"""
|
||||
fire_hit_list = pygame.sprite.spritecollide(self,firepower,False)
|
||||
for fire in fire_hit_list:
|
||||
enemy_list.remove(self)
|
||||
```
|
||||
|
||||
The code is simple. Each enemy object checks to see if it has been hit by the `firepower` sprite group. If it has, then the enemy is removed from the enemy group and disappears.
|
||||
|
||||
To integrate that function into your game, call the function in your new firing block in the main loop:
|
||||
|
||||
|
||||
```
|
||||
if fire.firing: # context
|
||||
fire.update(worldy) # context
|
||||
firepower.draw(screen) # context
|
||||
enemy_list.update(firepower,enemy_list) # update enemy
|
||||
```
|
||||
|
||||
You can try your game now, and most everything works as expected. There's still one problem, though, and that's the direction of the throw.
|
||||
|
||||
### Change the throw mechanic direction
|
||||
|
||||
Currently, your hero's fireball moves only to the right. This is because the `update` function of the `Throwable` class adds pixels to the position of the fireball, and in Pygame, a larger number on the X-axis means movement toward the right of the screen. When your hero turns the other way, you probably want it to throw its fireball to the left.
|
||||
|
||||
By this point, you know how to implement this, at least technically. However, the easiest solution uses a variable in what may be a new way for you. Generically, you can "set a flag" (sometimes also termed "flip a bit") to indicate the direction your hero is facing. Once you do that, you can check that variable to learn whether the fireball needs to move left or right.
|
||||
|
||||
First, create a new variable in your `Player` class to represent which direction your hero is facing. Because my hero faces right naturally, I treat that as the default:
|
||||
|
||||
|
||||
```
|
||||
self.score = 0
|
||||
self.facing_right = True # add this
|
||||
self.is_jumping = True
|
||||
```
|
||||
|
||||
When this variable is `True`, your hero sprite is facing right. It must be set anew every time the player changes the hero's direction, so do that in your main loop on the relevant `keyup` events:
|
||||
|
||||
|
||||
```
|
||||
if event.type == pygame.KEYUP:
|
||||
if event.key == pygame.K_LEFT or event.key == ord('a'):
|
||||
player.control(steps, 0)
|
||||
player.facing_right = False # add this line
|
||||
if event.key == pygame.K_RIGHT or event.key == ord('d'):
|
||||
player.control(-steps, 0)
|
||||
player.facing_right = True # add this line
|
||||
```
|
||||
|
||||
Finally, change the `update` function of your `Throwable` class to check whether the hero is facing right or not and to add or subtract pixels from the fireball's position as appropriate:
|
||||
|
||||
|
||||
```
|
||||
if self.rect.y < worldy:
|
||||
if player.facing_right:
|
||||
self.rect.x += 15
|
||||
else:
|
||||
self.rect.x -= 15
|
||||
self.rect.y += 5
|
||||
```
|
||||
|
||||
Try your game again and clear your world of some baddies.
|
||||
|
||||
![Python platformer with throwing capability][15]
|
||||
|
||||
(Seth Kenlon, [CC BY-SA 4.0][16])
|
||||
|
||||
As a bonus challenge, try incrementing your player's score whenever an enemy is vanquished.
|
||||
|
||||
### The complete code
|
||||
|
||||
|
||||
```
|
||||
#!/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/\>][17].
|
||||
|
||||
import pygame
|
||||
import pygame.freetype
|
||||
import sys
|
||||
import os
|
||||
|
||||
'''
|
||||
Variables
|
||||
'''
|
||||
|
||||
worldx = 960
|
||||
worldy = 720
|
||||
fps = 40
|
||||
ani = 4
|
||||
world = pygame.display.set_mode([worldx, worldy])
|
||||
forwardx = 600
|
||||
backwardx = 120
|
||||
|
||||
BLUE = (80, 80, 155)
|
||||
BLACK = (23, 23, 23)
|
||||
WHITE = (254, 254, 254)
|
||||
ALPHA = (0, 255, 0)
|
||||
|
||||
tx = 64
|
||||
ty = 64
|
||||
|
||||
font_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fonts", "amazdoom.ttf")
|
||||
font_size = tx
|
||||
pygame.freetype.init()
|
||||
myfont = pygame.freetype.Font(font_path, font_size)
|
||||
|
||||
'''
|
||||
Objects
|
||||
'''
|
||||
|
||||
def stats(score, health):
|
||||
myfont.render_to(world, (4, 4), "Score:"+str(score), BLUE, None, size=64)
|
||||
myfont.render_to(world, (4, 72), "Health:"+str(health), BLUE, None, size=64)
|
||||
|
||||
class Throwable(pygame.sprite.Sprite):
|
||||
"""
|
||||
Spawn a throwable object
|
||||
"""
|
||||
def __init__(self, x, y, img, throw):
|
||||
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.firing = throw
|
||||
|
||||
def update(self, worldy):
|
||||
'''
|
||||
throw physics
|
||||
'''
|
||||
if self.rect.y < worldy:
|
||||
if player.facing_right:
|
||||
self.rect.x += 15
|
||||
else:
|
||||
self.rect.x -= 15
|
||||
self.rect.y += 5
|
||||
else:
|
||||
self.kill()
|
||||
self.firing = 0
|
||||
|
||||
# x location, y location, img width, img height, img file
|
||||
class Platform(pygame.sprite.Sprite):
|
||||
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
|
||||
|
||||
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.damage = 0
|
||||
self.score = 0
|
||||
self.facing_right = True
|
||||
self.is_jumping = True
|
||||
self.is_falling = True
|
||||
self.images = []
|
||||
for i in range(1, 5):
|
||||
img = pygame.image.load(os.path.join('images', 'walk' + 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 gravity(self):
|
||||
if self.is_jumping:
|
||||
self.movey += 3.2
|
||||
|
||||
def control(self, x, y):
|
||||
"""
|
||||
control player movement
|
||||
"""
|
||||
self.movex += x
|
||||
|
||||
def jump(self):
|
||||
if self.is_jumping is False:
|
||||
self.is_falling = False
|
||||
self.is_jumping = True
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
Update sprite position
|
||||
"""
|
||||
|
||||
# moving left
|
||||
if self.movex < 0:
|
||||
self.is_jumping = True
|
||||
self.frame += 1
|
||||
if self.frame > 3 * ani:
|
||||
self.frame = 0
|
||||
self.image = pygame.transform.flip(self.images[self.frame // ani], True, False)
|
||||
|
||||
# moving right
|
||||
if self.movex > 0:
|
||||
self.is_jumping = True
|
||||
self.frame += 1
|
||||
if self.frame > 3 * ani:
|
||||
self.frame = 0
|
||||
self.image = self.images[self.frame // ani]
|
||||
|
||||
# collisions
|
||||
enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False)
|
||||
if self.damage == 0:
|
||||
for enemy in enemy_hit_list:
|
||||
if not self.rect.contains(enemy):
|
||||
self.damage = self.rect.colliderect(enemy)
|
||||
if self.damage == 1:
|
||||
idx = self.rect.collidelist(enemy_hit_list)
|
||||
if idx == -1:
|
||||
self.damage = 0 # set damage back to 0
|
||||
self.health -= 1 # subtract 1 hp
|
||||
|
||||
ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False)
|
||||
for g in ground_hit_list:
|
||||
self.movey = 0
|
||||
self.rect.bottom = g.rect.top
|
||||
self.is_jumping = False # stop jumping
|
||||
|
||||
# fall off the world
|
||||
if self.rect.y > worldy:
|
||||
self.health -=1
|
||||
print(self.health)
|
||||
self.rect.x = tx
|
||||
self.rect.y = ty
|
||||
|
||||
plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False)
|
||||
for p in plat_hit_list:
|
||||
self.is_jumping = False # stop jumping
|
||||
self.movey = 0
|
||||
if self.rect.bottom <= p.rect.bottom:
|
||||
self.rect.bottom = p.rect.top
|
||||
else:
|
||||
self.movey += 3.2
|
||||
|
||||
if self.is_jumping and self.is_falling is False:
|
||||
self.is_falling = True
|
||||
self.movey -= 33 # how high to jump
|
||||
|
||||
loot_hit_list = pygame.sprite.spritecollide(self, loot_list, False)
|
||||
for loot in loot_hit_list:
|
||||
loot_list.remove(loot)
|
||||
self.score += 1
|
||||
print(self.score)
|
||||
|
||||
plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False)
|
||||
|
||||
self.rect.x += self.movex
|
||||
self.rect.y += self.movey
|
||||
|
||||
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
|
||||
|
||||
def update(self, firepower, enemy_list):
|
||||
"""
|
||||
detect firepower collision
|
||||
"""
|
||||
fire_hit_list = pygame.sprite.spritecollide(self, firepower, False)
|
||||
for fire in fire_hit_list:
|
||||
enemy_list.remove(self)
|
||||
|
||||
class Level:
|
||||
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
|
||||
|
||||
def bad(lvl, eloc):
|
||||
if lvl == 1:
|
||||
enemy = Enemy(eloc[0], eloc[1], 'enemy.png')
|
||||
enemy_list = pygame.sprite.Group()
|
||||
enemy_list.add(enemy)
|
||||
if lvl == 2:
|
||||
print("Level " + str(lvl))
|
||||
|
||||
return enemy_list
|
||||
|
||||
# x location, y location, img width, img height, img file
|
||||
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((550, 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
|
||||
|
||||
def loot(lvl):
|
||||
if lvl == 1:
|
||||
loot_list = pygame.sprite.Group()
|
||||
loot = Platform(tx*5, ty*5, tx, ty, 'loot_1.png')
|
||||
loot_list.add(loot)
|
||||
|
||||
if lvl == 2:
|
||||
print(lvl)
|
||||
|
||||
return loot_list
|
||||
|
||||
'''
|
||||
Setup
|
||||
'''
|
||||
|
||||
backdrop = pygame.image.load(os.path.join('images', 'stage.png'))
|
||||
clock = pygame.time.Clock()
|
||||
pygame.init()
|
||||
backdropbox = world.get_rect()
|
||||
main = True
|
||||
|
||||
player = Player() # spawn player
|
||||
player.rect.x = 0 # go to x
|
||||
player.rect.y = 30 # go to y
|
||||
player_list = pygame.sprite.Group()
|
||||
player_list.add(player)
|
||||
steps = 10
|
||||
fire = Throwable(player.rect.x, player.rect.y, 'fire.png', 0)
|
||||
firepower = pygame.sprite.Group()
|
||||
|
||||
eloc = []
|
||||
eloc = [300, worldy-ty-80]
|
||||
enemy_list = Level.bad(1, eloc)
|
||||
gloc = []
|
||||
|
||||
i = 0
|
||||
while i <= (worldx / tx) + tx:
|
||||
gloc.append(i * tx)
|
||||
i = i + 1
|
||||
|
||||
ground_list = Level.ground(1, gloc, tx, ty)
|
||||
plat_list = Level.platform(1, tx, ty)
|
||||
enemy_list = Level.bad( 1, eloc )
|
||||
loot_list = Level.loot(1)
|
||||
|
||||
'''
|
||||
Main Loop
|
||||
'''
|
||||
|
||||
while main:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
pygame.quit()
|
||||
try:
|
||||
sys.exit()
|
||||
finally:
|
||||
main = False
|
||||
|
||||
if event.type == pygame.KEYDOWN:
|
||||
if event.key == ord('q'):
|
||||
pygame.quit()
|
||||
try:
|
||||
sys.exit()
|
||||
finally:
|
||||
main = False
|
||||
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'):
|
||||
player.jump()
|
||||
|
||||
if event.type == pygame.KEYUP:
|
||||
if event.key == pygame.K_LEFT or event.key == ord('a'):
|
||||
player.control(steps, 0)
|
||||
player.facing_right = False
|
||||
if event.key == pygame.K_RIGHT or event.key == ord('d'):
|
||||
player.control(-steps, 0)
|
||||
player.facing_right = True
|
||||
if event.key == pygame.K_SPACE:
|
||||
if not fire.firing:
|
||||
fire = Throwable(player.rect.x, player.rect.y, 'fire.png', 1)
|
||||
firepower.add(fire)
|
||||
|
||||
# scroll the world forward
|
||||
if player.rect.x >= forwardx:
|
||||
scroll = player.rect.x - forwardx
|
||||
player.rect.x = forwardx
|
||||
for p in plat_list:
|
||||
p.rect.x -= scroll
|
||||
for e in enemy_list:
|
||||
e.rect.x -= scroll
|
||||
for l in loot_list:
|
||||
l.rect.x -= scroll
|
||||
|
||||
# scroll the world backward
|
||||
if player.rect.x <= backwardx:
|
||||
scroll = backwardx - player.rect.x
|
||||
player.rect.x = backwardx
|
||||
for p in plat_list:
|
||||
p.rect.x += scroll
|
||||
for e in enemy_list:
|
||||
e.rect.x += scroll
|
||||
for l in loot_list:
|
||||
l.rect.x += scroll
|
||||
|
||||
world.blit(backdrop, backdropbox)
|
||||
player.update()
|
||||
player.gravity()
|
||||
player_list.draw(world)
|
||||
if fire.firing:
|
||||
fire.update(worldy)
|
||||
firepower.draw(world)
|
||||
enemy_list.draw(world)
|
||||
enemy_list.update(firepower, enemy_list)
|
||||
loot_list.draw(world)
|
||||
ground_list.draw(world)
|
||||
plat_list.draw(world)
|
||||
for e in enemy_list:
|
||||
e.move()
|
||||
stats(player.score, player.health)
|
||||
pygame.display.flip()
|
||||
clock.tick(fps)
|
||||
```
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/20/9/add-throwing-python-game
|
||||
|
||||
作者:[Seth Kenlon][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/seth
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/game_pawn_grid_linux.png?itok=4gERzRkg (Gaming on a grid with penguin pawns)
|
||||
[2]: https://www.python.org/
|
||||
[3]: https://www.pygame.org/news
|
||||
[4]: https://opensource.com/article/17/10/python-101
|
||||
[5]: https://opensource.com/article/17/12/game-framework-python
|
||||
[6]: https://opensource.com/article/17/12/game-python-add-a-player
|
||||
[7]: https://opensource.com/article/17/12/game-python-moving-player
|
||||
[8]: https://opensource.com/article/18/5/pygame-enemy
|
||||
[9]: https://opensource.com/article/18/7/put-platforms-python-game
|
||||
[10]: https://opensource.com/article/19/11/simulate-gravity-python
|
||||
[11]: https://opensource.com/article/19/12/jumping-python-platformer-game
|
||||
[12]: https://opensource.com/article/19/12/python-platformer-game-run
|
||||
[13]: https://opensource.com/article/19/12/loot-python-platformer-game
|
||||
[14]: https://opensource.com/article/20/1/add-scorekeeping-your-python-game
|
||||
[15]: https://opensource.com/sites/default/files/uploads/pygame-throw.jpg (Python platformer with throwing capability)
|
||||
[16]: https://creativecommons.org/licenses/by-sa/4.0/
|
||||
[17]: http://www.gnu.org/licenses/\>
|
@ -0,0 +1,80 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (How to Go Full Dark Mode With LibreOffice)
|
||||
[#]: via: (https://itsfoss.com/libreoffice-dark-mode/)
|
||||
[#]: author: (Dimitrios Savvopoulos https://itsfoss.com/author/dimitrios/)
|
||||
|
||||
How to Go Full Dark Mode With LibreOffice
|
||||
======
|
||||
|
||||
[LibreOffice][1] is a free and open-source cross-platform office productivity software. If you’re not making the most of it, the [LibreOffice Tips][2] article is a must-read.
|
||||
|
||||
Dark theme is getting popular even among non-programmers. It is less stressing on the eyes specially for extended use of the screen. Some people believe that it makes the texts looks crisp and clear and that helps improve their productivity.
|
||||
|
||||
Some Linux distributions like [Ubuntu come with dark mode][3] these days giving your systems a darker tint. When you turn on the dark mode, some applications will automatically switch to dark mode.
|
||||
|
||||
LibreOffice also does that except the main area where you write:
|
||||
|
||||
![LibreOffice semi dark mode matching with the system theme][4]
|
||||
|
||||
You can change that. If you want to go complete dark mode with LibreOffice, you just have to change a few settings. Let me show you how to do that.
|
||||
|
||||
### How to enable complete dark mode in LibreOffice
|
||||
|
||||
As I mentioned earlier, you need to enable a system-wide dark mode first. This will ensure that the window color (or the title bar) blends well with the in-app dark color.
|
||||
|
||||
Next, open _**any**_ LibreOffice tool from the suite such as **Writer**. Then from the menu, click **Tools -> Options** **->** **Application Colors** and select **Document background & Application** background as **Black** or **Automatic** (whichever works for you).
|
||||
|
||||
![][5]
|
||||
|
||||
In case the icons are not in dark colour, you can change them from menu (as shown in the image below), **Tools -> Options** **->** **View** and my personal choice on MX Linux, is the [Yaru][6] icon style from Ubuntu (if you have a dark version of the icon pack, select that).
|
||||
|
||||
![][7]
|
||||
|
||||
Of course, you can also try some other [icon themes][8] available for Linux distros.
|
||||
|
||||
The end result should look like this:
|
||||
|
||||
![][9]
|
||||
|
||||
#### Additional tip for LibreOffice flatpak package
|
||||
|
||||
If you’re using the [Flatpak package][10] of LibreOffice suite, the header area (or the menu area) of LibreOffice may look white. In that case, you can try navigating to **Tools-> Options-> Personalization** and then select the “**Grey theme**” as shown in the screenshot below.
|
||||
|
||||
![][11]
|
||||
|
||||
It isn’t completely black but it should make things look better. Hope that helps you switch to a dark theme LibreOffice experience!
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Dark themes have slowly started to dominate our desktops, giving a modern taste and reducing the eye strain, especially in low light conditions.
|
||||
|
||||
LibreOffice gives you the freedom to switch your working environment to a fully dark theme or to keep light themed elements. In fact, you get a decent amount of customization options to tweak what you prefer. Have you switched to a dark theme on LibreOffice? Which color combination is your preferred? Let us know in the comments below!
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://itsfoss.com/libreoffice-dark-mode/
|
||||
|
||||
作者:[Dimitrios Savvopoulos][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://itsfoss.com/author/dimitrios/
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://www.libreoffice.org
|
||||
[2]: https://itsfoss.com/libreoffice-tips/
|
||||
[3]: https://itsfoss.com/dark-mode-ubuntu/
|
||||
[4]: https://i1.wp.com/itsfoss.com/wp-content/uploads/2020/11/libreOffice-dark-mode.png?resize=799%2C450&ssl=1
|
||||
[5]: https://i1.wp.com/itsfoss.com/wp-content/uploads/2020/11/1-libreoffice-application-colours.png?resize=800%2C551&ssl=1
|
||||
[6]: https://extensions.libreoffice.org/en/extensions/show/yaru-icon-theme
|
||||
[7]: https://i0.wp.com/itsfoss.com/wp-content/uploads/2020/11/2-libreoffice-iconstyle-1.png?resize=800%2C531&ssl=1
|
||||
[8]: https://itsfoss.com/best-icon-themes-ubuntu-16-04/
|
||||
[9]: https://i2.wp.com/itsfoss.com/wp-content/uploads/2020/11/3-libreoffice-dark.png?resize=800%2C612&ssl=1
|
||||
[10]: https://itsfoss.com/what-is-flatpak/
|
||||
[11]: https://i2.wp.com/itsfoss.com/wp-content/uploads/2020/11/libre-office-personalization.png?resize=800%2C636&ssl=1
|
@ -0,0 +1,681 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (robsean)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Add throwing mechanics to your Python game)
|
||||
[#]: via: (https://opensource.com/article/20/9/add-throwing-python-game)
|
||||
[#]: author: (Seth Kenlon https://opensource.com/users/seth)
|
||||
|
||||
在你的 Python 游戏中添加投掷技巧
|
||||
======
|
||||
四处奔跑躲避敌人是一回事。反击敌人是另一回事。
|
||||
学习如何在这系列的第十二篇文章中创建在 Pygame 中的平台游戏
|
||||
![使用企鹅兵来在格子上完游戏][1]
|
||||
|
||||
这是仍在进行中的关于使用 [Pygame][3] 模块来 [Python 3][2] 中创建电脑游戏的第十二部分。先前的文章是:
|
||||
|
||||
1. [通过构建一个简单的掷骰子游戏去学习怎么用 Python 编程][4]
|
||||
2. [使用 Python 和 Pygame 模块构建一个游戏框架][5]
|
||||
3. [如何在你的 Python 游戏中添加一个玩家][6]
|
||||
4. [用 Pygame 使你的游戏角色移动起来][7]
|
||||
5. [如何向你的 Python 游戏中添加一个敌人][8]
|
||||
6. [在 Pygame 游戏中放置平台][9]
|
||||
7. [在你的 Python 游戏中模拟引力][10]
|
||||
8. [为你的 Python 平台类游戏添加跳跃功能][11]
|
||||
9. [使你的 Python 游戏玩家能够向前和向后跑][12]
|
||||
10. [在你的 Python 平台类游戏中放一些奖励][13]
|
||||
11. [添加计分到你的 Python 游戏][14]
|
||||
|
||||
|
||||
|
||||
上一篇文章是这一系列文章的最后一篇,它鼓励你为这个游戏编写自己的附加程序。你们很多人都这么做了!我收到了一名普通的机械师的一些电子邮件,要求帮助我还没有涵盖的服务:战斗。毕竟,跳起来躲避坏人是一回事,但是有时候让他们走开是一件非常令人满意的事。在电脑游戏中向你的敌人投掷一些东西是很常见的,不管是一个火球,一支箭,一道闪电,还是其它适合游戏的东西。
|
||||
|
||||
在这个系列中,到目前为止,投掷项目不像你在平台类游戏中编程的任何东西,它有一个 _生存时间_。 在你投掷一个对象后,它会如期在移动一段距离后消失。如果它是一支箭或其它类似的东西,它可能会在通过屏幕的边缘时而消失。如果它是一个火球或一道闪电,它可能会在一段时间后熄灭。
|
||||
|
||||
这意味着每次生成一个可丢弃的项目时,它的生存时间也必需生成一个独特的衡量标准。为了介绍这个概念,这篇文章演示如何一次只投掷一个项目。(换句话说,每次仅存在一个投掷项目)。 一方面,这是一个游戏的限制条件,但另一方面,它却是游戏本身的运行机制。你的玩家不能每次都投掷 50 个火球,因为每次仅允许一个投掷项目,所以当你的玩家释放一个火球来尝试击中一名敌人就成为了的一项挑战。在幕后,这也使你的代码保持简单。
|
||||
|
||||
如果你想启用每次投掷多个项目,在完成这篇教程后,通过学习这篇教程所获取的知识来挑战你自己。
|
||||
|
||||
### 创建 throwable 类
|
||||
|
||||
如果你跟随学习这系列的其它文章,那么你应该熟悉基础的在屏幕上生成一个新的对象的 `__init__` 函数。这和你用来生成你的 [玩家][6] 和 [敌人][8] 的功能是一样的。这里是一个 `__init__` 函数来生成一个 投掷对象:
|
||||
|
||||
|
||||
```
|
||||
class Throwable(pygame.sprite.Sprite):
|
||||
"""
|
||||
生成一个投掷对象
|
||||
"""
|
||||
def __init__(self, x, y, img, throw):
|
||||
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.firing = throw
|
||||
```
|
||||
|
||||
同你的 `Player` 类或 `Enemy` 类的 `__init__` 函数相比,这个函数的主要区别是,它有一个 `self.firing` 变量。这个变量保持跟踪一个投掷对象是否在当前屏幕上活动,因此当一个投掷对象创建时,将变量设置为 `1` 的合乎情理的。
|
||||
|
||||
### 判断活动时间
|
||||
|
||||
接下来,就像使用 `Player` 和 `Enemy` 一样,你需要一个 `update` 函数,以便投掷对象在瞄准敌人抛向空中时,它会自己移动。
|
||||
|
||||
测定一个投掷对象活动时间的最简单方法是侦测它何时离开屏幕。你需要监视的屏幕边缘取决于你投掷对象的物理特性。
|
||||
|
||||
* 如果你的玩家正在投掷的东西是沿着水平轴快速移动的,像一只弩箭或箭或一股非常快的魔法力量,而你想监视你游戏屏幕的水平轴极限。这可以通过 `worldx` 定义。
|
||||
* 如果你的玩家正在投掷的东西是沿着垂直方向或同时沿着水平方向和垂直方向移动的,那么吗必需监视你游戏屏幕的垂直轴极限。这可以通过 `worldy` 定义。
|
||||
|
||||
|
||||
|
||||
这个示例假设你的投掷对象向前移动一点并最终落地地面上。不过,投掷对象不会从地面上反弹起来,而是继续掉落出屏幕。你可以尝试不同的设置来看看什么最适合你的游戏:
|
||||
|
||||
|
||||
```
|
||||
def update(self,worldy):
|
||||
'''
|
||||
投掷物理学
|
||||
'''
|
||||
if self.rect.y < worldy: #垂直轴
|
||||
self.rect.x += 15 #它向前移动的速度有多快
|
||||
self.rect.y += 5 #它掉落的速度有多快
|
||||
else:
|
||||
self.kill() #移除投掷对象
|
||||
self.firing = 0 #解除火力发射
|
||||
```
|
||||
|
||||
为使你的投掷对象移动地更快,增加 `self.rect` 的动量值。
|
||||
|
||||
如果投掷对象不在屏幕上,那么对象将被销毁,以及释放其所占用的寄存器。另外,`self.firing` 将被设置回 `0` 以允许你的玩家来进行另一次射击。
|
||||
|
||||
### 设置你的投掷对象
|
||||
|
||||
就像使用你的玩家和敌人一样,你必需在你的 setup 部分中创建一个精灵组来保持投资对象。
|
||||
|
||||
此外,你必需创建一个非活动的投掷对象来供开始的游戏使用。如果在游戏开始时却没有一个投掷对象,那么玩家在第一次尝试投掷一柄武器时,投掷将失败。
|
||||
|
||||
这个示例假设你的玩家使用一个火球作为开始的武器,因此,每一个投掷实例都是由 `fire` 变量指派的。在后面的关卡中,当玩家获取新的技能时,你可以使用相同的 `Throwable` 类来引入一个新的变量以使用一张不同的图像。
|
||||
|
||||
在这代码块中,前两行已经在你的代码中,因此不要重新键入它们:
|
||||
|
||||
|
||||
```
|
||||
player_list = pygame.sprite.Group() #上下文
|
||||
player_list.add(player) #上下文
|
||||
fire = Throwable(player.rect.x,player.rect.y,'fire.png',0)
|
||||
firepower = pygame.sprite.Group()
|
||||
```
|
||||
|
||||
注意,每一个投掷对象的起始位置都是和玩家所在的位置相同。这使得它看起来像是投掷对象来自玩家。在第一个火球生成时,使用 `0` 来显示 `self.firing` 是可用的。
|
||||
|
||||
### 在主循环中获取 throwing
|
||||
|
||||
没有在主循环中出现的代码将不会在游戏中使用,因此你需要在你的主循环中添加一些东西,以便能在你的游戏世界中获取投掷东西。
|
||||
|
||||
首先,添加玩家控制。当前,你没有火力触发器。在键盘上的按键是有两种状态的:按键能够在上面,也能够在下面。为了移动,你要使用这两种状态:按下按键来启动玩家移动,释放按键 ( 按键是在上面的 ) 来停止玩家移动。开火仅需要一个信号。你使用哪个按键事件 ( 按键按下或按键释放 ) 来触发你的投掷对象取决于你的品味。
|
||||
|
||||
在这个代码语句块中,前两行是用于上下文的:
|
||||
|
||||
|
||||
```
|
||||
if event.key == pygame.K_UP or event.key == ord('w'):
|
||||
player.jump(platform_list)
|
||||
if event.key == pygame.K_SPACE:
|
||||
if not fire.firing:
|
||||
fire = Throwable(player.rect.x,player.rect.y,'fire.png',1)
|
||||
firepower.add(fire)
|
||||
```
|
||||
|
||||
与你在你 setup 部分创建的火球不同,你使用一个 `1` 来设置 `self.firing` 为不可用。
|
||||
|
||||
最后,你必需更新和绘制你的投掷对象。这个顺序很重要,因此把这段代码放置到你现有的 `enemy.move` 和 `player_list.draw` 的代码行之间:
|
||||
|
||||
|
||||
```
|
||||
enemy.move() # 上下文
|
||||
|
||||
if fire.firing:
|
||||
fire.update(worldy)
|
||||
firepower.draw(world)
|
||||
player_list.draw(screen) # 上下文
|
||||
enemy_list.draw(screen) # 上下文
|
||||
```
|
||||
|
||||
注意,这些更新仅在 `self.firing` 变量被设置为 1 时执行。如果它被设置为 0 ,那么 `fire.firing` 就不是真的,接下来就跳过更新。如果你尝试做上述这些更新,不管怎样,你的游戏都会崩溃,因为在游戏中将不会更新或绘制一个 `火球` 对象。
|
||||
|
||||
启动你的游戏,尝试挑战你的武器。
|
||||
|
||||
### 检测碰撞
|
||||
|
||||
如果你玩使用新投掷技巧的游戏,你可能会注意到,你可以投掷对象,但是它却不会对你的敌人有任何影响。
|
||||
|
||||
原因是你的敌人没有被查到碰撞事故。一名敌人可能会被你的投掷对象所击中,但是敌人却从来不知道被击中了。
|
||||
|
||||
你已经在你的 `Player` 类中完成了碰撞检测,这非常类似。在你的 `Enemy` 类中,添加一个新的 `update` 函数:
|
||||
|
||||
|
||||
```
|
||||
def update(self,firepower, enemy_list):
|
||||
"""
|
||||
检测火力碰撞
|
||||
"""
|
||||
fire_hit_list = pygame.sprite.spritecollide(self,firepower,False)
|
||||
for fire in fire_hit_list:
|
||||
enemy_list.remove(self)
|
||||
```
|
||||
|
||||
代码很简单。每个敌人对象都检查并看看它自己是否被 `firepower` 精灵组的成员所击中。如果它被击中,那么敌人就会从敌人组中移除和消失。
|
||||
|
||||
为集成这些功能到你的游戏之中,在主循环中调用位于新触发语句块中的函数:
|
||||
|
||||
|
||||
```
|
||||
if fire.firing: # 上下文
|
||||
fire.update(worldy) # 上下文
|
||||
firepower.draw(screen) # 上下文
|
||||
enemy_list.update(firepower,enemy_list) # 更新敌人
|
||||
```
|
||||
|
||||
你现在可以尝试一下你的游戏了,大多数的事情都如预期般的那样工作。不过,这里仍然有一个问题,那就是投掷的方向。
|
||||
|
||||
### 更改投掷技巧的方向
|
||||
|
||||
当前,你英雄的火球只会向右移动。这是因为 `Throwable` 类的 `update` 函数将像素添加到火球的位置,在 Pygame 中,在 X 轴上一个较大的数字意味着向屏幕的右侧移动。当你的英雄转向另一个方向时,你可能希望它投掷的火球也抛向左侧。
|
||||
|
||||
到目前为止,你已经知道如何实现这一点,至少在技术上是这样的。然而,最简单的解决方案却是使用一个变量,在一定程度上对你来说可能是一种新的方法。一般来说,你可以 "设置一个标记" ( 有时也被称为 "转刀" ) 来标明你的英雄所面向的方向。在你做完后,你就可以检查这个变量来得知火球是向左移动还是向右移动。
|
||||
|
||||
首先,在你的 `Player` 类中创建一个新的变量来代表你的游戏所面向的方向。因为我的游戏天然地面向右侧,由此我把面向右侧作为默认值:
|
||||
|
||||
|
||||
```
|
||||
self.score = 0
|
||||
self.facing_right = True # 添加这行
|
||||
self.is_jumping = True
|
||||
```
|
||||
|
||||
当这个变量是 `True` 时,你的英雄精灵是面向右侧的。当玩家每次更改英雄的方向时,变量也必需重新设置,因此,在你的主循环中相关的 `keyup` 事件中这样做:
|
||||
|
||||
|
||||
```
|
||||
if event.type == pygame.KEYUP:
|
||||
if event.key == pygame.K_LEFT or event.key == ord('a'):
|
||||
player.control(steps, 0)
|
||||
player.facing_right = False # 添加这行
|
||||
if event.key == pygame.K_RIGHT or event.key == ord('d'):
|
||||
player.control(-steps, 0)
|
||||
player.facing_right = True # 添加这行
|
||||
```
|
||||
|
||||
最后,更改你的 `Throwable` 类的 `update` 函数,以检测英雄是否面向右侧,并恰当地添加或减去来自火球位置的像素:
|
||||
|
||||
|
||||
```
|
||||
if self.rect.y < worldy:
|
||||
if player.facing_right:
|
||||
self.rect.x += 15
|
||||
else:
|
||||
self.rect.x -= 15
|
||||
self.rect.y += 5
|
||||
```
|
||||
|
||||
再次尝试你的游戏,清除掉你游戏世界中的一些坏人。
|
||||
|
||||
![Python 平台类使用投掷能力][15]
|
||||
|
||||
(Seth Kenlon, [CC BY-SA 4.0][16])
|
||||
|
||||
作为一项额外的挑战,当彻底打败敌人时,尝试增加你玩家的得分。
|
||||
|
||||
### 完整的代码
|
||||
|
||||
|
||||
```
|
||||
#!/usr/bin/env python3
|
||||
# 作者: 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/>][17].
|
||||
|
||||
import pygame
|
||||
import pygame.freetype
|
||||
import sys
|
||||
import os
|
||||
|
||||
'''
|
||||
变量
|
||||
'''
|
||||
|
||||
worldx = 960
|
||||
worldy = 720
|
||||
fps = 40
|
||||
ani = 4
|
||||
world = pygame.display.set_mode([worldx, worldy])
|
||||
forwardx = 600
|
||||
backwardx = 120
|
||||
|
||||
BLUE = (80, 80, 155)
|
||||
BLACK = (23, 23, 23)
|
||||
WHITE = (254, 254, 254)
|
||||
ALPHA = (0, 255, 0)
|
||||
|
||||
tx = 64
|
||||
ty = 64
|
||||
|
||||
font_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fonts", "amazdoom.ttf")
|
||||
font_size = tx
|
||||
pygame.freetype.init()
|
||||
myfont = pygame.freetype.Font(font_path, font_size)
|
||||
|
||||
'''
|
||||
对象
|
||||
'''
|
||||
|
||||
def stats(score, health):
|
||||
myfont.render_to(world, (4, 4), "Score:"+str(score), BLUE, None, size=64)
|
||||
myfont.render_to(world, (4, 72), "Health:"+str(health), BLUE, None, size=64)
|
||||
|
||||
class Throwable(pygame.sprite.Sprite):
|
||||
"""
|
||||
生成一个投掷的对象
|
||||
"""
|
||||
def __init__(self, x, y, img, throw):
|
||||
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.firing = throw
|
||||
|
||||
def update(self, worldy):
|
||||
'''
|
||||
投掷物理学
|
||||
'''
|
||||
if self.rect.y < worldy:
|
||||
if player.facing_right:
|
||||
self.rect.x += 15
|
||||
else:
|
||||
self.rect.x -= 15
|
||||
self.rect.y += 5
|
||||
else:
|
||||
self.kill()
|
||||
self.firing = 0
|
||||
|
||||
# x 位置, y 位置, img 宽度, img 高度, img 文件
|
||||
class Platform(pygame.sprite.Sprite):
|
||||
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
|
||||
|
||||
class Player(pygame.sprite.Sprite):
|
||||
"""
|
||||
生成一名玩家
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pygame.sprite.Sprite.__init__(self)
|
||||
self.movex = 0
|
||||
self.movey = 0
|
||||
self.frame = 0
|
||||
self.health = 10
|
||||
self.damage = 0
|
||||
self.score = 0
|
||||
self.facing_right = True
|
||||
self.is_jumping = True
|
||||
self.is_falling = True
|
||||
self.images = []
|
||||
for i in range(1, 5):
|
||||
img = pygame.image.load(os.path.join('images', 'walk' + 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 gravity(self):
|
||||
if self.is_jumping:
|
||||
self.movey += 3.2
|
||||
|
||||
def control(self, x, y):
|
||||
"""
|
||||
控制玩家移动
|
||||
"""
|
||||
self.movex += x
|
||||
|
||||
def jump(self):
|
||||
if self.is_jumping is False:
|
||||
self.is_falling = False
|
||||
self.is_jumping = True
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
更新精灵位置
|
||||
"""
|
||||
|
||||
# 向左移动
|
||||
if self.movex < 0:
|
||||
self.is_jumping = True
|
||||
self.frame += 1
|
||||
if self.frame > 3 * ani:
|
||||
self.frame = 0
|
||||
self.image = pygame.transform.flip(self.images[self.frame // ani], True, False)
|
||||
|
||||
# 向右移动
|
||||
if self.movex > 0:
|
||||
self.is_jumping = True
|
||||
self.frame += 1
|
||||
if self.frame > 3 * ani:
|
||||
self.frame = 0
|
||||
self.image = self.images[self.frame // ani]
|
||||
|
||||
# 碰撞
|
||||
enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False)
|
||||
if self.damage == 0:
|
||||
for enemy in enemy_hit_list:
|
||||
if not self.rect.contains(enemy):
|
||||
self.damage = self.rect.colliderect(enemy)
|
||||
if self.damage == 1:
|
||||
idx = self.rect.collidelist(enemy_hit_list)
|
||||
if idx == -1:
|
||||
self.damage = 0 # 设置伤害回 0
|
||||
self.health -= 1 # 减去 1 单位健康度
|
||||
|
||||
ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False)
|
||||
for g in ground_hit_list:
|
||||
self.movey = 0
|
||||
self.rect.bottom = g.rect.top
|
||||
self.is_jumping = False # 停止跳跃
|
||||
|
||||
# 掉落世界
|
||||
if self.rect.y > worldy:
|
||||
self.health -=1
|
||||
print(self.health)
|
||||
self.rect.x = tx
|
||||
self.rect.y = ty
|
||||
|
||||
plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False)
|
||||
for p in plat_hit_list:
|
||||
self.is_jumping = False # 停止跳跃
|
||||
self.movey = 0
|
||||
if self.rect.bottom <= p.rect.bottom:
|
||||
self.rect.bottom = p.rect.top
|
||||
else:
|
||||
self.movey += 3.2
|
||||
|
||||
if self.is_jumping and self.is_falling is False:
|
||||
self.is_falling = True
|
||||
self.movey -= 33 # 跳跃多高
|
||||
|
||||
loot_hit_list = pygame.sprite.spritecollide(self, loot_list, False)
|
||||
for loot in loot_hit_list:
|
||||
loot_list.remove(loot)
|
||||
self.score += 1
|
||||
print(self.score)
|
||||
|
||||
plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False)
|
||||
|
||||
self.rect.x += self.movex
|
||||
self.rect.y += self.movey
|
||||
|
||||
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
|
||||
self.counter = 0
|
||||
|
||||
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
|
||||
|
||||
def update(self, firepower, enemy_list):
|
||||
"""
|
||||
检测火力碰撞
|
||||
"""
|
||||
fire_hit_list = pygame.sprite.spritecollide(self, firepower, False)
|
||||
for fire in fire_hit_list:
|
||||
enemy_list.remove(self)
|
||||
|
||||
class Level:
|
||||
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
|
||||
|
||||
def bad(lvl, eloc):
|
||||
if lvl == 1:
|
||||
enemy = Enemy(eloc[0], eloc[1], 'enemy.png')
|
||||
enemy_list = pygame.sprite.Group()
|
||||
enemy_list.add(enemy)
|
||||
if lvl == 2:
|
||||
print("Level " + str(lvl))
|
||||
|
||||
return enemy_list
|
||||
|
||||
# x 位置, y 位置, img 宽度, img 高度, img 文件
|
||||
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((550, 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
|
||||
|
||||
def loot(lvl):
|
||||
if lvl == 1:
|
||||
loot_list = pygame.sprite.Group()
|
||||
loot = Platform(tx*5, ty*5, tx, ty, 'loot_1.png')
|
||||
loot_list.add(loot)
|
||||
|
||||
if lvl == 2:
|
||||
print(lvl)
|
||||
|
||||
return loot_list
|
||||
|
||||
'''
|
||||
Setup 部分
|
||||
'''
|
||||
|
||||
backdrop = pygame.image.load(os.path.join('images', 'stage.png'))
|
||||
clock = pygame.time.Clock()
|
||||
pygame.init()
|
||||
backdropbox = world.get_rect()
|
||||
main = True
|
||||
|
||||
player = Player() # 生成玩家
|
||||
player.rect.x = 0 # 转到 x
|
||||
player.rect.y = 30 # 转到 y
|
||||
player_list = pygame.sprite.Group()
|
||||
player_list.add(player)
|
||||
steps = 10
|
||||
fire = Throwable(player.rect.x, player.rect.y, 'fire.png', 0)
|
||||
firepower = pygame.sprite.Group()
|
||||
|
||||
eloc = []
|
||||
eloc = [300, worldy-ty-80]
|
||||
enemy_list = Level.bad(1, eloc)
|
||||
gloc = []
|
||||
|
||||
i = 0
|
||||
while i <= (worldx / tx) + tx:
|
||||
gloc.append(i * tx)
|
||||
i = i + 1
|
||||
|
||||
ground_list = Level.ground(1, gloc, tx, ty)
|
||||
plat_list = Level.platform(1, tx, ty)
|
||||
enemy_list = Level.bad( 1, eloc )
|
||||
loot_list = Level.loot(1)
|
||||
|
||||
'''
|
||||
主循环
|
||||
'''
|
||||
|
||||
while main:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
pygame.quit()
|
||||
try:
|
||||
sys.exit()
|
||||
finally:
|
||||
main = False
|
||||
|
||||
if event.type == pygame.KEYDOWN:
|
||||
if event.key == ord('q'):
|
||||
pygame.quit()
|
||||
try:
|
||||
sys.exit()
|
||||
finally:
|
||||
main = False
|
||||
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'):
|
||||
player.jump()
|
||||
|
||||
if event.type == pygame.KEYUP:
|
||||
if event.key == pygame.K_LEFT or event.key == ord('a'):
|
||||
player.control(steps, 0)
|
||||
player.facing_right = False
|
||||
if event.key == pygame.K_RIGHT or event.key == ord('d'):
|
||||
player.control(-steps, 0)
|
||||
player.facing_right = True
|
||||
if event.key == pygame.K_SPACE:
|
||||
if not fire.firing:
|
||||
fire = Throwable(player.rect.x, player.rect.y, 'fire.png', 1)
|
||||
firepower.add(fire)
|
||||
|
||||
# 向向滚动世界
|
||||
if player.rect.x >= forwardx:
|
||||
scroll = player.rect.x - forwardx
|
||||
player.rect.x = forwardx
|
||||
for p in plat_list:
|
||||
p.rect.x -= scroll
|
||||
for e in enemy_list:
|
||||
e.rect.x -= scroll
|
||||
for l in loot_list:
|
||||
l.rect.x -= scroll
|
||||
|
||||
# 向后滚动世界
|
||||
if player.rect.x <= backwardx:
|
||||
scroll = backwardx - player.rect.x
|
||||
player.rect.x = backwardx
|
||||
for p in plat_list:
|
||||
p.rect.x += scroll
|
||||
for e in enemy_list:
|
||||
e.rect.x += scroll
|
||||
for l in loot_list:
|
||||
l.rect.x += scroll
|
||||
|
||||
world.blit(backdrop, backdropbox)
|
||||
player.update()
|
||||
player.gravity()
|
||||
player_list.draw(world)
|
||||
if fire.firing:
|
||||
fire.update(worldy)
|
||||
firepower.draw(world)
|
||||
enemy_list.draw(world)
|
||||
enemy_list.update(firepower, enemy_list)
|
||||
loot_list.draw(world)
|
||||
ground_list.draw(world)
|
||||
plat_list.draw(world)
|
||||
for e in enemy_list:
|
||||
e.move()
|
||||
stats(player.score, player.health)
|
||||
pygame.display.flip()
|
||||
clock.tick(fps)
|
||||
```
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/20/9/add-throwing-python-game
|
||||
|
||||
作者:[Seth Kenlon][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[robsean](https://github.com/robsean)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/seth
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/game_pawn_grid_linux.png?itok=4gERzRkg (Gaming on a grid with penguin pawns)
|
||||
[2]: https://www.python.org/
|
||||
[3]: https://www.pygame.org/news
|
||||
[4]: https://opensource.com/article/17/10/python-101
|
||||
[5]: https://opensource.com/article/17/12/game-framework-python
|
||||
[6]: https://opensource.com/article/17/12/game-python-add-a-player
|
||||
[7]: https://opensource.com/article/17/12/game-python-moving-player
|
||||
[8]: https://opensource.com/article/18/5/pygame-enemy
|
||||
[9]: https://opensource.com/article/18/7/put-platforms-python-game
|
||||
[10]: https://opensource.com/article/19/11/simulate-gravity-python
|
||||
[11]: https://opensource.com/article/19/12/jumping-python-platformer-game
|
||||
[12]: https://opensource.com/article/19/12/python-platformer-game-run
|
||||
[13]: https://opensource.com/article/19/12/loot-python-platformer-game
|
||||
[14]: https://opensource.com/article/20/1/add-scorekeeping-your-python-game
|
||||
[15]: https://opensource.com/sites/default/files/uploads/pygame-throw.jpg (Python platformer with throwing capability)
|
||||
[16]: https://creativecommons.org/licenses/by-sa/4.0/
|
||||
[17]: http://www.gnu.org/licenses/\>
|
Loading…
Reference in New Issue
Block a user