随笔 - 97, 文章 - 22, 评论 - 81, 引用 - 0
数据加载中……

Pygame游戏开发 之二

Pygame游戏开发之二

初窥门径

游戏中需要用到很多对象,比如人物、野怪、建筑物、UI控件等等,他们的行为不尽相同,但是总有那么些共同点,如果将所有的东西都封装成各自的类难免会写上很多重复的代码,因为两个对象如果具有相同的行为(比如人物和野怪都可以行走)如果写两个move()函数,以后改起来就要修改两个地方,不容易维护而且扩展很麻烦,但是人物和野怪又有其它不相同的行为,比如技能之类的不同。所以比较好的处理方法是抽象出一个生物类,然后让人物和野怪去继承它,举个例子:

class RenderObject(pygame.sprite.Sprite) :

    # 静态变量

    images = []

    def __init__(self, selfdata) :

        self.image       = self.images[ int(selfdata[0])  ]

        self.size         = [self.image.get_width(), self.image.get_height()]

        self.offset       = [0,0]

        self.position     = [0,0]

        self.speed       = [0.0,0.0]

    def move(self, xgo, ygo) :

        do_move()

    def draw(self, screen) :

        do_draw()

以上这个类RenderObject是自己定义的用于渲染的类的基类,它继承自pygame.sprite.Sprite,这里有必要先介绍一下pygame.sprite,这个模块在pygame中是选择性使用的,他可以作为游戏中很多对象的基类,并且将它拥有的东西绘制到Surface上,还有一个Group类是Sprite类的容器类,可以包含很多Sprite

       RenderObject有三个类函数,__init__Python中的特殊函数,类似C语言中的构造函数,当类实例被创建的时候,这个函数会被自动执行,它的目的是执行一些该对象的必要的初始化工作(我在这个函数中加入了一个selfdata的列表,以便于不同类型的控件添加初始化信息)。move函数是执行移动操作,而draw函数则负责渲染。

       首先来看下__init__函数,在Python中使用变量不需要提前声明,所以要用到的变量我把它放在了__init__函数中进行初始化,注意到每个函数都有一个self参数,这个就好比C语言中的this指针,但是它是显式声明,隐式调用的。在使用类内部变量的时候需要加self做修饰,我们可以看到该类的一些成员变量:

self.image      # 该渲染对象对应的Surface

self.size        # 该渲染对象在Surface的矩形区域的宽高

self.offset       # 该渲染对象在Surface的左上角坐标

self.position     # 该渲染对象将被绘制到屏幕的位置

self.speed       # 该渲染对象的速度

       之所以要用到offsetsize,是因为该Surface不是完全被用来显示的,我们只取某一张Surface的某一部分用于显示,如果不是很明白,那么参看Player类的相关描述就知道为什么了。

       对于move(self, xgo, ygo)函数,我们暂时可以这样实现:

if xgo :

self.position[0] += self.speed[0] * xgo

if ygo :

self.position[1] += self.speed[1] * ygo

xgoygo都是三值变量(1,0,-1),分别表示在两个坐标轴方向是否发生了位移,并且这个位移是正向的还是逆向的。于是我们可以通过键盘输入来传递对应的参数,position的值就会随之变化了。注意如果这里速度为0,则表示静态物体,position不会做任何变化。

对于draw(self, screen)函数,用于将当前对象绘制到屏幕上,screen参数是上一节中提到的屏幕Surface,我们调用Surfaceblit函数就可以在屏幕上绘制当前对象了,像这样:

screen.blit( self.image, self.position, (self.offset, self.size))

(self.offset, self.size)表示self.image这个Surface上对应的矩形区域。

基类完成后,我们需要派生出一些子类,例如:

class Player(RenderObject) :

    actionwait = 50

    def __init__(self, selfdata) :

        RenderObject.__init__(self, selfdata)

        self.size[0] /= 4

        self.size[1] /= 4

        self.speed = [0.1,0.1]

        self.direction=3

        self.action=0

    def move(self, xgo, ygo) :

        RenderObject.move(self, xgo, ygo)

        if xgo or ygo :

            self.action = (self.action + 1) % (self.actionwait * 4)

        self.offset[0] = (self.action / self.actionwait) * self.size[0]

        self.offset[1] = self.direction * self.size[1]

 Player类作为游戏中的操作玩家,继承自RenderObject,并且重写它的__init__move函数,引入directionaction变量分别表示当前Player的方向和动作,如图2-1(资源来自66RPG,一个基于RPG Maker xp业余游戏制作的交流、合作、分享的网站,在此感谢66RPG为我们游戏开发者提供这么多的资源下载,泪流满面啊||*_*||)。

2-1

该图是游戏人物的四方向行走图,每一行代表一个方向,每一列代表一个动作,我们只需要通过键盘或者鼠标的输入事件不断改变offset的值,就可以顺利得进行人物动画的切换。这就是为什么之前要引入offsetsize的原因了,因为这个对象只取当前图片的某一部分用于显示,而且每次是根据人物不同的状态进行切换的,还可以发现Player类中引入了一个actionwait的静态变量,之所以用到它是因为现在的电脑的处理速度实在是太快了,每次move调用,我们会将self.action的值+1,并且循环往复,如果没有一个延时操作,人物的动作切换会非常快,显得十分不自然。

同时可以根据自己的喜好派生出很多不同类型的游戏元素,比如野怪、按钮、菜单等等。

 

说到这里,还没讲到images这个静态变量。RenderObject中定义了一个静态列表,表示所有图片的Surface对象的集合,我们可以在游戏载入的时候将所有的图片都转化成Surface,然后每次调用的时候直接在images中寻找对应的Surface即可,这样就不用每次都调用load_image函数了。

但是还有个问题,比如我现在有以下几张图片:hero.pnggo.pngtitle.pngmenu.png,那么不免要写下面这段代码:

Imgs = []

Imgs.append( load_image( ‘hero.png’) )

Imgs.append( load_image( ‘go.png’) )

Imgs.append( load_image( ‘title.png’) )

Imgs.append( load_image( ‘menu.png’) )

RenderObject.images = Imgs

append是列表的函数,表示在已有列表中加入一个新的成员。这样就可以把所有图片预先载入到RenderObject.images列表中,以后访问的时候只需要用下标索引即可。这也就是问题所在,因为现在图片只有四张,这个代码没什么大的问题,但是如果图片的数量增加到四十张、乃至四百张,那么问题也就随之而来了,你将不断地添加同一段代码Imgs.append( load_image( ‘XXX’) ),这会使程序日渐冗长,而且每次新增一张图片就需要添加一段代码,某张图片不需要了,有需要找到那段代码将其删除,维护会变得越来越繁琐。

图片的文件名其实是一系列的字符串数据,我们大可以把它放到配置文件中,然后利用Python的文件读取接口来将它们读入,这样以后需要添加一张图片的时候只需要在配置文件中加入一个文件名即可,方便了许多。

我把所有的图片的文件名放到了一个叫image_config.txt的文件中,具体的结构组织可以看个人喜好,以下是我的组织形式:

 

/Control

title 1

menu 1

/Player

hero 1

monster 0

/Animation

cloud 10

 

’/’开头的表示文件夹,比如/Control表示在Control文件夹下有两种类型的文件(titlemenu),否则是某个文件的前缀加上当前类型文件的数量,比如title 2表示的其实是title-0.pngtitle-1.png这两个文件,cloud 4则是cloud-0.pngcloud-1.pngcloud-2.pngcloud-3.png这四个文件。之所以要用编号的形式,是因为这些图片有可能是一个动画,又或许是一类相同的对象,这样一来添加新图片的时候会方便许多。

       然后就是按照你自己定的配置文件格式进行解析,从而将图片进行载入。以下是我的图片载入过程,我把它放在了InitializeImage函数中:

def InitializeImage() :

    config = open('image_config.txt', 'r')

    config.seek(0)

    filelist = config.readlines()

    config.close()

    nowfolder = ''

    imgs      = []

    for filename in filelist :

        if len(filename) < 2:

            continue

        if filename[0] == '/' :

            nowfolder = filename[1:-1]

        else :

            idx = filename.find(' ')

            pre = filename[:idx]

            for x in range(  int(filename[idx+1:-1])  ) :

                pfile = nowfolder + '\\' + pre + '-' + str(x) + '.png'

                print pfile

                imgs.append( data.load_image(pfile) )

    RenderObject.images = imgs

       open函数是Python中的文件读取接口,类似C语言中的fopen,参数也是一样的,返回的则是一个文件对象,我们首先调用seek(0)将文件头指针定位到文件开头,然后将该文件中所有的行通过readlines()读入到filelist列表中,并且关闭文件。这时候filelist就储存了该配置文件中所有的图片文件信息,接下来的步骤是对它进行解析。

       通过遍历filelist的每个成员,判断当前的这一行表示的是文件夹信息还是图片文件信息。如果是文件夹则更新nowfoldernowfolder表示当前文件夹。filename[1:-1]表示不包含第一个字符和最后一个字符的子串(-1是最后一个字符的索引),之所以不要最后一个字符是因为读入的时候是通过读行的,所以最后一个字符在Win32下是\n也就是换行;如果是图片文件信息,那么必定是类似 XXX num 的串,那么我们首先要找到这个串中的空格(这里需要保证文件名中不包含空格),通过字符串内置的find(‘ ‘)函数可以找到第一个出现空格的字符的下标,然后我们就可以把这个串分成两部分了,将第二个部分也就是找到的下标到结尾的子串,将它转换成int类型,这就是当前类型图片的数量,最后用一个for循环将所有当前类型的图片创建出来。一个个appendimgs上去即可。

       再回头来看RenderObject.__init__(self, selfdata)函数,该函数中还有selfdata变量,我把它定义成一个列表,不同的子类将会拥有各自不同的列表元素和个数,就好比对于动画Animation类来说,它的值就是[st, en],表示当前动画需要表示的始末图片在images中的索引编号。

       Smpl = Animation([3, 12])

       可以简单得通过以上的语句,就构造出一个Animation的对象,并且可以通过Smpl访问它所有的成员变量来进行初始化对象的起始位置以及其它信息。这里的动画指的是最普通的动画,是通过不同图片的循环播放来实现的,以上的实例就是一个拥有10张图片的动画。(未完待续)

    以上内容均为原创 转载请注明出处

 

posted on 2011-04-24 19:55 英雄哪里出来 阅读(5603) 评论(3)  编辑 收藏 引用 所属分类: Pygame

评论

# re: Pygame游戏开发 之二  回复  更多评论   

留爪,持续关注
2011-04-24 23:11 | zhaoyg

# re: Pygame游戏开发 之二  回复  更多评论   

@zhaoyg
阁下在学Pygame吗?
2011-04-25 10:12 | 英雄哪里出来

# re: Pygame游戏开发 之二  回复  更多评论   

不错不错,期待更新
2011-04-27 00:44 | 贼寇在何方

只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理