【pygame实现星露谷物语风格游戏】1.窗口的创建与展示

前言:

下面是对本系列的一些说明:

首先游戏是一个外国人写的,原视频链接为https://www.youtube.com/watch?v=R9apl6B_ZgI

bilibili搬运视频的链接为:简介_哔哩哔哩_bilibili

至于游戏是不是星露谷物语风格,我也不知道,因为我也没玩过,只不过bilibili的视频标题就是这样的。

游戏的最终效果,可以点进B站的链接去看视频的P1简介部分

原视频是英文,机器翻译,可能会对一些人的学习造成难度

我跟着原视频写了一遍,在代码中添加了中文注释,对原版的代码出现bug的地方做了修改,并且根据自己的理解写了这篇中文版教程。

本系列,python自带的东西一概不会多说,也不会系统的将pygame里的所有API是如何使用的。但是只要是pygame里面的东西,第一次出现的时候都会详细说明是怎么使用的,比如这个函数的参数代表什么,返回值是什么。只要讲过一次,以后再出现就不会细讲了。本游戏是比较典型的2D游戏,所以一套下来pygame中常用到的东西基本都会讲到。

相比于pygame中的API,这次实战更重要的是能够获得2D游戏创作的一些套路,很多东西都是通用的,比如主角的移动,摄像机的设置,伪3D效果,碰撞检测。这些东西无论是在python中写,还是在其他的游戏开发引擎中写,大体思想都是相同的。学会了了这些思想,想要开发新的2D游戏,基本就不会遇到任何困难了。

完成这次实战,更加能够锻炼面向对象编程的能力,因为所有的代码基本都封装到类里面了。

本系列基本上是对代码逐行解读,并且会在每一节的最后,贴出本节修改过的文件里面的全部代码。

接下来是原作者给出的项目文件的链接,我们需要里面的图片素材,音频素材等等:

https://github.com/clear-code-projects/PyDew-Valley

下载完成后文件格式如下图所示

原作者在完成一部分功能后,会把截止到目前的所有代码保存到一个文件夹下,总共有23个文件夹,每个文件夹里面都有我们需要的完整素材,我们可以随便打开一个文件夹,里面名为code的文件夹是存放代码的地方,其他的文件夹都是我们用到的音频或者图片的素材,我们可以把除了code的文件夹都复制到我们的项目下,并且在我们的项目下新建一个名为code的文件夹,在这个文件夹里面写代码

接下来就是正文部分了,本节将讲述第一部分,窗口的创建与展示:


一.项目的架构:

项目的架构如下图所示,在主文件夹下存放了图中的5个文件夹,其存放的内容分别如箭头标注所示,其中code存放代码,我们每次创建.py文件都在该文件夹下创建。本次所需要编写的是main.py,settings.py和level.py三个文件,后续也会根据功能的增加不断添加新的代码,或者添加新的.py文件。

二.settings.py

再编写一个项目时通常会创建很多常量,我们希望把所有用到的常量都写在一个python文件中,如果要修改一些常量,直接到这个文件中去修改即可。

settings.py就是我们用来存放常量的文件,它的内容如下所示:

from pygame.math import Vector2

# screen

SCREEN_WIDTH = 1280

SCREEN_HEIGHT = 720

TILE_SIZE = 64


# overlay positions

OVERLAY_POSITIONS = {

    'tool' : (40, SCREEN_HEIGHT - 15),

    'seed': (70, SCREEN_HEIGHT - 5)}


PLAYER_TOOL_OFFSET = {

    'left': Vector2(-50,40),

    'right': Vector2(50,40),

    'up': Vector2(0,-10),

    'down': Vector2(0,50)

}


LAYERS = {

    'water': 0,

    'ground': 1,

    'soil': 2,

    'soil water': 3,

    'rain floor': 4,

    'house bottom': 5,

    'ground plant': 6,

    'main': 7,

    'house top': 8,

    'fruit': 9,

    'rain drops': 10

}


APPLE_POS = {

    'Small': [(18,17), (30,37), (12,50), (30,45), (20,30), (30,10)],

    'Large': [(30,24), (60,65), (50,50), (16,40),(45,50), (42,70)]

}


GROW_SPEED = {

    'corn': 1,

    'tomato': 0.7

}


SALE_PRICES = {

    'wood': 4,

    'apple': 2,

    'corn': 10,

    'tomato': 20

}

PURCHASE_PRICES = {

    'corn': 4,

    'tomato': 5

}

 

我们这次只关心其中的两个常量,就是下图中框出来的部分

他们分别代表了窗口的高度和宽度,其值可以根据各自的情况更改。

至于其他的值代表的含义,以后用到的时候会说,这里可以先把所有的内容都先复制到自己的setting.py中

三.main.py

main.py的作用是给用户提供一个运行程序的接口,在运行main.py后,他会创建一个窗口并将它显示出来。

通常一个项目我们不希望它的main.py文件很长,因此我们并不在main.py中实现游戏的内容,而是希望再level.py中实现游戏的内容,因此在main.py文件中会调用level.py提供的接口,而后续的其他游戏功能的实现,都会写在level.py文件中,或者写在其他的.py文件中再由level.py去调用,总之,这次写完main.py后,我们基本上不会动这个文件了。其完整代码如下所示:

import pygame, sys

from settings import *

from level import Level



class Game:

    def __init__(self):

        #初始化

        pygame.init()

        #设置屏幕

        self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))

        #设置标题

        pygame.display.set_caption('农场')

        #创建时钟,为以后得到dt使用

        self.clock = pygame.time.Clock()

        #创建关卡对象Level,该类在level.py文件中

        self.level = Level()


    def run(self):

        while True:

            #按键检测

            for event in pygame.event.get():

                """如果按下的是右上角的红叉,就退出程序"""

                if event.type == pygame.QUIT:

                    #退出pygame

                    pygame.quit()

                    #退出python程序,不捕获异常,不加上这一句也能退出,但是会在中断报错

                    sys.exit()


            dt = self.clock.tick() / 1000

            self.level.run(dt)

            #更新画面,一定要写在while循环的最后

            pygame.display.update()



if __name__ == '__main__':

    game = Game()

    game.run()

 

如下是代码解读:

1.导包部分

import pygame, sys

from settings import *

from level import Level

 

首先要导入pygame

sys是对系统进行操作的一个包,用到的很少,下面会进行解释

然后就是从settings(我们自己的settings.py)中导入所有的东西,这样我们就可以直接使用其中的常量了

然后就是从level.py中导入Level类,因为level.py还没写,所以先跳过

2.编写Game类

其中包含了两个函数,__init__函数是初始化的,每次创建一个game对象就会自动调用__init__函数

def __init__(self):

    #初始化

    pygame.init()

    #设置屏幕

    self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))

    #设置标题

    pygame.display.set_caption('农场')

    #创建时钟,为以后得到dt使用

    self.clock = pygame.time.Clock()

    #创建关卡对象Level,该类在level.py文件中

    self.level = Level()

 

pygame.init()是初始化pygame,这是pygame作者的硬性要求,在使用pygame之前写上这么一句就行了。只要在这里初始化过一次之后就不用再写了

pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))是创建一个窗口,其中的(SCREEN_WIDTH, SCREEN_HEIGHT)分别是窗口的宽度和高度,这窗口作为返回值被self.screen接收,以后我们要对窗口进行操作,对self.screen进行操作就行了

pygame.display.set_caption('农场')是设置标题,文字可以根据个人喜好随便写,下图箭头所指的位置就是一个窗口的标题

self.clock = pygame.time.Clock()是创建一个Clock对象,翻译过来可能称作时钟更为贴切,作者再time.py这个模块中写了很多关于时间的类,Clock这个类中包含了一些关于游戏时钟的方法,我们在下面会用到其中的一些方法,等用到的时候会再说。

self.level = Level()这个就是创建一个Level对象,但是level.py还没写,所以可以先跳过

下面是run函数,也就是让游戏运行起来的函数:

def run(self):

    while True:

        #按键检测

        for event in pygame.event.get():

            """如果按下的是右上角的红叉,就退出程序"""

            if event.type == pygame.QUIT:

                #退出pygame

                pygame.quit()

                #退出python程序,不捕获异常,不加上这一句也能退出,但是会在中断报错

                sys.exit()


        dt = self.clock.tick() / 1000

        self.level.run(dt)

        #更新画面,一定要写在while循环的最后

        pygame.display.update()

 

我们不希望游戏是窗口闪过一下就退出的,因此我们需要让这个程序一直保持运行,因此我们写了一个死循环While True:,这样我们就能一直运行这个程序除非我们主动关闭它。

这个循环被称为游戏的主循环,和pygame.init()必须写在开头类似,有一个语句是一定要写在主循环的结尾处的,就是pygame.display.update(),它的作用是更新窗口中的画面,就比如一开始主角的位置在(0,0)处,主角向右移动了1个像素,他的坐标变成了(1,0),但是此时游戏的画面中主角还是在原位置,只有执行了pygame.display.update()之后,游戏画面才会被更新,主角才会到他相应的位置。

由于一次循环调用一次pygame.display.update(),一次循环更新一次画面,因此一次循环我们称为一帧。

pygame.event.get()是一个检测运行中出现的事件的方法,它会将这一次循环(这一帧)中所有出现的事件都检测出来,并且以一个列表的形式返回。

而pygame中的事件大概有退出(pygame.QUIT),键盘按下,键盘松开等等,这里我们只用到退出事件,退出事件指的是用户点击窗口右上角的X。

pygame.event.get()返回这一帧发生的所有时间的列表,我们用for循环来遍历这个列表,如果发现列表中有一个事件是退出(if event.type == pygame.QUIT:),那我们就退出pygame(pygame.quit()),然后再推出这个python程序(sys.exit())

这里用到了刚刚导包的时候导入的sys,sys.exit()的作用是退出这个python程序,如果我们不写这句话,也可以退出,只不过是以程序错误的形式被迫退出的,如下图所示,如果不写sys.exit(),点击窗口右上角的X退出,下面的窗口会提示报错信息,进而中断整个程序

而加上了这句话,就相当于我们主动退出,不会报错

 dt = self.clock.tick() / 1000,其中dt这个变量代表英文完整版翻译过来应该是变化的事件,它代表的是一帧(一次循环)所花费的事件,单位为秒

他的得到的过程如下:

 self.clock.tick()就是我们上面说的Clock类中的一个方法,它的返回值是这一帧距离上一帧经过了多少毫秒。再除以1000,得到的就是这一帧距离上一帧过去了多少秒了。

tick()这个方法还有一个作用就是控制游戏的帧率,如果括号内传入参数,这个方法就会控制游戏的帧率不超过这个参数的值,比如在这里写self.clock.tick(60),那么就会控制游戏的帧率不超过60,主要原理就是通过暂停来让游戏主循环进行的不那么快,一秒最多只能循环60次。

dt这个变量再后续会又用,等用到了再说,先知道他是代表一帧所花的时间就行

self.level.run(dt) 这个语句是调用上面创建的Level类的run函数,因为还没写level.py,先跳过。

3.主函数

上面编写的Game类是不会自己运行的,需要你在主函数中创建一个game对象,此时会自动调用__init__函数,然后再在主函数中调用game的run函数即可。

if __name__ == '__main__':

    game = Game()

    game.run()

 

四.level.py

我们在main.py创建了一个Level类,并且调用了他的run函数,因此我们要在level.py中写一个Level类,并实现他的run函数。相似的,Level类中的__init__函数是用来初始化的,run函数是用来运行游戏的。

在我们启动的main.py后,首先创建一个Game类的对象game,自动调用game的__init__函数,在这个__init__函数中,我们创建了一个Level类的对象level,自动调用了level的__init__函数,至此,游戏的初始化完成。我们调用game的run()函数,在game的run()函数中,我们写了一个游戏的主循环,每一次循环我们都调用了level的run函数。因此,我们也可以猜出来,Level类的run()函数,才是实现游戏的主要部分。

完整代码如下:

import pygame

from settings import *


class Level():

    def __init__(self):

        #得到屏幕的画面,得到的这个画面与main.py中的screen相同

        self.display_surface = pygame.display.get_surface()

    def run(self,dt):

        #窗口的背景设为黑色

        self.display_surface.fill('black')

 

本次的level部分实际上并没有实现什么东西,只不过写了一个大概的框架,留着以后填充。

1.导包部分

import pygame

from settings import *

 

pygame是一定要导的

settings作为记录本项目所有常量的文件,也是一定要导的

2.Level类

class Level():

    def __init__(self):

        #得到屏幕的画面,得到的这个画面与main.py中的screen相同

        self.display_surface = pygame.display.get_surface()

    def run(self,dt):

        #窗口的背景设为黑色

        self.display_surface.fill('black')

 

其中pygame.display.get_surface()是得到窗口,由于我们的窗口是在main.py中创建的,并且用了Game类中的self.screen来接受它,这就导致了如果我们想在Level类中对窗口进行操作,还得去调用main.py中的Game类中的screen对象,这需要我们在开头import main ,但是在main.py中我们import了level,又在level.py中import了mian,这在逻辑上是十分混乱的,我们还不如直接写到同一个文件中去。好在pygame提供了这样的一个方法,让我们能获取窗口,这里的display_surface与main.py中的screen指的是同一块窗口。

self.display_surface.fill('black')是将窗口填充为黑色,参数可以用字符串的形式来表示颜色,也可以用一个列表的形式,利用RGB来表示颜色,比如(0,0,0)代表黑色,(255,0,0)代表红色等等。

五.总结

本次,我们在settings.py中创建了本项目所需要的所有常量,并且在main.py中完成了游戏窗口的创建与显示,并且创建了Level对象并在游戏主循环中调用其run()函数。在level.py中,我们编写了Level类的框架,包括一个初始化__init__函数和一个run()函数,这两个函数等待我们的后续完善。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

owooooow

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值