用Python和Pygame写游戏-从入门到精通(10)

By | 2011/06/26

有时候无聊在网上翻翻小说看看,绝大多数那叫一个无聊。比如说修炼的境界分几种,都有个名字,然后每种境界再有几层,这不就是变相的打怪练级么?文笔也不咋样,故事情节的驾驭能力更是让我瞠目结舌,想到这些类小说盛行,不觉感到悲从中来。感觉看这些小说,就想在看别人打游戏一般,崩溃到极点。游戏和小说的最大区别,除了声色以外,最不同的就是玩家可以沉入进去,通过自己的双手来参与;而好的游戏,更是可以通过玩家的选择,完全掌控游戏的发展,这是传统的故事媒介无法做到的事情。

很自然的,我们讲述了游戏中视觉上的种种,现在开始就要学习一下游戏中的用户输入。同样我们也要探讨一下如何让用户的输入更为顺畅,换个词就是,如果让游戏的手感更好一些。

游戏设备

玩过游戏的都知道鼠标和键盘是游戏的不可或缺的输入设备。键盘可以控制有限的方向和诸多的命令操作,而鼠标更是提供了全方位的方向和位置操作。不过这两个设备并不是为游戏而生,专业的游戏手柄给玩家提供了更好的操作感,加上力反馈等技术,应该说游戏设备越来越丰富,玩家们也是越来越幸福。

键盘设备

我们先从最广泛的键盘开始讲起。

现在使用的键盘,基本都是QWERTY键盘(看看字幕键盘排布的左上就知道了),尽管这个世界上还有其他种类的键盘,比如AZERTY啥的,反正我是没见过,如果你能在写游戏的时候考虑到这些特殊用户自然是最好,个人感觉是问题不大吧。

以前第二部分也稍微使用了一下键盘,那时候是用了pygame.event.get()获取所有的事件,当event.type == KEYDOWN的时候,在判断event.key的种类,而各个种类也使用K_aK_b……等判断。这里再介绍一个pygame.key.get_pressed()来获得所有按下的键值,它会返回一个元组。这个元组的索引就是键值,对应的就是是否按下,比如说:

    pressed_keys = pygame.key.get_pressed()
    if pressed_keys[K_SPACE]:
        # Space key has been pressed
        fire()pressed_keys = pygame.key.get_pressed()

当然key模块下还有很多函数:

  • key.get_focused —— 返回当前的pygame窗口是否激活
  • key.get_pressed —— 刚刚解释过了
  • key.get_mods —— 按下的组合键(Alt, Ctrl, Shift)
  • key.set_mods —— 你也可以模拟按下组合键的效果(KMOD_ALT, KMOD_CTRL, KMOD_SHIFT)
  • key.set_repeat —— 无参数调用设置pygame不产生重复按键事件,二参数(delay, interval)调用设置重复事件发生的时间
  • key.name —— 接受键值返回键名

注:感谢xumaomao朋友的倾情指正!

使用键盘控制方向

有了上一章向量的基础,只需一幅图就能明白键盘如何控制方向:

很多游戏也使用ASDW当做方向键来移动,我们来看一个实际的例子:

background_image_filename = 'sushiplate.jpg'
sprite_image_filename = 'fugu.png'

import pygame
from pygame.locals import *
from sys import exit
from gameobjects.vector2 import Vector2

pygame.init()

screen = pygame.display.set_mode((640, 480), 0, 32)

background = pygame.image.load(background_image_filename).convert()
sprite = pygame.image.load(sprite_image_filename).convert_alpha()

clock = pygame.time.Clock()

sprite_pos = Vector2(200, 150)
sprite_speed = 300.

while True:

    for event in pygame.event.get():
        if event.type == QUIT:
            exit()

    pressed_keys = pygame.key.get_pressed()

    key_direction = Vector2(0, 0)
    if pressed_keys[K_LEFT]:
        key_direction.x = -1
    elif pressed_keys[K_RIGHT]:
        key_direction.x = +1
    if pressed_keys[K_UP]:
        key_direction.y = -1
    elif pressed_keys[K_DOWN]:
        key_direction.y = +1

    key_direction.normalize()

    screen.blit(background, (0,0))
    screen.blit(sprite, sprite_pos)

    time_passed = clock.tick(30)
    time_passed_seconds = time_passed / 1000.0

    sprite_pos+= key_direction * sprite_speed * time_passed_seconds

    pygame.display.update()

这个例子很简单,就是使用方向键移动小鱼。使用的知识也都讲过了,相信大家都可以理解。不过这里并不是单纯的判断按下的键来获得方向,而是通过对方向的加减来获得最终的效果,这样可能会更简短一些,也需要一些技术;如果把方向写入代码,效率更高,不过明显通用性就要低一些。记得把力气花在刀刃上!当然这个例子也不是那么完美,看代码、实践一下都能看到,左方向键的优先级大于右方向键,而上则优于下,我们是否有更好的方法?……有兴趣的自己考虑~

这个例子我们可以看到,小鱼只能在八个方向移动,如何做到全方向?如果你游戏经验足一点或许可以想到,是的,先转向,再移动,尽管不是那么快捷,但毕竟达到了目标。我们看一下这样的代码怎么写:

background_image_filename = 'sushiplate.jpg'
sprite_image_filename = 'fugu.png'

import pygame
from pygame.locals import *
from sys import exit
from gameobjects.vector2 import Vector2
from math import *

pygame.init()

screen = pygame.display.set_mode((640, 480), 0, 32)

background = pygame.image.load(background_image_filename).convert()
sprite = pygame.image.load(sprite_image_filename).convert_alpha()

clock = pygame.time.Clock()

sprite_pos = Vector2(200, 150)   # 初始位置
sprite_speed = 300.     # 每秒前进的像素数(速度)
sprite_rotation = 0.      # 初始角度
sprite_rotation_speed = 360. # 每秒转动的角度数(转速)

while True:

    for event in pygame.event.get():
        if event.type == QUIT:
            exit()

    pressed_keys = pygame.key.get_pressed()

    rotation_direction = 0.
    movement_direction = 0.

    # 更改角度
    if pressed_keys[K_LEFT]:
        rotation_direction = +1.
    if pressed_keys[K_RIGHT]:
        rotation_direction = -1.
    # 前进、后退
    if pressed_keys[K_UP]:
        movement_direction = +1.
    if pressed_keys[K_DOWN]:
        movement_direction = -1.

    screen.blit(background, (0,0))

    # 获得一条转向后的鱼
    rotated_sprite = pygame.transform.rotate(sprite, sprite_rotation)
    # 转向后,图片的长宽会变化,因为图片永远是矩形,为了放得下一个转向后的矩形,外接的矩形势必会比较大
    w, h = rotated_sprite.get_size()
    # 获得绘制图片的左上角(感谢pltc325网友的指正)
    sprite_draw_pos = Vector2(sprite_pos.x-w/2, sprite_pos.y-h/2)
    screen.blit(rotated_sprite, sprite_draw_pos)

    time_passed = clock.tick()
    time_passed_seconds = time_passed / 1000.0

    # 图片的转向速度也需要和行进速度一样,通过时间来控制
    sprite_rotation += rotation_direction * sprite_rotation_speed * time_passed_seconds

    # 获得前进(x方向和y方向),这两个需要一点点三角的知识
    heading_x = sin(sprite_rotation*pi/180.)
    heading_y = cos(sprite_rotation*pi/180.)
    # 转换为单位速度向量
    heading = Vector2(heading_x, heading_y)
    # 转换为速度
    heading *= movement_direction

    sprite_pos+= heading * sprite_speed * time_passed_seconds

    pygame.display.update()

我们通过上下控制前进/后退,而左右控制转向。我们通过pygame.transform.rotate()来获得了转向后的图片,具体参数可以参考代码。各条语句的作用也可以参考注释。

下次讲解使用鼠标控制游戏。

>> 用Python和Pygame写游戏-从入门到精通(11)

44 thoughts on “用Python和Pygame写游戏-从入门到精通(10)

    1. xishui Post author

      更新11鼠标输入篇,特意在侧边加了一个收藏链接~ 算是友情交换么:)

      Reply
  1. tangly

    # 获得前进(x方向和y方向),这两个需要一点点三角的知识
    heading_x = sin(sprite_rotation*pi/180.)
    heading_y = cos(sprite_rotation*pi/180.)
    基础知识比较菜,感觉这里好像有点模糊

    Reply
    1. xishui Post author

      前进方向是随着角度的变化而变化的,sin和cos不是可以把方向分解为x、y方向的分量么。至于具体的…… 还是需要参考三角函数,本教程不涉及这些东东~见谅!

      Reply
  2. rabbitfan

    用户输入如果是有一个输入框 然后输入任何东西进去的话 要怎么做?

    Reply
  3. pltc325

    # 获得鱼儿的中心点
    sprite_draw_pos = Vector2(sprite_pos.x-w/2, sprite_pos.y-h/2)

    这里不是获得中心点啦,sprite_pos已经是中心点了,这里是获得图像的topleft点啦,紧接着的blit函数不是用到了这个topleft点嘛。

    Reply
    1. xishui Post author

      You are absolutely right! 马上改正!

      Reply
    2. 为什么sprite_pos 是中心点 我打印了一下 它也是左上角的坐标啊?

      Reply
  4. 1111

    fire()pressed_keys = pygame.key.get_pressed()

    前面的fire()pressed_keys没理解什么意思,变量名里可以加个括号?

    Reply
  5. qdmimosa

    转向前进的代码在我的机器上跑前后方向是相反地,即按向下方向键,向上跑,看代码没问题,觉得好奇怪。另外上节鱼朝向鼠标点击点移动的代码,震荡好厉害,一会就看不到鱼了,出去了

    Reply
  6. doo

    现在回贴有点挖fen的味道。^_^

    上面代码我用python3.2 试了下,有问题
    这里应该是反了….
    heading_x = sin(sprite_rotation*pi/180.)
    heading_y = cos(sprite_rotation*pi/180.)
    应该是
    heading_x = cos(sprite_rotation*pi/180.)
    heading_y = sin(sprite_rotation*pi/180.)

    因为坐标系统问题 y 轴是相反的所以..y需加负
    # 转换为单位速度向量
    heading = Vector2(heading_x, -heading_y)

    Reply
  7. xumaomao

    对照pygame的文档发现博主的翻译有些不准确的地方:
    “key.get_focused —— 当前激活的pygame窗口。”这句其实不是“激活的窗口”,而是display的窗口是否获得输入焦点(keyboard focus),返回一个bool值;

    ”key.set_repeat —— 设定允许pygame接受重复按键。“这句也不是指用户在重复按键,而是模拟用户一直按下某键时产生重复按键的效果:用key.set_repeat(delay,interval)时就是按住某个键每隔interval毫秒产生一个KEYDOWN事件。记得前面好像有人问到持续按键是一个event还是多个events的问题,就是这个意思。

    Reply
    1. xishui Post author

      我要是说“我故意留下的错误希望大家来纠正的”,会不会有人相信?好吧,我自己也不信…… 偶改!多谢指正:)

      Reply
  8. soyoung

    “这里再介绍一个pygame.key.get_pressed()来获得所有按下的键值,它会返回一个元组。”这句话翻译有误,返回的是列表(list),不是元组(tuple)。

    Reply
    1. wayhome

      返回的是词典。List和tuple的key只能是integer。

      Reply
  9. fuis

    我想实现屏蔽重复按键的效果 但是不会 求大大帮忙

    pygame.key.set_repeat() #禁止一直按一个键
    pressed_keys = pygame.key.get_pressed()

    是这么用吗?

    Reply
  10. pythonone

    key_direction.normalize()
    为何要转换为单位向量?

    Reply
  11. staroid

    # 图片的转向速度也需要和行进速度一样,通过时间来控制
    sprite_rotation += rotation_direction * sprite_rotation_speed * time_passed_seconds

    你好,这里还不是很清楚。为什么要把rotation_direction也*进来,这个初始为0的值表示的是什么意思?我取消之后发现图片会不停的转动。
    如果该语句计算的是图片的转向速度,这个值得作用是什么呢?
    希望能得到解答,谢谢!

    Reply
  12. 木头

    from gameobjects.vector2 import Vector2 报错是为什么啊
    raise IndexError, “There are 2 values in this object, index should be 0 or 1

    有人能提供一下gameobjects的下载链接么

    Reply
  13. LeonZhao

    第二个 程序
    # 前进、后退
    if pressed_keys[K_UP]:
    movement_direction = +1.
    if pressed_keys[K_DOWN]:
    movement_direction = -1.
    向上的键按了,方向应该是-1,跟向下的搞反了:)

    Reply
    1. PulqueDelaRiddlehous

      0————————->x
      |
      |
      | ———–>0
      | \ sprite_rotation
      | \
      v
      y
      坐标系和角度大概是这样的

      Reply
  14. yang123

    感觉看成向量做有点难理解,不如看成点的坐标

    Reply
  15. yang123

    在纸上画了画正弦余弦,又回到了初中的感觉。其实也没必要用正余弦,直接上下也挺好,还省事。

    Reply
  16. yang123

    rotated_sprite = pygame.transform.rotate(sprite, sprite_rotation)
    ……
    sprite_rotation += rotation_direction * sprite_rotation_speed * time_passed_seconds
    那么只要按下左或右键,rotation_direction就非0,sprite_rotation也非0了,那rotate_sprite岂不是会一直旋转。
    求大神解释

    Reply
    1. yang123

      我知道了,变的一直是sprite,笨死了

      Reply
      1. xishui Post author

        不笨不笨,看你这么短时间内学了这么多,很佩服:)

        Reply
  17. Pingback: 用Python和Pygame写游戏-从入门到精通(10)-演道网

  18. MR. Chen

    抱歉我来挖坟了
    第一个程序按手动下一次按键,程序判定按下次数是两到三次,这样似乎很难通过按两个反方向使得鱼停止。
    这是和扫描频率(或别的什么词)有关吗?

    Reply
  19. student

    ERROR: Could not find a version that satisfies the requirement gameobjects (from versions: none)
    ERROR: No matching distribution found for gameobjects

    Reply
    1. 乔晓晟

      这个是py2的包,py3没有提供服务,只能自己写异常,跳过吧

      Reply
  20. Pingback: pygame学习_Johngo学长

发表评论

您的电子邮箱地址不会被公开。