Python开发接水果小游戏
来源:互联网 发布:我知天下之中央 编辑:程序博客网 时间:2024/05/02 04:56
我研发的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, addChildfrom pylash.text import TextFielddef 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, KeyCodefrom pylash.system import LoadManagefrom pylash.display import Sprite, BitmapData, Bitmap, FPSfrom pylash.text import TextField, TextFormatWeightfrom pylash.events import MouseEvent, Event, KeyboardEventfrom pylash.ui import LoadingSample1
这些类和函数在下面的代码中都会被用到。由于我是提前写好了游戏,所以在这里把这部分代码一块儿贴出来了,大家使用的时候可以根据自己使用情况,每用一个引入一个。
全局变量
游戏中需要用到一些全局变量,大家可以先浏览一遍,不同知道它们是干什么的,后文会用到它们:
dataList = {}stageLayer = Noneplayer = NoneitemLayer = NonescoreTxt = NoneaddItemSpeed = 40addItemSpeedIndex = 0score = 0keyboardEnabled = 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 stagefrom 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 stagefrom pylash.display import Sprite, Bitmap, BitmapDataclass 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 = Nonedef onMouseDown(e): global player if e.offsetX > (stage.width / 2): player.direction = "right" else: player.direction = "left"def onMouseUp(e): global player player.direction = Nonedef 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" % scoredef 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
http://blog.csdn.net/yorhomwang
- Python开发接水果小游戏
- Pygame开发Python小游戏
- Python 开发接豆人小游戏 TurnipBit
- python模仿开发贪食蛇小游戏历程
- python开发的2048的小游戏
- 我的第一款Html5小游戏《接水果》,防android平台一款应用
- 实例 26 买水果的小游戏
- 小游戏开发
- 开发切水果游戏
- Unity学习笔记-切水果小游戏第一发
- 微信HTML5小游戏之水果忍者
- Python小游戏--扫雷
- 扫雷小游戏-python
- 一个python 小游戏
- python小游戏实现代码
- 用Python做小游戏
- Python小游戏--扫雷
- Python 猜数字 小游戏
- 1053. Path of Equal Weight (30)
- 慕课网【HTML+CSS】3.13前学习内容汇总
- java学习之(一) 基本数据及语法
- ZOJ-3490-String Successor【8th浙江省赛】【模拟】
- netty在游戏服务器开发中的应用(一)
- Python开发接水果小游戏
- 使用python实现打印所有100以内的所有质数
- 《leetCode》:Evaluate Reverse Polish Notation
- poj3235 Fence Repair 优先队列 哈弗曼树
- Loadrunner监控win10服务器资源(设置)
- 左值 右值
- Mybatis第三弹
- HDOJ 1166 敌兵布阵(一维树状数组)
- 成熟--到底是什么样子的?