我研发的Python游戏引擎Pylash已经更新到1.4了。如今我们就来使用它完毕一个极其简单的小游戏:接水果。
下面是游戏截图:
游戏操作说明:点击屏幕左右两边或者使用键盘方向键控制人物移动。使人物与水果接触得分,碰到非水果的物品,如碎玻璃。就会game over。
接下来是详尽的开发过程,篇幅较长,请看官耐心阅读。
Pylash项目地址
因为本次开发用到了pylash,大家能够先去Github上对引擎进行了解。
https://github.com/yuehaowang/pylash_engine
创建项目
首先在工作文件夹创建一个名为get_fruits
的文件夹。然后到Github下载Pylash。引擎是基于Python3和PyQt4构建的。所以在使用前请确保你使用的是Python3并且安装了PyQt4。
假设没有,能够在上述项目地址中找到他们的相关网页链接进行下载安装。安装和配置步骤都十分简单。这里不再赘述。
下载完Pylash后,我们得到这种文件夹结构:
+- pylash_engine/
|
+- pylash/
|
+- demo/
|
+- examples/
大家能够在demo/
和examples/
两个文件夹下查看演示样例。
本文的源码能够在examples/get_fruits
中找到。
pylash
文件夹就是引擎源码。
接下来把这个文件夹拷贝到我们创建的get_fruits
文件夹下,再在get_fruits
文件夹下创建一个images
文件夹。用于储存图片。
最后创建一个Main.py
文件。这时,我们的get_fruits
文件夹结构例如以下:
+- get_fruits/
|
+- pylash/
|
+- images/
|
+- Main.py
然后将引擎文件夹plash_engine/examples/get_fruits/images/
下图片拷贝到项目文件夹get_fruits/images/
下,用作游戏素材。
这样一来,我们的项目就创建好了,接下来仅仅用往Main.py
里填写代码,然后执行就可以。
编写Hello World小程序
用代码编辑器(推荐Sublime Text)打开Main.py
文件,写入下面代码:
# !/usr/bin/env python3
# -*- coding: utf-8 -*-
from pylash.utils import init, addChild
from pylash.text import TextField
def main():
# 创建文本显示对象
txt = TextField()
# 设置文本内容
txt.text = "Hello World"
# 设置文本颜色
txt.textColor = "red"
# 设置文本位置
txt.x = 50
txt.y = 100
# 设置文本字体大小
txt.size = 50
# 将文本对象增加到最底层
addChild(txt)
# 初始化窗体。參数:界面刷新时间(单位:毫秒)。窗体标题。窗体宽度。窗体高度。初始化完毕后回调函数
init(1000 / 60, "Hello World", 800, 600, main)
执行Main.py
,假设得到了例如以下图所看到的的界面,说明程序正常运转起来了。
大家能够结合凝视初步认识Pylash。熟悉flash的同学不难发现。TextField
就是flash里显示文本的类,并且使用方法十分相近。
我们从代码的第4行看起,这里我们引入了pylash中的一些函数和类。pylash提供了非常多模块。大家能够到这里查看它们的简单介绍。
再往下看。我们会发现。pylash提供了一个用于显示文本的类,通过设置这个类的不同属性来设定文本样式。最后使用addChild
将文本显示对象增加到界面中。我们能够把游戏看作分为非常多层:地图层、人物层、UI层……,通过分层我们就能实现层次化显示效果。比方人物一直是在地图上方显示的,那么人物层就在地图层上方。addChild
函数就是把一个显示对象加到最底层。
最后。我们使用init
函数初始化窗体。
Pylash提供了很多基础显示对象,除了TextField
文本显示类,还有Bitmap
图片显示类。Sprite
精灵类等。下文会提及。
编写游戏
有了上述对pylash的大致了解。我们就能够開始编写游戏了。首先,删除第四行以后全部代码。
引入所需
首先引入我们所需的全部类和函数。改动Main.py
:
from pylash.utils import stage, init, addChild, KeyCode
from pylash.system import LoadManage
from pylash.display import Sprite, BitmapData, Bitmap, FPS
from pylash.text import TextField, TextFormatWeight
from pylash.events import MouseEvent, Event, KeyboardEvent
from pylash.ui import LoadingSample1
这些类和函数在下面的代码中都会被用到。因为我是提前写好了游戏。所以在这里把这部分代码一块儿贴出来了。大家使用的时候能够依据自己使用情况,每用一个引入一个。
全局变量
游戏中须要用到一些全局变量。大家能够先浏览一遍,不同知道它们是干什么的。后文会用到它们:
dataList = {}
stageLayer = None
player = None
itemLayer = None
scoreTxt = None
addItemSpeed = 40
addItemSpeedIndex = 0
score = 0
keyboardEnabled = False
载入资源
我们的游戏中要用到图片,所以要提前载入图片(存储于images/
文件夹下)。载入图片我们用到LoadManage
静态类和LoadingSample1
进度条类(还有LoadingSample2
、LoadingSample3
这两款不相同式的进度条。或者大家深入学习后,能够自己写一个进度条类)。
改动main
函数:
def main():
# 资源列表,一个list对象,格式:{"name" : 资源名称, "path" : 资源路径}
loadList = [
{"name" : "player", "path" : "./images/player.png"},
{"name" : "bg", "path" : "./images/bg.jpg"},
{"name" : "item0", "path" : "./images/item0.png"},
{"name" : "item1", "path" : "./images/item1.png"},
{"name" : "item2", "path" : "./images/item2.png"},
{"name" : "item3", "path" : "./images/item3.png"},
{"name" : "item4", "path" : "./images/item4.png"},
{"name" : "item5", "path" : "./images/item5.png"},
{"name" : "item6", "path" : "./images/item6.png"},
{"name" : "item7", "path" : "./images/item7.png"}
]
# 创建进度条
loadingPage = LoadingSample1()
addChild(loadingPage)
# 载入完毕后调用的函数。接受一个參数,该參数是一个dict对象,通过result[资源名称]来获取载入完毕的资源
def loadComplete(result):
# 调用remove方法从界面上移除自身
loadingPage.remove()
# 调用初始化游戏函数
gameInit(result)
# 载入文件。參数:资源列表,每载入完一个资源回调函数(多用于显示运行进度)。载入全然部资源回调函数
LoadManage.load(loadList, loadingPage.setProgress, loadComplete)
上述代码含有具体凝视,理解起来应该不算困难。能够看到,我们使用LoadManage.load
实现载入。LoadingSample1.setProgress
用于设置显示运行进度。
创建開始界面
我们在main
函数中调用了gameInit
函数。所以增加该函数:
def gameInit(result):
global dataList, stageLayer
# 保存载入完毕的资源,这样一来。就能够使用dataList[资源名称]来获取载入完毕的资源
dataList = result
# 创建舞台层
stageLayer = Sprite()
addChild(stageLayer)
# 增加FPS,方便查看游戏效率
fps = FPS()
addChild(fps)
# 增加背景图片
bg = Bitmap(BitmapData(dataList["bg"]))
stageLayer.addChild(bg)
# 增加文本
titleTxt = TextField()
titleTxt.text = "Get Furit"
titleTxt.size = 70
titleTxt.textColor = "red"
titleTxt.x = (stage.width - titleTxt.width) / 2
titleTxt.y = 100
stageLayer.addChild(titleTxt)
hintTxt = TextField()
hintTxt.text = "Tap to Start the Game!~"
hintTxt.textColor = "red"
hintTxt.size = 40
hintTxt.x = (stage.width - hintTxt.width) / 2
hintTxt.y = 300
stageLayer.addChild(hintTxt)
engineTxt = TextField()
engineTxt.text = "- Powered by Pylash -"
engineTxt.textColor = "red"
engineTxt.size = 20
engineTxt.weight = TextFormatWeight.BOLD
engineTxt.italic = True
engineTxt.x = (stage.width - engineTxt.width) / 2
engineTxt.y = 500
stageLayer.addChild(engineTxt)
# 增加鼠标点击事件:点击舞台层后。開始游戏
stageLayer.addEventListener(MouseEvent.MOUSE_UP, startGame)
# 增加键盘事件:用于控制游戏中的人物
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDown)
stage.addEventListener(KeyboardEvent.KEY_UP, keyUp)
def startGame(e):
print("start game")
def keyDown(e):
print("key down")
def keyUp(e):
print("key up")
上述代码中,我们须要突破下面几个难点:
Sprite
精灵类。Sprite
是一个精灵类。但是什么是精灵?事实上你能够把它理解为一个层。它拥有addChild
方法,用于把显示对象加到自身这个层上(和全局的addChild
函数相似)。当然Sprite
不仅仅是有层的功能,只是你姑且把它看作一个层吧。Bitmap
和BitmapData
类的使用。Bitmap
在上文中提到是一个用于显示图片的类。和TextField
一样。使用addChild
将它增加界面。BitmapData
类是用于储存图像数据的,它接收的參数就是载入完毕的图片资源。将他作为參数传给
Bitmap
类的构造器。就能创建出图片。BitmapData
还能够进行像素操作。只是这是较高级的功能,眼下不用了解。事件。在pylash中。使用
addEventListener
统一接口增加事件,该方法參数:事件类型,事件监听器(即事件回调函数)。什么是事件呢?相似于一个信号。这个信号在某种情况下被发送后,指定的信号监听器就会被触发。这里增加鼠标事件的
addEventListener
是在EventDispatcher
中定义的。DisplayObject
类继承自EventDispatcher
。所以继承自DisplayObject
的全部类。都能增加事件。只是仅仅有Sprite
才有触发鼠标事件的能力。所以我们给stageLayer
(舞台层,一个Sprite
对象)增加了鼠标点击事件(MouseEvent.MOUSE_UP
)。相应addEventListener
方法的有removeEventListener
(移除事件,參数相同)。鼠标事件除了MouseEvent.MOUSE_UP
(鼠标弹起),还有MouseEvent.MOUSE_DOWN
(鼠标按下),MouseEvent.MOUSE_MOVE
(鼠标移动),MouseEvent.MOUSE_OUT
(鼠标移出)等事件。后文会用到一些。事件的监听器是一个函数,
startGame
、keyDown
、keyUp
它们都是事件监听器。监听器在事件触发时被调用,并接受一个事件数据參数(通常写为
e
),通过这个參数能够获取事件的一些信息。如鼠标事件的监听器能够通过该參数获取鼠标位置。stage
全局类。这里的
stage
是一个全局类,用于管理整个窗体。比方设置窗体刷新速度、获取窗体尺寸(stage.width。stage.height),有点相似于JavaScript里的window
。键盘事件总不能加到某个对象上吧,所以stage
还能增加键盘事件。增加键盘事件相同使用
addEventListener
这个的统一接口。
最后增加init
函数初始化窗体:
init(1000 / 60, "Get Fruits", 800, 600, main)
init
函数中。值得注意的是第一个參数,上文代码的凝视中解释的是“界面刷新时间”,也就是说我们的界面是在不断刷新重绘的。这个參数就是用来决定刷新的时间。
參数值越小,刷新得越快,游戏越流畅,只是也不用设置得太小。太小了话,刷新速度过快,设备会跟不上这个节奏的。
玩过游戏的朋友能够这么理解这个參数,用1000除以这个參数,得到的就是FPS。
执行Main.py。得到例如以下界面:
能够看到,我们的界面上有图片也有文本。点击界面输出“start game”,按下键盘输出“key down”,释放键盘输出“key up”。这样一来。我们就成功地增加了显示对象和鼠标&键盘事件。
開始游戏
舞台层鼠标点击事件的监听器是startGame
函数。也就是说。我们点击開始界面就開始游戏。改动startGame
函数:
def startGame(e):
global player, itemLayer, scoreTxt, addItemSpeedIndex, score, keyboardEnabled
# 初始一些全局变量
addItemSpeedIndex = 0
score = 0
keyboardEnabled = True
# 清空舞台层和舞台事件
stageLayer.removeAllChildren()
stageLayer.removeAllEventListeners()
# 增加背景
bg = Bitmap(BitmapData(dataList["bg"]))
stageLayer.addChild(bg)
# 创建角色
player = Player(dataList["player"])
player.x = (stage.width - player.width) / 2
player.y = 450
stageLayer.addChild(player)
# 创建下落物品层
itemLayer = Sprite()
stageLayer.addChild(itemLayer)
# 将人物对象保存到itemLayer中。用于检測碰撞
itemLayer.hitTarget = player
# 增加分数文本
scoreTxt = TextField()
scoreTxt.text = "Score: 0"
scoreTxt.textColor = "red"
scoreTxt.size = 30
scoreTxt.x = scoreTxt.y = 30
scoreTxt.weight = TextFormatWeight.BOLDER
stageLayer.addChild(scoreTxt)
# 增加事件
stageLayer.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown)
stageLayer.addEventListener(MouseEvent.MOUSE_UP, onMouseUp)
stageLayer.addEventListener(Event.ENTER_FRAME, loop)
def onMouseDown(e):
print("mouse down")
def onMouseUp(e):
print("mouse up")
def loop(e):
print("loop")
相应addChild
,Sprite
提供了removeChild
方法用于移除显示对象。除此之外还有removeAllChildren
移除全部对象方法。
removeAllEventListeners
顾名思义就是移除全部事件。
上面的代码会让人一头雾水,相同的。我们须要突破下面难关:
全局变量。
addItemSpeedIndex
是用于控制增加下落物品的时间间隔。后文会提及。score
是保存分数的变量。因为游戏開始后,这些变量要回到初始值,所以在startGame
函数中增加了这些代码来完毕这项任务。keyboardEnabled = True
这行代码是用于打开键盘事件,键盘事件是加到stage
对象上的(见上文),但是是用于操作游戏中主角的,所以仅仅有在游戏開始后才实用,所以增加keyboardEnabled
变量作为是否能使用键盘的开关,后文改动键盘事件监听器时会用到它。Player类。这个类是我们要自己创建的人物类,后文会展示其代码。
时间轴事件ENTER_FRAME。我们了解了鼠标事件,认识
MouseEvent.MOUSE_DOWN
、MouseEvent.MOUSE_UP
,但是Event.ENTER_FRAME
是什么东西-_-#?这个事件就是时间轴事件。时间轴事件相似于一个计时器。这个事件的监听器每隔段事件就会触发。事件触发的时间间隔取决于
init
函数的第一个參数。
执行代码,点击開始界面開始游戏,你能够发现控制台在不停地输出“loop”。代表时间轴事件运转了。
Player人物类
上文提到了这个类。在写这个类之前,我们又一次在get_fruits/
文件夹下创建一个名为Player.py
的python文件。
创建完毕后,打开这个文件,增加下面代码:
from pylash.utils import stage
from pylash.display import Sprite, Animation, BitmapData
# 创建Player类,并使其继承自Sprite类
class Player(Sprite):
def __init__(self, playerImage):
super(Player, self).__init__()
# 移动方向,【right向右。left向左,None不移动】
self.direction = None
# 移动速度
self.step = 5
# 创建图片数据
bmpd = BitmapData(playerImage)
# 创建动画帧列表
frames = Animation.divideUniformSizeFrames(bmpd.width, bmpd.height, 4, 4)
# 创建动画
self.animation = Animation(bmpd, frames)
# 设置动画播放速度
self.animation.speed = 5
# 播放动画
self.animation.play()
# 将动画增加界面
self.addChild(self.animation)
def loop(self):
# 向右移动
if self.direction == "right":
self.x += self.step
# 播放向右移动时的动画
self.animation.currentRow = 2
# 向左移动
elif self.direction == "left":
self.x -= self.step
# 播放向左移动时的动画
self.animation.currentRow = 1
# 不移动
else:
# 播放不移动时的动画
self.animation.currentRow = 0
# 限制人物位置
if self.x < 0:
self.x = 0
elif self.x > stage.width - self.width:
self.x = stage.width - self.width
这个Player
类须要继承自Sprite
,使其成为一个显示对象。也就是说继承自Sprite
后,就能够被addChild
到界面上去了。并能够显示出来。除此之外,还能够使用Player
对象的addChild
方法来向人物类增加显示元件。
Player
类的构造器接收一个人物图片參数。
代码中用到了Animation
类。它由pylash提供。用于创建简单的基于图片的动画。
Animation
构造器接收两个參数:动画位图数据,动画帧列表。
一般而言,我们的动画用的图片都是这种:
所以我们播放动画的时候,仅仅须要控制位图显示区域的大小和位置就能实现播放动画。
相似于放映机放映电影。例如以下两幅图所看到的。显示区域就是空白部分。不被显示的区域就是被半透明黑色幕布遮住的部分。动画中的每一个小图叫帧,移动显示区域就实现切换帧,达到播放动画的目的。
代码中的Animation.divideUniformSizeFrames(bmpd.width, bmpd.height, 4, 4)
就是用于获取每帧的位置和大小。divideUniformSizeFrames
静态方法接收四个參数:动绘图片宽度,动绘图片高度,动画列数,动画行数。该方法仅仅适合得到每帧分布和大小都是均匀的帧列表。
Animation
有个speed
属性,用于控制动画播放速度。假设不设置这个属性,动画中每帧的切换速度就和init
中设置的刷新速度一样。设置后。切换速度变为speed * 刷新速度。
Animation
类默认仅仅播放第一排第一行动画,要指定动画播放的位置,须要设置currentRow
和currentColumn
属性来控制播放的行和列。
下落物品类:Item
这个类在前面没出现过,只是我们先写好放在这里,下文要用到。相同的,新建一个名为Item.py
的文件,打开它,写入代码:
from pylash.utils import stage
from pylash.display import Sprite, Bitmap, BitmapData
class Item(Sprite):
# 定义自己定义事件
EVENT_ADD_SCORE = "event_add_score"
EVENT_GAME_OVER = "event_game_over"
def __init__(self, image):
super(Item, self).__init__()
bmp = Bitmap(BitmapData(image))
self.addChild(bmp)
self.index = 0
self.y = -bmp.height / 2
def loop(self):
player = None
# 获取人物对象
if self.parent:
player = self.parent.hitTarget
if player is None:
return
# 向下移动
self.y += 5
# 碰撞检測
if (abs(self.x + self.width / 2 - player.x - player.width / 2) <= (self.width + player.width) / 2) and (abs(self.y + self.height / 2 - player.y - player.height / 2) <= (self.height + player.height) / 2):
# 假设index <= 3,代表物品是水果
if self.index <= 3:
# 触发自己定义事件:加分事件
self.dispatchEvent(Item.EVENT_ADD_SCORE)
self.remove()
# 假设物品是非水果
else:
# 触发自己定义事件:游戏结束
self.dispatchEvent(Item.EVENT_GAME_OVER)
# 移除自身,当自身移出了屏幕
if self.y >= stage.height:
self.remove()
Item
类的构造器和Player
构造器一样,接受一个图片參数。
我们这里用到了一个比較高级的功能:自己定义事件。自定的事件能够是一个字符串。作为该事件的标识。
使用dispatchEvent
方法触发事件。dispatchEvent
方法在EventDispatcher
中定义。通过继承使Item
也能使用这种方法。
值得关注的还有检測碰撞部分。
眼下处理简单的矩形碰撞就可以。首先来看张图:
假设要横向推断碰撞的话。推断(x1-x2)的绝对值是否小于或者等于w1/2+w2/2,假设是则横向则有碰撞。纵向推断是一样的,推断(y1-y2)的绝对值是否小于或等于h1/2+h2/2就可以。
改动事件监听器
上面的代码中我们尽管增加了事件,但是没有增加有效的事件监听器,所以改动这些函数:
def keyDown(e):
global player
if not keyboardEnabled or not player:
return
if e.keyCode == KeyCode.KEY_RIGHT:
player.direction = "right"
elif e.keyCode == KeyCode.KEY_LEFT:
player.direction = "left"
def keyUp(e):
global player
if not keyboardEnabled or not player:
return
player.direction = None
def onMouseDown(e):
global player
if e.offsetX > (stage.width / 2):
player.direction = "right"
else:
player.direction = "left"
def onMouseUp(e):
global player
player.direction = None
def loop(e):
global player, itemLayer, addItemSpeed, addItemSpeedIndex
player.loop()
for o in itemLayer.childList:
o.loop()
# 控制增加下落物品时间间隔
if addItemSpeedIndex < addItemSpeed:
addItemSpeedIndex += 1
return
addItemSpeedIndex = 0
# 获得随机下落物品
randomNum = random.randint(0, 7)
# 增加下落物品
item = Item(dataList["item" + str(randomNum)])
item.index = randomNum
item.x = int(random.randint(30, stage.width - 100))
itemLayer.addChild(item)
# 增加自己定义的事件
item.addEventListener(Item.EVENT_ADD_SCORE, addScore)
item.addEventListener(Item.EVENT_GAME_OVER, gameOver)
keyDown
、keyUp
、onMouseDown
、onMouseUp
这四个监听器用于操作人物(player
)。
接下来看监听器loop
。
该函数中,首先调用了人物的loop
方法(见Player
类的loop
)。
我们在上文定义的itemLayer
是一个Sprite
对象,Sprite
对象有一个childList
属性,是一个list
对象,保存了全部的子对象。所以我们通过遍历itemLayer
的这个列表,获取每一个下落物品,调用它们的loop
方法。
接下来使用addItemSpeedIndex
和addItemSpeed
两个全局变量控制增加下落物品的速度。
接下来的代码就是来构造Item
类创建下落物品。
加分和Game Over
我们给Item
对象增加了自己定义事件,分别触发addScore
和gameOver
监听器,增加这两个监听器:
def addScore(e):
global score, scoreTxt
score += 1
scoreTxt.text = "Score: %s" % score
def gameOver(e):
global player, scoreTxt, stageLayer, keyboardEnabled
keyboardEnabled = False
stageLayer.removeAllEventListeners()
scoreTxt.remove()
player.animation.stop()
resultTxt = TextField()
resultTxt.text = "Final Score: %s" % score
resultTxt.size = 40
resultTxt.weight = TextFormatWeight.BOLD
resultTxt.textColor = "orangered"
resultTxt.x = (stage.width - resultTxt.width) / 2
resultTxt.y = 250
stageLayer.addChild(resultTxt)
hintTxt = TextField()
hintTxt.text = "Double Click to Restart"
hintTxt.size = 35
hintTxt.textColor = "red"
hintTxt.x = (stage.width - hintTxt.width) / 2
hintTxt.y = 320
stageLayer.addChild(hintTxt)
# 增加双击事件,点击后又一次開始游戏
stageLayer.addEventListener(MouseEvent.DOUBLE_CLICK, startGame)
执行Main.py
,開始游戏后。得到本文开篇图片所看到的效果。移动人物,接触下落的物品。假设碰到碎玻璃等非水果物品就会game over:
Ok,我们的接水果小游戏就完毕了。可见使用python+pylash开发小游戏还是非常方便的。
源码
本文的源码能够在引擎文件夹的examples/get_fruits
中找到。或者到这里在线查看。
文中有不论什么不妥之处或者读者有疑问的话。欢迎大家交流~
欢迎大家继续关注我的博客
转载请注明出处:Yorhom’s Game Box