Pygame新手避坑指南

英文文档日文翻译中文转述
这是一篇Pygame的新手指南。
也是我踩过,别人不必再踩的那些坑的记录
或者叫我是如何忘记忧愁,爱上blit的历史
Image
Pygame是SDL库的Python封装,由Pete Shinners书写。也就是说,使用Python写的Pygame跨平台游戏,不需修改就可以在其他支持SDL的系统里面(Windows、Unix、Mac、beOS或其他)运行。

Pygame学习起来比较简单。但是图形编程对初学者而言应该是很麻烦的事。我们能用Pygame书写东西,是这些年里大量先行者与pySQL奋斗,并把其中有用的知识传播给别人的结果。我把这些建议按照重要程度排列,但这些建议能对你有多大的作用,则取决于项目实际细节,与你的背景知识。

  1. The most important thing is to feel confident using python.

    Learning something as potentially complicated as graphics programming will be a real chore if you’re also unfamiliar with the language you’re using. Write a few sizable non-graphical programs in python – parse some text files, write a guessing game or a journal-entry program or something. Get comfortable with string and list manipulation – know how to split, slice and combine strings and lists. Know how import works – try writing a program that is spread across several source files. Write your own functions, and practice manipulating numbers and characters; know how to convert between the two. Get to the point where the syntax for using lists and dictionaries is second-nature – you don’t want to have to run to the documentation every time you need to slice a list or sort a set of keys. Resist the temptation to run to a mailing list, comp.lang.python, or IRC when you run into trouble. Instead, fire up the interpreter and play with the problem for a few hours. Print out the Python 2.0 Quick Reference and keep it by your computer.

    import の仕組みを学ぼう - 複数のソースにまたがるプログラムを 書こう。自分独自の関数を書いて、数値と文字を操作するやりかたを 練習しよう。どうやってこの 2つを変換するか。Python の構文と、 リストや辞書の使いかたが第2の母国語になるくらいまで上達しよう - リストを切り取ったりキーを sort したりするのに、わざわざドキュメントを あさるようなことはしたくないでしょ? トラブルがあっても、 メイリングリストや、comp.lang.python や IRC に駆け込むのは 我慢しよう。かわりに、インタプリタを立ち上げてその問題を 数時間いじくりまわしてみるんだ。 Python Quick Reference を印刷して、コンピュータのとなりにいつもおいておこう。

1、踏实掌握Python

最重要的就是,能够灵活地运用Python。图形编程本来就够麻烦了,如果你还对编程语言一知半解,拿着工作就真的让人心累了。首先从不牵扯图像显示的命令行程序学起吧,比如说读取文本文件的程序、猜数字游戏、账簿程序等等。首先把文字操作、列表切片、分割、连接等等用熟。学习import用法、尝试写一个由多个源文件组成的程序。写一些自己的函数,试着操作一下数值与文字,了解一下它们两个如何转换。掌握Python的格式、列表与字典的用法

This may sound incredibly dull, but the confidence you’ll gain through your familiarity with python will work wonders when it comes time to write your game. The time you spend making python code second-nature will be nothing compared to the time you’ll save when you’re writing real code.

これらはとてつもなく退屈な仕事に思えるかもしれない。 けれど、Python に慣れることで得た確信は、いざゲームを 書く段になったときに信じられないくらいの奇跡をうみ出す。 実際のコードを書くときに節約できる時間に比べたら、 それまで Python を鍛えてきた時間なんて屁みたいなもんだ。

2、知道Pygame的那些部分对你来说是必要的

  1. Recognize which parts of pygame you really need.

    Looking at the jumble of classes at the top of the pygame Documentation index may be confusing. The important thing is to realize that you can do a great deal with only a tiny subset of functions. Many classes you’ll probably never use – in a year, I haven’t touched the Channel, Joystick, cursors, Userrect, surfarray or version functions.

  2. 本当に必要なのは、pygame のどの部分かを知ろう。

Pygame ドキュメント一覧で、ごちゃごちゃしたクラスを 見ると混乱するかもしれない。大切なのは、かなりのことをやるのに 必要な関数はこれらのうちのごく一部でいいということなんだ。 多くのクラスはたぶん使いもしないだろう -- ぼくだって最初の一年間は Channel, Joystick, cursors, Userrect, surfarray や version といった 関数には手をつけさえしなかった。

3、理解Suface

3.Know what a surface is.
The most important part of pygame is the surface. Just think of a surface as a blank piece of paper. You can do a lot of things with a surface – you can draw lines on it, fill parts of it with color, copy images to and from it, and set or read individual pixel colors on it. A surface can be any size (within reason) and you can have as many of them as you like (again, within reason). One surface is special – the one you create with pygame.display.set_mode(). This ‘display surface’ represents the screen; whatever you do to it will appear on the user’s screen. You can only have one of these – that’s an SDL limitation, not a pygame one.

So how do you create surfaces? As mentioned above, you create the special ‘display surface’ with pygame.display.set_mode(). You can create a surface that contains an image by using image.load(), or you can make a surface that contains text with font.render(). You can even create a surface that contains nothing at all with Surface().

Most of the surface functions are not critical. Just learn blit(), fill(), set_at() and get_at(), and you’ll be fine.

  1. surface (サーフェイス) とは何かを理解しよう。

Pygame のもっとも重要な部分は “surface (サーフェイス)” だ。 surface というのはちょうど白紙の紙切れだと思えばいい。 surface に対してはかなりいろんなことができる -- 線を引いたり、 その一部分に色をつけたり、イメージをコピーしたりされたり、 そして各ピクセルごとの色を設定・取得できたりする。 surface は (それなりの大きさまでなら) どんなサイズにもできるし、 (それなりの数までなら) いくらでも好きなだけ多くの surface を持てる。 ある surface は特別だ -- これは pygame.display.set_mode() で 作成する surface で、“display surface (ディスプレイ サーフェイス)” と 呼ばれている。これは画面全体を表す surface で、これに対して したことはみんなユーザの画面に反映される。この surface は ひとつしか持てない。これは SDL の制限で、pygame のじゃない。

じゃあどうやって surface を作ったらいいんだろうか? 上でふれたように、特別な “display surface” は pygame.display.set_mode() でつくれる。 image.load() を使えば画像をもった surface がつくれるし、 font.render()を使えばテキスト文字列をもった surface もつくれる。 Surface() を使えば、まったく何もない surface さえつくれるんだよ。

ほとんどの surface 関数はそんなに重要じゃない。 blit(), fill(), set_at() そして get_at() を覚えれば十分だ。

4、使用surface.convert().

When I first read the documentation for surface.convert(), I didn’t think it was something I had to worry about. ‘I only use PNGs, therefore everything I do will be in the same format. So I don’t need convert()‘;. It turns out I was very, very wrong.

The ‘format’ that convert() refers to isn’t the file format (ie PNG, JPEG, GIF), it’s what’s called the ‘pixel format’. This refers to the particular way that a surface records individual colors in a specific pixel. If the surface format isn’t the same as the display format, SDL will have to convert it on-the-fly for every blit – a fairly time-consuming process. Don’t worry too much about the explanation; just note that convert() is necessary if you want to get any kind of speed out of your blits.

How do you use convert? Just call it after creating a surface with the image.load() function. Instead of just doing:

surface = pygame.image.load('foo.png')

Do:

surface = pygame.image.load('foo.png').convert()

It’s that easy. You just need to call it once per surface, when you load an image off the disk. You’ll be pleased with the results; I see about a 6x increase in blitting speed by calling convert().

The only times you don’t want to use convert() is when you really need to have absolute control over an image’s internal format – say you were writing an image conversion program or something, and you needed to ensure that the output file had the same pixel format as the input file. If you’re writing a game, you need speed. Use convert().

  1. surface.convert() を使おう。

最初に surface.convert() の説明を読んだとき、 ぼくはそれがそんなに気にかけるほどのものだとは思わなかった。 「自分は png しか使わないから、ぼくが扱うものはすべて 同一の形式になるはずだ、だから convert() なんか必要ない」 -- でもこれは激しく間違っていることがわかった。

convert() が言っている “format (形式)”というのは ファイル形式 (png とか jpeg とか gif とか) のことじゃなかったんだ -- ここでの形式というのは “pixel format (ピクセル形式)” のことだった。 これは surface がピクセル中の個々の色をどうやって記録しているかという 方式をさす。もしその surface の形式が display の形式と違っていると、 SDL はそれを毎回 blit するごとに「現場で」変換する -- これはとても時間がかかるものだ。でもこのことでそんなに心配しなくてもいい。 ただ blit するときにある程度の速度が必要なら、convert() は 必要なんだということだけ知っていればいいんだ。

じゃあどうやって convert を使うんだろう? image.load() 関数で surface を作ったすぐあとにこれを呼べばいい。 つまり今までこうしていたのを:

surface = pygame.image.load('foo.png')

かわりに、こうすればいい:

surface = pygame.image.load('foo.png').convert()

簡単でしょ? これを呼ぶ必要があるのは各 surface で一回だけ、 画像をディスクから読み込むときだけだ。そしてその結果にはうれしくなるよ。 ぼくのところでは convert() を呼ぶことで、blit の スピードは約 6倍になった。

convert() を使うべきでない唯一の場合というのは、 その画像の内部形式をほんとうにいじりたいときだけだ -- 画像変換プログラムとか、そのたぐいのものを作ろうとしているなら、 出力ファイルを入力ファイルと同じピクセル形式にしたいと思うだろう。 でもゲームを書いているときなら、スピードが必要だ。 convert() を使おう。

  1. Dirty rect animation.

The most common cause of inadequate frame rates in pygame programs results from misunderstanding the pygame.display.update() function. With pygame, merely drawing something to the display surface doesn’t cause it to appear on the screen – you need to call pygame.display.update(). There are three ways of calling this function:

    pygame.display.update() – This updates the whole window (or the whole screen for fullscreen displays).
    pygame.display.flip() – This does the same thing, and will also do the right thing if you’re using double-buffered hardware acceleration, which you’re not, so on to...
    pygame.display.update(a rectangle or some list of rectangles) – This updates just the rectangular areas of the screen you specify.

Most people new to graphics programming use the first option – they update the whole screen every frame. The problem is that this is unacceptably slow for most people. Calling update() takes 35 milliseconds on my machine, which doesn’t sound like much, until you realize that 1000 / 35 = 28 frames per second maximum. And that’s with no game logic, no blits, no input, no AI, nothing. I’m just sitting there updating the screen, and 28 fps is my maximum framerate. Ugh.

The solution is called ‘dirty rect animation’. Instead of updating the whole screen every frame, only the parts that changed since the last frame are updated. I do this by keeping track of those rectangles in a list, then calling update(the_dirty_rectangles) at the end of the frame. In detail for a moving sprite, I:

    Blit a piece of the background over the sprite’s current location, erasing it.
    Append the sprite’s current location rectangle to a list called dirty_rects.
    Move the sprite.
    Draw the sprite at it’s new location.
    Append the sprite’s new location to my dirty_rects list.
    Call display.update(dirty_rects)

The difference in speed is astonishing. Consider that SolarWolf has dozens of constantly moving sprites updating smoothly, and still has enough time left over to display a parallax starfield in the background, and update that too.

There are two cases where this technique just won’t work. The first is where the whole window or screen really is being updated every frame – think of a smooth-scrolling engine like an overhead real-time strategy game or a side-scroller. So what do you do in this case? Well, the short answer is – don’t write this kind of game in pygame. The long answer is to scroll in steps of several pixels at a time; don’t try to make scrolling perfectly smooth. Your player will appreciate a game that scrolls quickly, and won’t notice the background jumping along too much.

A final note – not every game requires high framerates. A strategic wargame could easily get by on just a few updates per second – in this case, the added complexity of dirty rect animation may not be necessary.

  1. 汚れた rect をアニメーションさせる。

Pygame プログラムで十分なフレームレートがとれない 原因の最たるものは、pygame.display.update() 関数が 誤解されて使われていることだ。Pygame では、display surface に ただ何かを描画するだけではそれは画面に現れない -- pygame.display.update() を呼ぶ必要がある。 そしてこの関数を呼ぶやり方は 3つある。

pygame.display.update() - これはウインドウ全体 (あるいはフルスクリーンモードなら画面全体) を更新する。
pygame.display.flip() - これも同じ。だがこちらはさらにダブルバッファつきの ハードウエアアクセラレーションを使っているときでも 正しく処理してくれる。 which you're not, so on to...
pygame.display.update(rect あるいは rectのリスト) - これは画面中の指定した矩形領域の中だけを更新する。 

グラフィックスのプログラミングをはじめてまだ間もない ほとんどの人々は最初の関数を使う -- フレームごとに 画面全体を更新するのだ。問題は、ほとんどの人にとって これは話にならないくらい遅いということだ。ぼくのマシンでは、 update() を呼ぶと 35ミリ秒を消費する。大したことないように 思えるかもしれないが、1秒間に 1000 / 35 = 28フレームが 最高速度 だと考えるとどうだろうか。しかもここにはゲームの ロジックは含まれていない。blit もなし、入力もなし、 AI もなしだ。ただじっと座って、画面が更新されてるのを見るだけで、 28 フレーム/秒がぼくのマシンでは最高のフレームレートなんだ。うあ。

これに対する解決策は “汚れた rect のアニメーション” と呼ばれる。 フレームごとに画面全体を更新するのではなく、前のフレームから変更された (訳注: 汚れた) 部分だけを更新しよう。ぼくはこれらの長方形 (rect) をリストに保持しておいて、 各フレームの最後で update(the_dirty_rectangles) を呼ぶことにしている。 スプライトを動かすやりかたを詳しく書くと、ぼくは:

スプライトの現在位置に背景の一部を blit し、そいつを消す。
スプライトの現在位置をあらわす rectangle を、dirty_rects と呼ばれるリストの末尾に追加する。
スプライトを動かす。
新しい位置にスプライトを描画する。
新しいスプライトの位置を、dirty_rects の末尾に追加する。
display.update(dirty_rects) を呼ぶ。 

こいつがうみだす速度のちがいには、ぶったまげる。 Solarwolf を思い出してよ、 これは何十というスプライトがスムーズに動いて update していて、 そのうえまだ背景に交差する星々を流していて、これまた update する時間が じゅうぶんにあるんだ。

このテクニックがうまくいかない場合が 2つある。ひとつは、 ウインドウあるいはスクリーンがフレームごとに、本当にすべて update されなければならない場合 -- 上から見下ろす形の地図を使う戦略ゲームなどの スムーススクロールするエンジンやサイドスクロールを考えてみてよ。 じゃあこういう場合はどうすればいいのか? えーと、簡単な答えはだね -- この手のゲームを pygame で書くべきではないということだ。長めの答えとしては、 いちどに数ピクセルをスクロールさせればいい。 完璧なスムーススクロールを実現させようとしないこと。 プレイヤーはスクロールが速いゲームを評価するんで、 背景が少々ぎこちなくても気にしないものだ。

最後の注意。すべてのゲームが高いフレームレートを必要とするわけではない。 戦略的ウォーゲームなら毎秒数フレームの update でじゅうぶんやっていけるだろう -- この場合、汚れた rect アニメーションなどの複雑な技は必要ないかもしれない。

5、 局部矩形刷新

  1. There is NO rule six.

6、“没有规则”的规则

  1. Hardware surfaces are more trouble than they’re worth.

    If you’ve been looking at the various flags you can use with pygame.display.set_mode(), you may have thought like this: Hey, HWSURFACE! Well, I want that – who doesn’t like hardware acceleration. Ooo... DOUBLEBUF; well, that sounds fast, I guess I want that too!. It’s not your fault; we’ve been trained by years of 3-d gaming to believe that hardware acceleration is good, and software rendering is slow.

Unfortunately, hardware rendering comes with a long list of drawbacks:

    It only works on some platforms. Windows machines can usually get hardware surfaces if you ask for them. Most other platforms can’t. Linux, for example, may be able to provide a hardware surface if X4 is installed, if DGA2 is working properly, and if the moons are aligned correctly. If a hardware surface is unavailable, SDL will silently give you a software surface instead.
    It only works fullscreen.
    It complicates per-pixel access. If you have a hardware surface, you need to Lock the surface before writing or reading individual pixel values on it. If you don’t, Bad Things Happen. Then you need to quickly Unlock the surface again, before the OS gets all confused and starts to panic. Most of this process is automated for you in pygame, but it’s something else to take into account.
    You lose the mouse pointer. If you specify HWSURFACE (and actually get it), your pointer will usually just vanish (or worse, hang around in a half-there, half-not flickery state). You’ll need to create a sprite to act as a manual mouse pointer, and you’ll need to worry about pointer acceleration and sensitivity. What a pain.
    It might be slower anyway. Many drivers are not accelerated for the types of drawing that we do, and since everything has to be blitted across the video bus (unless you can cram your source surface into video memory as well), it might end up being slower than software access anyway.

Hardware rendering has it’s place. It works pretty reliably under Windows, so if you’re not interested in cross-platform performance, it may provide you with a substantial speed increase. However, it comes at a cost – increased headaches and complexity. It’s best to stick with good old reliable SWSURFACE until you’re sure you know what you’re doing.

  1. ハードウエア surface は利点よりもトラブルのもと。

pygame.display.set_mode() で使ういろんなフラグを見てみると、 こんなふうに思うかもしれない。「HWSURFACE だって? んー、よさそうだな… ハードウエアアクセラレーションを嫌う人なんかいないし… お、DOUBLEBUF? なんか速そうだな。これもいれちゃえ!」 これはきみの責任じゃない。何年にもわたって 3D ゲームを見てきているぼくらは、 ハードウエアアクセラレーションはよいもので、ソフトウエアによる描画は 遅いと信じこまされているからね。

でも残念なことに、ハードウエアによる描画には多くの欠点がつきまとう:

これは限られたプラットフォームでしか動かない。 Windows マシンなら、ふつう望めばハードウエア surface を使わせてくれるだろう。 ほかのほとんどのプラットフォームでは、これはできない。 たとえば Linux では、X4 がインストールされていて、DGA2 が 正しく動いており、天の星々の配置が正しければ ハードウエア surface を使えるかもしれない。ハードウエアによる 描画が使えないときは、SDL はだまってソフトウエア surface を返す。
これが使えるのはフルスクリーンのときのみ。
ハードウエア描画はピクセルごとのアクセスをややこしくする。 ハードウエア surface を使っているときは、個々のピクセルを 読み書きするまえに、その surface を Lock しなけりゃなんない。 もしこれをやらないと、良・く・な・い・こ・と・が・お・き・る・よ。 そのあと素早く Unlock する。OS が混乱して panic になる前に。 このプロセスはほとんど pygame では自動化されている。 でも他にも考慮に入れなきゃならないことがあるんだ。
マウスカーソルが使えなくなる。HWSURFACE を指定すると (かりにそれが使えたとして)、マウスカーソルはただ単に 消えてしまう (あるいは、もっとわるい場合は、表示されたか されないかというような状態でチラチラしてる)。 手動のマウスカーソルのようにふるまうスプライトを 用意しなきゃいけなくなるだろうし、カーソルの加速や感度について 気をもまなければいけなくなる。なんてこったい。
それでもとにかく遅くなる場合がある。多くのドライバは ここでやるようなタイプの描画でアクセラレーションしてくれないし、 いっさいがっさいが video バスを経由して blit されるため、 (描画するのに使う元絵の surface も一緒に video メモリにつめ込んじまわない限りは) 結局ソフトウエアアクセスよりさらに遅くなるという事態も発生する。 

ハードウエア描画に適した場所がある。これは Windows 上では 非常に安定して動くので、もし他のプラットフォームでのパフォーマンスを 気にしないのであれば、絶大なスピード増加がのぞめるだろう。 でも、これはコストのかかるものだ -- 複雑さも頭痛のタネも増加する。 自分がやっていることにきちんと確信がもてるまでは、 古き良き SWSURFACE を使いつづけるほうがいい。

7. Hardware surface麻烦比好处多

  1. Don’t get distracted by side issues.

Sometimes, new game programmers spend too much time worrying about issues that aren’t really critical to their game’s success. The desire to get secondary issues ‘right’ is understandable, but early in the process of creating a game, you cannot even know what the important questions are, let alone what answers you should choose. The result can be a lot of needless prevarication.

For example, consider the question of how to organize your graphics files. Should each frame have its own graphics file, or each sprite? Perhaps all the graphics should be zipped up into one archive? A great deal of time has been wasted on a lot of projects, asking these questions on mailing lists, debating the answers, profiling, etc, etc. This is a secondary issue; any time spent discussing it should have been spent coding the actual game.

The insight here is that it is far better to have a ‘pretty good’ solution that was actually implemented, than a perfect solution that you never got around to writing.

  1. 枝葉の問題に心をうばわれないようにしよう。

ときに新人ゲームプログラマーは、そのゲームにとって本当に 重要ではないことで長い間頭を悩ませてしまう。副次的な問題を 「きちんと」させたいという気持ちはわかる。でも、 ゲームを作るプロセスの早い段階では、どの解を選ぶべきかは もちろんのこと、何が重要な質問かさえもわかっていないものだ。 その結果は不必要な言い訳に終わることがある。

たとえば画像ファイルをどうやって管理するか考えてみよう。 フレームごとに画像ファイルを用意すべきだろうか、あるいはスプライトごとに? もしかしたら、すべての画像はひとつの zip アーカイブに収めるべきじゃないだろうか? メイリングリストに質問したり、答えについてディベートしたり、統計をとってみたり、 などなど、多くのプロジェクトで莫大な時間がむだになっている。 でもこれらは本質的な問題じゃないんだ -- そんなことを議論している時間があるなら、 その時間を実際のゲームをコーディングすることに使うべきなんだ。

ここでの教訓は、実際に動く「そこそこの」解決策のほうが、 決してたどりつかない完璧な解決策よりはるかにいいということ。

8、 不要过于操心细枝末节

9.Rects are your friends.

Pete Shinners’ wrapper may have cool alpha effects and fast blitting speeds, but I have to admit my favorite part of pygame is the lowly Rect class. A rect is simply a rectangle – defined only by the position of its top left corner, its width, and its height. Many pygame functions take rects as arguments, and they also take ‘rectstyles’, a sequence that has the same values as a rect. So if I need a rectangle that defines the area between 10, 20 and 40, 50, I can do any of the following:

rect = pygame.Rect(10, 20, 30, 30)
rect = pygame.Rect((10, 20, 30, 30))
rect = pygame.Rect((10, 20), (30, 30))
rect = (10, 20, 30, 30)
rect = ((10, 20, 30, 30))

If you use any of the first three versions, however, you get access to Rect’s utility functions. These include functions to move, shrink and inflate rects, find the union of two rects, and a variety of collision-detection functions.

For example, suppose I’d like to get a list of all the sprites that contain a point (x, y) – maybe the player clicked there, or maybe that’s the current location of a bullet. It’s simple if each sprite has a .rect member – I just do:

sprites_clicked = [sprite for sprite in all_my_sprites_list if sprite.rect.collidepoint(x, y)]

Rects have no other relation to surfaces or graphics functions, other than the fact that you can use them as arguments. You can also use them in places that have nothing to do with graphics, but still need to be defined as rectangles. Every project I discover a few new places to use rects where I never thought I’d need them.

  1. Rect はともだち。

Pete Shinners のラッパは、格好いいアルファ透過効果や 高速な blit を含んでいる。でもぼくが pygame で一番すきなのは、 低レベルな Rect クラスだと認めなければならないだろう。Rect は ただの長方形だ -- その左上端の位置と、幅と、高さのみによって定義される。 pygame 関数の多くは、rect 値を引数としてとる。それらはまた 「rectスタイル」 -- Rect と同じ値をもつシーケンスのことだ -- も 引数として受けつけている。だからもしぼくが 10,20 から 40,50 の範囲を あらわす rect を定義したければ、以下のどの方法でも同じように定義できる:

rect = pygame.Rect(10, 20, 30, 30)
rect = pygame.Rect((10, 20, 30, 30))
rect = pygame.Rect((10, 20), (30, 30))
rect = (10, 20, 30, 30)
rect = ((10, 20, 30, 30))

でも、もし最初の 3つの方法を使っているなら、rect のユーティリティ関数を 使うことができる。これらの関数は移動や収縮伸張、2つの rect の和をとったり、 いろんな衝突判定用の機能をふくんでいる。

たとえば点 (x, y) をふんでいるすべてのスプライトのリストが ほしいとしよう -- プレイヤーはそこをクリックしたり、もしかすると 現在の弾がその位置にあったりするのかもしれない。それぞれのスプライトが .rect メンバをもっていたとすると、これはじつに簡単だ -- ただこうするだけ:

sprites_clicked = sprite for sprite in all_my_sprites_list if sprite.rect.collidepoint(x, y)

Rect クラスは、それを surface やその他のグラフィック関数の 引数としてとれるということ以外、これらにはなんの関係もない。 グラフィックスとはなんの関係もないが、それでも長方形を定義したい ところで使うことだってできる。Rect を使うなんて思いもしなかったような プロジェクトでも、rect を使える場所がいくつかみつかっている。

9、Rect是你的朋友

10 .Don’t bother with pixel-perfect collision detection.

So you’ve got your sprites moving around, and you need to know whether or not they’re bumping into one another. It’s tempting to write something like the following:

    Check to see if the rects are in collision. If they aren’t, ignore them.
    For each pixel in the overlapping area, see if the corresponding pixels from both sprites are opaque. If so, there’s a collision.

There are other ways to do this, with ANDing sprite masks and so on, but any way you do it in pygame, it’s probably going to be too slow. For most games, it’s probably better just to do ‘sub-rect collision’ – create a rect for each sprite that’s a little smaller than the actual image, and use that for collisions instead. It will be much faster, and in most cases the player won’t notice the imprecision.

  1. ピクセルごとの完全な衝突判定に頭を悩ませるのはやめよう。

というわけで、めでたくスプライトを動かすことができたら、 次に必要なのはそいつらが互いにぶつかっているかどうかを知ることだ。 次のようなものを書くのは、心をそそられることだろう:

Rect が互いに衝突しているかどうかをチェックする。 もし衝突していなければ、無視する。
重なっている領域の各ピクセルをみて、そのピクセルが 両方のスプライトの不透明な部分と一致するかどうかを調べる。 もしそうなら、衝突だ。 

ほかにもスプライトどうしのマスクの AND をとったり、 いろいろな方法はあるが、いずれにせよこれを Pygame でやると たぶんあまりにも遅すぎて、どうしようもなくなるだろう。 ほとんどのゲームでは、たぶん「rectの部分的な衝突判定」をやるだけのほうがいい -- 実際の画像サイズよりやや小さめの rect をつくり、それを 衝突判定に使うようにする。このほうがずっと高速だし、 ほとんどの場合プレイヤーは多少いいかげんな判定でも気がつかない。

10、不要执着于完全像素碰撞

  1. Managing the event subsystem.

Pygame’s event system is kind of tricky. There are actually two different ways to find out what an input device (keyboard, mouse or joystick) is doing.

The first is by directly checking the state of the device. You do this by calling, say, pygame.mouse.get_pos() or pygame.key.get_pressed(). This will tell you the state of that device at the moment you call the function.

The second method uses the SDL event queue. This queue is a list of events – events are added to the list as they’re detected, and they’re deleted from the queue as they’re read off.

There are advantages and disadvantages to each system. State-checking (system 1) gives you precision – you know exactly when a given input was made – if mouse.get_pressed([0]) is 1, that means that the left mouse button is down right at this moment. The event queue merely reports that the mouse was down at some time in the past; if you check the queue fairly often, that can be ok, but if you’re delayed from checking it by other code, input latency can grow. Another advantage of the state-checking system is that it detects “chording” easily; that is, several states at the same time. If you want to know whether the t and f keys are down at the same time, just check:

if (key.get_pressed[K_t] and key.get_pressed[K_f]):
print "Yup!"

In the queue system, however, each keypress arrives in the queue as a completely separate event, so you’d need to remember that the t key was down, and hadn’t come up yet, while checking for the f key. A little more complicated.

The state system has one great weakness, however. It only reports what the state of the device is at the moment it’s called; if the user hits a mouse button then releases it just before a call to mouse.get_pressed(), the mouse button will return 0 – get_pressed() missed the mouse button press completely. The two events, MOUSEBUTTONDOWN and MOUSEBUTTONUP, will still be sitting in the event queue, however, waiting to be retrieved and processed.

The lesson is: choose the system that meets your requirements. If you don’t have much going on in your loop – say you’re just sitting in a while 1 loop, waiting for input, use get_pressed() or another state function; the latency will be lower. On the other hand, if every keypress is crucial, but latency isn’t as important – say your user is typing something in an editbox, use the event queue. Some keypresses may be slightly late, but at least you’ll get them all.

A note about event.poll() vs. wait() – poll() may seem better, since it doesn’t block your program from doing anything while it’s waiting for input – wait() suspends the program until an event is received. However, poll() will consume 100% of available CPU time while it runs, and it will fill the event queue with NOEVENTS. Use set_blocked() to select just those event types you’re interested in – your queue will be much more manageable.

  1. イベント・サブシステムを管理する。

Pygame のイベントシステムにはやや注意が必要だ。 入力デバイス (キーボード、マウスあるいはジョイスティック) が どうなっているかを知るには、実際には 2つのやり方がある。

最初の方法は、入力デバイスの状態を直接チェックすることだ。 これは、たとえば pygame.mouse.get_pos() や pygame.key.get_pressed() なんかを呼ぶことで実現できる。 これは その関数を呼んだ時点での 入力デバイスの状態を 教えてくれるだろう。

2番目の方法は SDL のイベントキューを使うことだ。 このキューはイベントのリスト -- イベントが 検出されると、リストに追加される -- になっている。 そしてこれらのイベントは取り出されるとリストから消える。

どちらのやり方にも長所と短所がある。 自分で状態をチェックする方法 (最初の方法) は正確さが売り物だ -- 入力が与えられた時刻を正確に知ることができる。もし mouse.get_pressed([0]) が 1 ならば、これはマウスの左ボタンが まさにこの瞬間に 押されていることを 意味している。イベントキューの場合は、ただマウスボタンが過去のある時点で 押されたということを教えてくれるだけだ。だからもしキューを 頻繁にチェックしているなら OK だけど、コードの他の部分を実行しているせいで チェックがおくれると、入力のずれは大きくなる。状態をたえずチェックする 方法のもうひとつの利点は、“和音の検出” が簡単になることだ。 つまり、いちどに複数の状態をチェックできる。もし T キーと F キーが 同時に押されているかどうかを知りたいなら、ただこうすればいい:

if (key.get_pressed[K_t] and key.get_pressed[K_f]):
print "いよっ!"

しかしキューを使っている場合、それぞれのキー入力は 完全に独立したイベントとしてキューにたまる。だからまず T キーが 押されたことを覚えておいても、F キーがくるまではわからない。 すこし複雑だ。

けれどもこの状態チェック法にはひとつの大きな欠点がある。 その関数が呼ばれた瞬間の入力デバイスの状態しか報告されないのだ。 だから、もし mouse.get_pressed() が呼ばれる直前に ユーザがマウスボタンを押して、離したとしたら、マウスボタンは 0 を返す -- つまり get_pressed() はボタンが押されたのを完全にとり逃してしまうのだ。 でもイベントキューならこれら 2つのイベント、MOUSEBUTTONDOWN と MOUSEBUTTONUP は確実にキューに残り、取り出されて処理されるのを待ってくれる。

ここでの教訓はこうだ: 自分の要求に合った方法を選ぼう。 もし自分のループでやることがそんなにない -- たとえば ただ 'while 1' ループの中なんかにすわって、入力を 待つだけなら get_pressed() やら何やらの状態チェック関数を 使えばいい。入力の遅れは大きくはならない。いっぽう、 すべてのキー入力がとても重要な意味をもつが、入力の遅れは重要では ないとき -- ユーザがテキストボックスに何かタイプしたりするときなんかは、 イベントキューを使おう。いくつかのキー入力はやや遅れるかもしれないが、 とにかく全部受けとることはできる。

ついでに event.poll() と wait() の違いについて。 poll() はプログラムをブロックしないので、こちらのほうが 入力を待っているあいだプログラムを中断させてしまう wait() よりもよさそうに見えるかもしれない。けれども poll() が 走っているあいだは使用可能な CPU 時間の 100% を消費してしまう。 そしてこれはイベントキューを NOEVENTS で埋めつくすことになるだろう。 set_blocked() を使って、必要なイベントだけを取るようにしよう -- こうすればイベントキューはずっと管理しやすくなる。

11、管理事件系统

  1. Colorkey vs. Alpha.

There’s a lot of confusion around these two techniques, and much of it comes from the terminology used.

‘Colorkey blitting’ involves telling pygame that all pixels of a certain color in a certain image are transparent instead of whatever color they happen to be. These transparent pixels are not blitted when the rest of the image is blitted, and so don’t obscure the background. This is how we make sprites that aren’t rectangular in shape. Simply call surface.set_colorkey(color), where color is an RGB tuple – say (0,0,0). This would make every pixel in the source image transparent instead of black.

‘Alpha’ is different, and it comes in two flavors. ‘Image alpha’ applies to the whole image, and is probably what you want. Properly known as ‘translucency’, alpha causes each pixel in the source image to be only partially opaque. For example, if you set a surface’s alpha to 192 and then blitted it onto a background, 3/4 of each pixel’s color would come from the source image, and 1/4 from the background. Alpha is measured from 255 to 0, where 0 is completely transparent, and 255 is completely opaque. Note that colorkey and alpha blitting can be combined – this produces an image that is fully transparent in some spots, and semi-transparent in others.

‘Per-pixel alpha’ is the other flavor of alpha, and it’s more complicated. Basically, each pixel in the source image has its own alpha value, from 0 to 255. Each pixel, therefore, can have a different opacity when blitted onto a background. This type of alpha can’t be mixed with colorkey blitting, and it overrides per-image alpha. Per-pixel alpha is rarely used in games, and to use it you have to save your source image in a graphic editor with a special alpha channel. It’s complicated – don’t use it yet.

  1. カラーキー (Colorkey) 対 アルファ (Alpha)

これら 2つのテクニックについては大きな混乱があるみたいだ。 たぶんそれは使われている用語からきていると思う。

“カラーキー (Colorkey) blit” というのは、ある画像中で特定の色をもつピクセルは それがなんであれすべて透明とみなせ、と pygame に教えるものだ。 これら透明ピクセルはその画像の他の部分が blit されるときには blit されない。 だから背景を塗りつぶさないですむ。長方形以外の形をしたスプライトは こうやって作っている。これには、ただ surface.set_colorkey(color) を 呼ぶだけだ。ここで color は RGB 値のタプル -- たとえば (0,0,0) とか -- になる。これでもとの画像中の黒いピクセルはすべて、黒のかわりに透明になるだろう。

“アルファ (Alpha)” はこれとは別のものだ。これには 2つの味つけが存在する。 ひとつは「画像ごとのアルファ」で、これは画像のすべてに適用されるアルファ属性だ。 たぶんこちらが望むほうだろう。アルファ属性は正しくは「半透明」として知られ、 描画に使う画像を やや 不透明にする。たとえば、ある surface の アルファ値を 192 にしてこれを背景に blit すると、各ピクセル色の 3/4 は描画した元絵の色が混ざり、のこりの 1/4 は背景の色が混ざったものになる。 カラーキーとアルファは一緒に使えることを覚えておこう -- これはある画像を、部分的には完全に透明にし、部分的に半透明にできる。

アルファのもうひとつの味つけである「ピクセルごとのアルファ」は もっとこみ入ってる。基本的には、描画に使う画像の各ピクセルが それぞれのアルファ値を 0~255 の範囲でもつようになる。 だからこれを背景に blit したとき、各ピクセルが異なる不透明さをもつように できるのだ。このタイプのアルファ属性はカラーキー blit と一緒に 使うことはできないし、画像ごとのアルファを上書きしてしまう。 ピクセルごとのアルファがゲームに使われることはほとんどなく、 これを使うためには使う画像を、 特別な「アルファチャンネル」をもつグラフィックエディタで 保存しておかなければいけない。これはとにかく複雑だ -- まだ使うのはよそう。

12、Colorkey 与 Alpha

  1. Do things the pythony way.

A final note (this isn’t the least important one; it just comes at the end). Pygame is a pretty lightweight wrapper around SDL, which is in turn a pretty lightweight wrapper around your native OS graphics calls. Chances are pretty good that if your code is still slow, and you’ve done the things I’ve mentioned above, then the problem lies in the way you’re addressing your data in python. Certain idioms are just going to be slow in python no matter what you do. Luckily, python is a very clear language – if a piece of code looks awkward or unwieldy, chances are its speed can be improved, too. Read over Python Performance Tips for some great advice on how you can improve the speed of your code. That said, premature optimisation is the root of all evil; if it’s just not fast enough, don’t torture the code trying to make it faster. Some things are just not meant to be :)

There you go. Now you know practically everything I know about using pygame. Now, go write that game!

  1. Python なやり方でプログラミングしよう。

最後の注意 (でもこれがどうでもいいわけじゃないよ、 ただ最後に来ただけ)。Pygame は SDL のとっても軽いラッパで、 これはまた、お使いの OS のグラフィックス用のとっても軽いラッパでもある。 いままでに書いてきたようなことをやって、それでもまだコードが遅いとしたら、 問題は Python でデータを扱うそのやり方にあるということが多い。 ある種の書き方は、Python では何をやるにも遅くなってしまうことがある。 でもラッキーなことに、Python はじつにクリアーな言語なんだ -- もしコードの一部が不自然に見えたり、重そうに見えたりしたら、 まだスピードアップがのぞめるということ。 Python Performance Tips を読んで、どうやって自分のコードのスピードアップをはかるかの参考にしよう。 この文書では、よく練られていない最適化は諸悪の根源だ、と述べられている。 もし、ただ十分には速くないという程度なら、さらに速くしようとして コードをひねりまわすのはやめよう。なるべくしてはならないこともあるんだよ :)

さあこれで、もうきみはぼくが pygame について知っている 実用的な知識はすべて身につけたわけだ。あとはゲームを書くだけだ!

13、做得更Python一些

David Clark は熱心な pygame ユーザで、コミュニティによって作成された Python ゲームの展示サイト Pygame Code Repository の編集者でもある。 また彼は標準的な pygame アーケードゲーム Twitch の作者でもある。

訳: Yusuke Shinyama

Comments
Write a Comment