介绍
RPG(Role-Playing Game),角色扮演游戏 是游戏类型的一种,玩家在游戏中负责扮演英雄角色在一个写实或虚构世界中活动。
Pygame框架,也叫作Pygame库 使得开发2D图形程序变得很容易。当你明白了Pygame是如何工作的,以及它提供了哪些功能,你会发现编写视频游戏程序,只不过是点亮像素,让漂亮的图片出现在屏幕上以响应键盘和鼠标输入而已。
本文作者结合参考书的学习和自己的实践经验,开发了《唐小山的世界》小游戏。在本游戏中,英雄唐小山不小心掉到一个洞穴里。想要离开洞穴,需要打败各种怪物使自己变强,最终打败看守洞口的Boss怪赢得游戏的胜利,如下图所示。本文将逐步介绍这个游戏是如何实现的。
一、构建游戏地图界面
游戏屏幕的宽高设置为800 x 600 像素,将这个屏幕划分成九个部分,如下图所示。 这个界面实现的原理是让pygame进入主循环,然后不断地在屏幕上画9个矩形。这九个矩形的left, top, width, height 可以设置为常量。
然后要建立9个列表(list ),分别负责在9个消息框中打印。
然后要实现鼠标移动到这9个框上时高亮显示该信息框,信息过多的时候鼠标点击翻页。
为了实现这个框架,我们需要建立一个游戏运行程序run.py ,和5个游戏模块GameConst.py 、Game.py 、Player.py 、Hero.py 、Narration.py 。
GameConst模块用来存放游戏相关的常量。
Game模块包括Game类,其中包含运行游戏、显示游戏屏幕、打印消息、响应玩家鼠标移动和点击的方法。
Player模块包含Player类,其中包含处理英雄角色数据的方法,如显示英雄属性、给消息框增加内容。
Hero模块包含Hero类,其中包含英雄角色的属性(如姓名)。
Narration模块包括显示文字形式的游戏剧本的函数。
run.py程序
run.py 的代码如下:
import pygame, sys
import Game, GameConst, Player, Hero
from pygame.locals import *
def main():
game = Game.Game(GameConst.fps, GameConst.window_width, GameConst.window_height, '唐小山的世界')
player = Player.Player()
hero = Hero.Hero()
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == MOUSEMOTION:
player.mouseX, player.mouseY = event.pos
if player.game_process < GameConst.WALKING_ON_THE_MAP and player.game_process >= 0:
game.display_introduction(hero, player, game)
elif player.game_process >= GameConst.WALKING_ON_THE_MAP and player.game_process < 90:
game.display_map(player, game)
game.respond_to_mouse_motion(player, game)
b1, b2, b3 = pygame.mouse.get_pressed()
game.respond_to_click(b1, b2, b3, player, game)
pygame.display.update()
game.tick()
if __name__ == '__main__':
while True:
main()
首先建立了Game 对象、Player 对象和Hero 对象。数据都存放在这些对象中,这些对象通过方法传递到其他模块中,对这些对象中的数据进行读写。
游戏的主循环主要包括对玩家鼠标移动时位置进行跟踪,然后根据player 对象中的game_process 变量的值判断游戏进度,如果刚开始则显示过场动画,如果在地图上则显示消息。
最后是鼠标点击事件的响应,b1 、b2 、b3 均为0 ,左键单击时b1 为1 ,右键单击时b3 为1 ,同时按下左键和右键则b2 为1 。
GameConst.py模块
GameConst.py 的代码如下:
fps = 9
window_width = 800
window_height = 600
FontSize = 18
FontHeight = 20
TextMargin = 10
MapBox = (0, 0, 400, 400)
MsgBox1 = (0, 400, 400, 100)
MsgBox2 = (0, 500, 400, 100)
MsgBox3 = (400, 0, 200, 160)
MsgBox4 = (600, 0, 200, 160)
MsgBox5 = (400, 160, 400, 120)
MsgBox6 = (400, 280, 400, 120)
MsgBox7 = (400, 400, 400, 100)
MsgBox8 = (400, 500, 400, 100)
MsgBoxF1 = (0, 0, 800, 600)
HERO_WIN = -2
HERO_LOST = -1
WALKING_ON_THE_MAP = 7
HERO_ATTACKING = 9
HERO_PROPERTIES_PANEL = 91
SKILLS_PANEL = 93
可以看到,在这里定义了一些常量,使用的时候只要在run.py 中用import 导入本模块,就可以直接使用这些常量:
>>> import GameConst
>>> GameConst.WALKING_ON_THE_MAP
7
Game.py模块
Game.py 的代码如下:
import pygame, sys, os, random
import RGB, GameConst, Narration, Player, Hero
class Game():
def __init__(self, fps, window_width, window_height, caption):
pygame.init()
self.__f = fps
self.__c = pygame.time.Clock()
self.window_width = window_width
self.window_height = window_height
self.DISPLAYSURF = pygame.display.set_mode((self.window_width, self.window_height))
self.FONT1 = pygame.font.SysFont('stzhongsong', 18)
self.__caption = caption
pygame.display.set_caption(caption)
self.message_1 = []
self.message_2 = []
self.message_3 = []
self.message_4 = []
self.message_5 = []
self.message_6 = []
self.message_7 = []
self.message_8 = []
self.message_F1 = []
def _getd(self): return self.DISPLAYSURF
def _setd(self, value): self.DISPLAYSURF = value
screen = property(_getd, _setd)
def _getf(self): return self.__f
def _setf(self, value): self.__f = value
FPS = property(_getf, _setf)
def _getc(self): return self.__c
def _setc(self, value): self.__c = value
fpsClock = property(_getc, _setc)
def _getcaption(self): return self.__caption
def _setcaption(self, value): self.__caption = value
caption = property(_getcaption, _setcaption)
def tick(self):
''' 自动的暂停,控制帧速率 '''
self.fpsClock.tick(self.FPS)
def fill(self, color):
self.DISPLAYSURF.fill(color)
def print_text(self, font, x, y, text, color=RGB.White, shadow=True):
''' 使用选定的字体font,在(x, y)位置开始以颜色color打印文本text。'''
imgText = font.render(text, True, color)
self.DISPLAYSURF.blit(imgText, (x, y))
def print_message_F1(self):
''' 在过场动画信息框内打印消息。'''
s = self.message_F1
left, top, width, height = GameConst.MsgBoxF1
for i, v in enumerate(s):
self.print_text(self.FONT1, left+GameConst.TextMargin, top+GameConst.TextMargin+i*GameConst.FontHeight, v, RGB.White)
def print_message_1(self):
''' 在位置信息框内打印消息。'''
s = self.message_1[0:4]
left, top, width, height = GameConst.MsgBox1
for i, v in enumerate(s):
self.print_text(self.FONT1, left+GameConst.TextMargin, top+GameConst.TextMargin+i*GameConst.FontHeight, v, RGB.White)
if len(self.message_1) > 4:
self.print_text(self.FONT1, left+300, top+GameConst.TextMargin, '点击翻页...', RGB.White)
def print_message_2(self):
''' 在战斗提示信息框内打印消息。'''
s = self.message_2[0:4]
left, top, width, height = GameConst.MsgBox2
for i, v in enumerate(s):
self.print_text(self.FONT1, left+GameConst.TextMargin, top+GameConst.TextMargin+i*GameConst.FontHeight, v, RGB.White)
if len(self.message_2) > 4:
self.print_text(self.FONT1, left+300, top+GameConst.TextMargin, '点击翻页...', RGB.White)
def print_message_3(self):
''' 在角色属性框内打印消息。'''
s = self.message_3
for i, v in enumerate(s):
left, top, width, height = GameConst.MsgBox3
self.print_text(self.FONT1, left+GameConst.TextMargin, top+GameConst.TextMargin+i*GameConst.FontHeight, v, RGB.White)
def print_message_4(self):
''' 在怪属性框内打印消息。'''
s = self.message_4
for i, v in enumerate(s):
left, top, width, height = GameConst.MsgBox4
self.print_text(self.FONT1, left+GameConst.TextMargin, top+GameConst.TextMargin+i*GameConst.FontHeight, v, RGB.White)
def print_message_5(self):
''' 在英雄攻击信息框内打印消息。'''
s = self.message_5
for i, v in enumerate(s):
left, top, width, height = GameConst.MsgBox5
self.print_text(self.FONT1, left+GameConst.TextMargin, top+GameConst.TextMargin+i*GameConst.FontHeight, v, RGB.White)
def print_message_6(self):
''' 在怪攻击信息框内打印消息。'''
s = self.message_6
for i, v in enumerate(s):
left, top, width, height = GameConst.MsgBox6
self.print_text(self.FONT1, left+GameConst.TextMargin, top+GameConst.TextMargin+i*GameConst.FontHeight, v, RGB.White)
def print_message_7(self):
''' 在拾到东西信息框内打印消息。'''
s = self.message_7[0:4]
left, top, width, height = GameConst.MsgBox7
for i, v in enumerate(s):
self.print_text(self.FONT1, left+GameConst.TextMargin, top+GameConst.TextMargin+i*GameConst.FontHeight, v, RGB.White)
if len(self.message_7) > 4:
self.print_text(self.FONT1, left+300, top+GameConst.TextMargin, '点击翻页...', RGB.White)
def print_message_8(self):
''' 在英雄升级信息框内打印消息。'''
s = self.message_8[0:4]
left, top, width, height = GameConst.MsgBox8
for i, v in enumerate(s):
self.print_text(self.FONT1, left+GameConst.TextMargin, top+GameConst.TextMargin+i*GameConst.FontHeight, v, RGB.White)
if len(self.message_8) > 4:
self.print_text(self.FONT1, left+300, top+GameConst.TextMargin, '点击翻页...', RGB.White)
def display_introduction(self, hero, player, game):
''' 显示过场动画的内容。'''
self.fill(RGB.Black)
Narration.display_introduction(hero, player, game)
def display_map(self, player, game):
self.fill(RGB.Black)
pygame.draw.rect(self.DISPLAYSURF, RGB.Black, GameConst.MsgBox1, 0)
pygame.draw.rect(self.DISPLAYSURF, RGB.White, GameConst.MsgBox1, 1)
pygame.draw.rect(self.DISPLAYSURF, RGB.Black, GameConst.MsgBox2, 0)
pygame.draw.rect(self.DISPLAYSURF, RGB.White, GameConst.MsgBox2, 1)
pygame.draw.rect(self.DISPLAYSURF, RGB.Black, GameConst.MsgBox3, 0)
pygame.draw.rect(self.DISPLAYSURF, RGB.White, GameConst.MsgBox3, 1)
pygame.draw.rect(self.DISPLAYSURF, RGB.Black, GameConst.MsgBox4, 0)
pygame.draw.rect(self.DISPLAYSURF, RGB.White, GameConst.MsgBox4, 1)
pygame.draw.rect(self.DISPLAYSURF, RGB.Black, GameConst.MsgBox5, 0)
pygame.draw.rect(self.DISPLAYSURF, RGB.White, GameConst.MsgBox5, 1)
pygame.draw.rect(self.DISPLAYSURF, RGB.Black, GameConst.MsgBox6, 0)
pygame.draw.rect(self.DISPLAYSURF, RGB.White, GameConst.MsgBox6, 1)
pygame.draw.rect(self.DISPLAYSURF, RGB.Black, GameConst.MsgBox7, 0)
pygame.draw.rect(self.DISPLAYSURF, RGB.White, GameConst.MsgBox7, 1)
pygame.draw.rect(self.DISPLAYSURF, RGB.Black, GameConst.MsgBox8, 0)
pygame.draw.rect(self.DISPLAYSURF, RGB.White, GameConst.MsgBox8, 1)
if player.game_process >= GameConst.WALKING_ON_THE_MAP and player.game_process < 90:
player.pageup_test(game)
self.print_message_1()
self.print_message_2()
player.display_attribution(game)
self.print_message_3()
self.print_message_4()
self.print_message_5()
self.print_message_6()
self.print_message_7()
self.print_message_8()
def respond_to_click(self, b1, b2, b3, player, game):
x, y = player.mouseX, player.mouseY
if b3:
if player.game_process < GameConst.WALKING_ON_THE_MAP and player.game_process >= 0:
player.game_process = GameConst.WALKING_ON_THE_MAP
elif b1:
if player.game_process < GameConst.WALKING_ON_THE_MAP and player.game_process >= 0:
player.game_process += 1
elif player.game_process == GameConst.WALKING_ON_THE_MAP:
MsgBox1 = pygame.Rect(GameConst.MsgBox1)
if MsgBox1.collidepoint(x, y):
if len(self.message_1) > 4:
self.message_1 = self.message_1[4:]
MsgBox2 = pygame.Rect(GameConst.MsgBox2)
if MsgBox2.collidepoint(x, y):
if len(self.message_2) > 4:
self.message_2 = self.message_2[4:]
MsgBox7 = pygame.Rect(GameConst.MsgBox7)
if MsgBox7.collidepoint(x, y):
if len(self.message_7) > 4:
self.message_7 = self.message_7[4:]
MsgBox8 = pygame.Rect(GameConst.MsgBox8)
if MsgBox8.collidepoint(x, y):
if len(self.message_8) > 4:
self.message_8 = self.message_8[4:]
def respond_to_mouse_motion(self, player, game):
x, y = player.mouseX, player.mouseY
if player.game_process >= GameConst.WALKING_ON_THE_MAP and player.game_process <= GameConst.HERO_ATTACKING:
MapBox = pygame.Rect(GameConst.MapBox)
if MapBox.collidepoint(x, y):
left, top, width, height = GameConst.MapBox
pygame.draw.rect(self.DISPLAYSURF, RGB.GhostWhite, (left, top, width+5, height+5), 4)
MsgBox1 = pygame.Rect(GameConst.MsgBox1)
if MsgBox1.collidepoint(x, y):
left, top, width, height = GameConst.MsgBox1
pygame.draw.rect(self.DISPLAYSURF, RGB.GhostWhite, (left, top, width+5, height+5), 4)
MsgBox2 = pygame.Rect(GameConst.MsgBox2)
if MsgBox2.collidepoint(x, y):
left, top, width, height = GameConst.MsgBox2
pygame.draw.rect(self.DISPLAYSURF, RGB.GhostWhite, (left, top, width+5, height+5), 4)
MsgBox3 = pygame.Rect(GameConst.MsgBox3)
if MsgBox3.collidepoint(x, y):
left, top, width, height = GameConst.MsgBox3
pygame.draw.rect(self.DISPLAYSURF, RGB.GhostWhite, (left, top, width+5, height+5), 4)
MsgBox4 = pygame.Rect(GameConst.MsgBox4)
if MsgBox4.collidepoint(x, y):
left, top, width, height = GameConst.MsgBox4
pygame.draw.rect(self.DISPLAYSURF, RGB.GhostWhite, (left, top, width+5, height+5), 4)
MsgBox5 = pygame.Rect(GameConst.MsgBox5)
if MsgBox5.collidepoint(x, y):
left, top, width, height = GameConst.MsgBox5
pygame.draw.rect(self.DISPLAYSURF, RGB.GhostWhite, (left, top, width+5, height+5), 4)
MsgBox6 = pygame.Rect(GameConst.MsgBox6)
if MsgBox6.collidepoint(x, y):
left, top, width, height = GameConst.MsgBox6
pygame.draw.rect(self.DISPLAYSURF, RGB.GhostWhite, (left, top, width+5, height+5), 4)
MsgBox7 = pygame.Rect(GameConst.MsgBox7)
if MsgBox7.collidepoint(x, y):
left, top, width, height = GameConst.MsgBox7
pygame.draw.rect(self.DISPLAYSURF, RGB.GhostWhite, (left, top, width+5, height+5), 4)
MsgBox8 = pygame.Rect(GameConst.MsgBox8)
if MsgBox8.collidepoint(x, y):
left, top, width, height = GameConst.MsgBox8
pygame.draw.rect(self.DISPLAYSURF, RGB.GhostWhite, (left, top, width+5, height+5), 4)
这里首先设置了一些get/set方法,作为类的property。注意property的名字不能和类成员变量的名字相同。如果要把类的成员变量做成property,可以把成员变量前面加两个下划线,以示区别。如:
self.__caption = caption
def _getcaption(self): return self.__caption
def _setcaption(self, value): self.__caption = value
caption = property(_getcaption, _setcaption)
接着用display_map() 方法把消息框显示在地图上,原理是画矩形,矩形的位置、宽高已经在GameConst模块中定义好了。在这里画了两个矩形,一个填充,一个描边。
pygame.draw.rect(self.DISPLAYSURF, RGB.Black, GameConst.MsgBox1, 0) # 0 - 填充
pygame.draw.rect(self.DISPLAYSURF, RGB.White, GameConst.MsgBox1, 1) # 1 - 线条宽度为1
接着是用print_text 在矩形框中打印消息。由于每个消息框最多容纳4行文字,所以增加了翻页功能,原理是打印时只打印前四行,鼠标在消息框上点击时将前4行文字从列表中删除。
if len(self.message_1) > 4:
self.message_1 = self.message_1[4:]
最后在respond_to_mouse_motion 方法中处理对玩家鼠标移动的响应。在这里实现了鼠标移动到某消息框上,高亮该框。原理是在游戏主循环中不断检测鼠标位置,鼠标的位置可以视为一个点,如果它与代表消息框的某个矩形碰撞,则在消息框的周围画个高亮的矩形。
x, y = player.mouseX, player.mouseY
MapBox = pygame.Rect(GameConst.MapBox)
if MapBox.collidepoint(x, y):
left, top, width, height = GameConst.MapBox
pygame.draw.rect(self.DISPLAYSURF, RGB.GhostWhite, (left, top, width+5, height+5), 4)
Player.py模块
Player.py 的代码如下:
import pygame, sys, RGB, random, GameConst, re
class Player():
def __init__(self):
self.__x = 0
self.__y = 0
self.__p = 0
self.__status = GameConst.WALKING_ON_THE_MAP
def _getstatus(self): return self.__status
def _setstatus(self, value): self.__status = value
status = property(_getstatus, _setstatus)
def _getp(self): return self.__p
def _setp(self, value): self.__p = value
game_process = property(_getp, _setp)
def _getmousex(self): return self.__x
def _setmousex(self, value): self.__x = value
mouseX = property(_getmousex, _setmousex)
def _getmousey(self): return self.__y
def _setmousey(self, value): self.__y = value
mouseY = property(_getmousey, _setmousey)
def display_attribution(self, game):
''' 在角色属性框显示英雄属性。'''
game.message_3 = []
game.message_3.append('姓名: ')
game.message_3.append('等级: ')
game.message_3.append('血量: /')
game.message_3.append('魔量: /')
game.message_3.append('攻击力: (%命中率)')
game.message_3.append('防御力: ')
def pageup_test(self, game):
''' 在角色属性框显示英雄属性。'''
game.message_1 = []
game.message_1.append('line 1: 翻页测试')
game.message_1.append('line 2: 翻页测试')
game.message_1.append('line 3: 翻页测试')
game.message_1.append('line 4: 翻页测试')
game.message_1.append('line 5: 翻页测试')
game.message_1.append('line 6: 翻页测试')
mouseX 和mouseY 用来记录鼠标的位置,game_process 和status 用来记录游戏进度和玩家的状态。
Hero.py模块
Hero.py 的代码如下:
import GameConst, random
class Hero():
def __init__(self):
self.__name = '唐小山'
def _getname(self): return self.__name
def _setname(self, value): self.__name = value
name = property(_getname, _setname)
def speaking(self, s):
return '【{}】'.format(self.name) + s
这样,这个游戏基本的框架就搭建起来了,接下来就是往模块里增加更多内容。以上示例的运行结果如下。 源代码已经上传到GitCode: class1 · master · 下唐人 / magic_tower_chapter_0 · GitCode
|