本文主要是介绍[pygame] pygame设计联机对战桌游(三),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
[pygame] pygame设计联机对战桌游(4-1)
- 内容概要
- 昨日问题
- 卡牌展示器
- 0. 成果总结
- 1. 卡牌显示器
- 2. 精灵类及其容器
- 3. 事件循环
- 1. 游戏对象类
- 1.1. 卡牌Card
- 1.2. 卡堆CardHeap
- 1.3. 所有卡堆list
- 2. 处理鼠标事件
- 3. 出牌
- 4. 全屏模式(不稳定,暂时删除)
- 5. 打包发行
- 6. 全部代码
- python’新手笔记
- 1. 类的实例化
- 2. 私有与公开
- 3. 继承,方法重写
- 4. 多继承
- 5. 命名空间和作用域
本系列总目录:https://blog.csdn.net/wxlxy316/article/details/104246724
内容概要
四、分析桌游需求,制作类图,开始开发桌游(1)
昨日问题
在学习了一定的知识后——实际上是看了太久繁杂的英文教程,感到了倦怠和恶心——我决心为自己做一份甜点休憩一下:从设计简单的卡片展示器着手,先制作出一个简单的雏形来,验证自己的学习成果,再继续我的学习路径
先提几个昨日遇到的问题
python 获取某个对象的引用地址
https://blog.csdn.net/cpongo6/article/details/89249293
出现UnboundLocalError: local variable ‘a’ referenced before assignment异常的情况与解决方法
https://blog.csdn.net/DansonC/article/details/88860885
pygame.event类别汇总
https://blog.csdn.net/qq_41556318/article/details/86303039
这些问题使我感到了深深的沮丧和挫败,并且让我发现我对python对象的有关概念的理解实在是烂透了,因此我打开了菜鸟教程恶补了几天python class的知识:https://www.runoob.com/python3/python3-class.html
,关于学习这些内容时的笔记和感悟我放在文章后部了,现在,我们先讨论一下目前的工作吧!
卡牌展示器
0. 成果总结
1. 卡牌显示器
最终我厘清了一些思路,制作了一份简单的卡牌显示器,它具有如下特性:
- 定义了两个类:卡堆
CardHeap
、卡牌Card
- 卡牌若干,分布在卡堆中,卡牌和卡堆一一对应
- 卡牌拥有正反两面,可以实现单击翻面特性
- 卡牌在卡堆中一字排开,存在次序,紧邻排布(未完成)
- 可以通过鼠标调整卡牌在卡堆中的顺序,也可以调整卡牌移动到不同的卡堆中
代码和素材移步github地址:https://github.com/bridgeL/card_show
2. 精灵类及其容器
学习了pygame.sprite.Sprite
类,pygame.sprite.Group
类,pygame.sprite.OrderedUpdates
类,简单来说,
后两个类是第一个类的容器,且较前者为无序容器,较后者为有序容器,一般来说有序容器较慢,应根据实际情形使用不同的容器打包pygame.sprite.Sprite
对象
3. 事件循环
深化了对于Event循环的理解:
- clock模块,提供适当延时,以保证阵列稳定为预设值
- 处理event队列,将每一帧间积攒的多个操作,依次处理
- 获取上一帧中的精灵对象的矩阵坐标,用底图的对应部分覆盖
screen.blit(background,sprite.rect)
,对于对于组内的精灵可用group.clear(screen,background)
,会自动迭代处理每一个精灵 - 运行游戏逻辑,计算这一帧精灵对象的矩阵坐标
sprite.update()
,对于组内的精灵可用group.update()
,会自动迭代处理每一个精灵 - 绘制组内精灵对象到这一帧
group.draw(screen)
- 应用更改到显示器
pygame.display.flip()
1. 游戏对象类
1.1. 卡牌Card
2020/2/11
今日实在太匆忙,代码只做了一个雏形,也来不及写完第三期的官方教程学习笔记,这一期也漏了一些很坑的问题,我明天再来补充
卡牌类继承自pygame.sprite.Sprite类,新增属性imgh
,imgb
,id
,label
,headup
,movepos
提供方法overturn
,isclick
,move
,重写了方法update
,最值得一提的是,我在卡牌类中封装一个公共属性imgb
总所周知,一套卡牌的正面图案各不相同,但是背面图是一致的,如果我们为每个卡牌都加载两份图片(正反两面)的内存空间,那么至少有一半的内存空间是无意义消耗的,因此我为卡牌封装了一个公共属性imgb
,存放反面图片,同时设计了属性self.headup
和方法Card.overturn(self)
这个方法将在翻转卡牌时起作用,它负责将属性self.headup
翻转,同时修改self.image
指向另一面,self.imgh
或imgb
class Card(pygame.sprite.Sprite):imgb = 0def __init__(self, id, lable):super(Card, self).__init__()self.imgh, self.rect = load_img(lable+'.jpg')self.image = self.imghself.id = idself.lable = lableself.headup = Trueself.movepos = [0, 0]def update(self):if self.movepos[0] != 0 or self.movepos[1] != 0:self.rect.move_ip(self.movepos)self.movepos = [0, 0]def overturn(self):self.headup = not self.headupif self.headup:self.image = self.imghelse:self.image = Card.imgbdef isclick(self, pos):return self.rect.collidepoint(pos)def move(self, rel):self.movepos[0] += rel[0]self.movepos[1] += rel[1]
比较坑的一点在于,我在摸索这一模式时,经常遇到了
UnboundLocalError: local variable '<module_name>' referenced before assignment
类似这种错误,这是为什么呢?
原代码如下:
class Card(pygame.sprite.Sprite):imgb = pygame.image.load('data/back.jpg')def __init__(self, id, lable):super(Card, self).__init__()self.imgh, self.rect = load_img(lable+'.jpg')self.image = self.imgh......
原来,这份代码在编译class Card类时——尽管此时还没有任何一个card对象实例化——调用了还没有初始化的pygame.image模块的函数,而pygame的初始化函数在main函数里,
经修改如下:
class Card(pygame.sprite.Sprite):imgb = 0def __init__(self, id, lable):super(Card, self).__init__()self.imgh, self.rect = load_img(lable+'.jpg')self.image = self.imgh......
def cardinit():Card.imgb = pygame.image.load('data/back.jpg')...
def main():pygame.init()...cardinit()...
问题解决√√√
1.2. 卡堆CardHeap
需要设计卡堆存储一定数量的卡牌。
可以继承pygame.sprite.Group类,但没必要,pygame提供了Sprite的容器类,但是没提供Group的容器类,所以就没通过继承模式设计
class CardHeap():def __init__(self, rect):self.items = pygame.sprite.Group()self.count = 0self.area = rectdef add(self, card):print('-----------------------------', card.rect, self.area.topleft)card.rect.move_ip(self.area.topleft[0] - card.rect.topleft[0], self.area.topleft[1] - card.rect.topleft[1])print('-----------------------------', card.rect)self.items.add(card)self.count += 1def remove(self, card):self.items.remove(card)self.count -= 1def clear(self, screen, background):self.items.clear(screen, background)def update(self):self.items.update()def draw(self, screen):self.items.draw(screen)def isclick(self, pos):for card in reversed(self.items.sprites()):if card.isclick(pos):return True, cardreturn False, 0def setarea(self, rect):self.area = rectfor card in self.items.sprites():card.rect.move_ip(rect.topleft)
每个卡堆里的卡牌都被限制在卡堆的area里,不能通过鼠标拖动到其他地方
1.3. 所有卡堆list
allcardheaps = [CardHeap(pygame.Rect((50, 100), (100, 200))), CardHeap(pygame.Rect((120, 200), (200, 300))), CardHeap(pygame.Rect((30, 30), (100, 100)))]
CH = dynamic({'player0': 0, 'player1': 1, 'table': 2})
CH这种操作可以说是想了很久,实现了类似c的枚举效果
当然也有更简单粗暴的
class CH():player0 = 0player1 = 1table = 2
调用的时候是这样的
allcardheaps[CH.player0].update()
上面这句代码的效果是更新player0的卡堆的所有卡牌
关于dynamic这种类,是找到的一个大神的文章里的,他的定义的dynamic类最终实现了类似event类的效果——将字典通过属性方式调用,而传统的字典是这样的:
CH['player0'] = 0
dynamic类原文地址
https://www.jianshu.com/p/edfa99a72a02
class dynamic(dict):def __init__(self, d=None):if d is not None:for k, v in d.items():self[k] = vreturn super().__init__()def __key(self, key):return "" if key is None else key.lower()def __str__(self):import jsonreturn json.dumps(self)def __setattr__(self, key, value):self[self.__key(key)] = valuedef __getattr__(self, key):return self.get(self.__key(key))def __getitem__(self, key):return super().get(self.__key(key))def __setitem__(self, key, value):return super().__setitem__(self.__key(key), value)
2. 处理鼠标事件
pygame的鼠标处理很强大,细节面很广,譬如button属性
button | 事件 |
---|---|
1 | 鼠标左键 |
2 | 鼠标中键 |
3 | 鼠标右键 |
1 | 滚轮向上滚动 |
1 | 滚轮向下滚动 |
而event.type又有三种,对应鼠标按下、鼠标移动、鼠标抬起,十分人性化
这里我又增加了一个全局鼠标姿态记录变量mouse_gesture
MG = dynamic({'no_action': 0, 'click_l': 1, 'click_r': 2, 'move': 3})
# Initialise mouse_gesture
mouse_gesture = MG.no_action
分别枚举了鼠标无有意义动作、鼠标左键点击了卡牌、鼠标右键点击了卡牌、鼠标正在拖动卡牌四个状态
# Event loopwhile True:for event in pygame.event.get():print(event)if event.type == QUIT:returnelif event.type == MOUSEBUTTONDOWN:#如果有卡牌被选中if event.button == 1:mouse_gesture = MG.click_lbreakelif event.button == 3:mouse_gesture = MG.click_rbreakelif event.type == MOUSEMOTION:if mouse_gesture == MG.click_l:# 如果左键拖动卡牌,移动距离足够远,说明用户希望移动卡牌而不是左键单击它mouse_gesture = MG.move# do somethingelif mouse_gesture == MG.move:# do somethingelif event.type == MOUSEBUTTONUP:if mouse_gesture == MG.click_l and event.button == 1:# 用户左键单击了某张卡牌elif mouse_gesture == MG.click_r and event.button == 3:# 用户右键单击了某张卡牌mouse_gesture = MG.no_action
这里关于用户拖动操作的模拟搞了很久,一度我以为应该根据用户鼠标按住的时长来判断是否应该移动卡牌,但是这种设计手感很差,最终替换成了如果左键拖动卡牌,移动距离足够远,说明用户希望移动卡牌而不是左键单击它
3. 出牌
将player牌组中的牌移动到桌面牌组里
def moveCH2T(ch_remove, card):ch_remove.remove(card)allcardheaps[CH.table].add(card)
player0牌组中的牌移到player1中
def moveCH2CH(ch_remove, ch_add, card):ch_remove.remove(card)ch_add.add(card)
4. 全屏模式(不稳定,暂时删除)
按F1切换窗口模式/全屏模式
https://blog.csdn.net/qq_38526635/article/details/84307903
5. 打包发行
推荐使用pyinstaller
http://c.biancheng.net/view/2690.html
cmd在对应目录下执行指令
pyinstaller -F app.py
有个问题是,pyinstaller不能将图片等资源文件也打包到一个包里,因此要记得把exe文件和资源文件放到一起运行
6. 全部代码
代码和素材移步github地址:https://github.com/bridgeL/card_show
python’新手笔记
python的类相比c++的类而言,依旧是体现了python的简洁特性,但若不了解一定的规则,使用起来则会造成一定的混乱和麻烦
1. 类的实例化
c++通过和类同名的构造函数实例化类为对象,python则通过__init__()
方法实现,同样的,__init__()
函数也可以有参数
菜鸟教程里举了这样一个例子:
#!/usr/bin/python3class Complex:def __init__(self, realpart, imagpart):self.r = realpartself.i = imagpart
x = Complex(3.0, -4.5)
print(x.r, x.i) # 输出结果:3.0 -4.5
self是python里对类的一个独特处理,它代表类的实例,而非类。这句话似乎难以理解,但是站在编译器的角度思考,似乎可以很容易理解。
对于一个类来说,它的每一个对象都拥有的个人财产(相对于公共财产)是一堆变量/属性(Attributes
)name
, age
, height
, weight
,diet
…,而公共财产则是一系列的函数/方法(Functions
)get_name_length()
,judge_health()
…,当一个对象请求调用一个公共函数judge_health()
,输入自己的年龄身高体重来计算它的健康指数并指导就餐计划(diet
)时,它显然要将自己的引用地址告知公共函数,以帮助它访问和修改自己的各项属性,而这一切,仅需要在设计函数时
class Person():...def judge_health(self): self.diet = self.age...self.height...self.weight
之后函数内便可以愉快地调用self
的属性,访问或者修改它们。对象使用函数时,self
将被自动调用,如下
person = Person(...)
person.judge_health()
这相当于
person = Person(...)
Person.judge_health(person)
self
代表的是类的实例,代表当前对象的地址,self.__class__
指向类,可以利用self.__class__
进行类名检查,提高代码安全性。
在c++的类中,函数往往是复杂烦人的,你需要不厌其烦地告诉公共函数该对象的每一属性的拷贝或者地址,最终的函数往往变成这样
void judge_health(const int& age, const int& height, const int& weight, Diet& diet)
{...
}
当然,这是安全妥当的做法,但是在开发效率上…
或者是这样,
class Person
{
private:int age,height,weight;Diet diet;
public:void judge_health(){diet = age... height...weight;}
}
冒出来一些让人摸不着头脑的变量,看起来它们也许是对象的变量,也可能是全局变量…
2. 私有与公开
双下划线前缀代表私有属性,私有方法
默认为公开属性,公开方法
#!/usr/bin/python3#类定义
class people:#定义基本属性name = ''age = 0#定义私有属性,私有属性在类外部无法直接进行访问__weight = 0#定义构造方法def __init__(self,n,a,w):self.name = nself.age = aself.__weight = wdef __prt(self):print("%s 说: 我 %d 岁。" %(self.name,self.age))def speak(self):self.__prt()# 实例化类
p = people('runoob',10,30)
p.speak()
3. 继承,方法重写
派生类的定义如下所示:
class DerivedClassName(BaseClassName1):...
BaseClassName(示例中的基类名)必须与派生类定义在一个作用域内。除了类,还可以用表达式,基类定义在另一个模块中时这一点非常有用:
class DerivedClassName(modname.BaseClassName):...
python中子类与父类__init__
函数的关系:
- 子类不重写
__init__
,实例化子类时,会自动调用父类定义的__init__
- 如果重写了
__init__
时,实例化子类,就不会调用父类已经定义的__init__
,因此需要重新编写内容实例化继承自父类的属性,或者如下操作
class DerivedClassName(BaseClassName1):...def __init__(self, 参数1,参数2,....):BaseClassName1.__init__(self, 参数1,参数2,....)...
或者使用super
继承父类的构造方法
class DerivedClassName(BaseClassName1):...def __init__(self, 参数1,参数2,....):super(DerivedClassName,self).__init__(参数1,参数2,....)...
方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
#!/usr/bin/python3#类定义
class people:#定义基本属性name = ''age = 0#定义私有属性,私有属性在类外部无法直接进行访问__weight = 0#定义构造方法def __init__(self,n,a,w):self.name = nself.age = aself.__weight = wdef speak(self):print("%s 说: 我 %d 岁。" %(self.name,self.age))#单继承示例
class student(people):grade = ''def __init__(self,n,a,w,g):#调用父类的构函people.__init__(self,n,a,w)self.grade = g#覆写父类的方法def speak(self):print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))s = student('ken',10,60,3)
s.speak()
没什么好说的,代码就是教程
4. 多继承
class DerivedClassName(Base1, Base2, Base3):...
#!/usr/bin/python3#类定义
class people:#定义基本属性name = ''age = 0#定义私有属性,私有属性在类外部无法直接进行访问__weight = 0#定义构造方法def __init__(self,n,a,w):self.name = nself.age = aself.__weight = wdef speak(self):print("%s 说: 我 %d 岁。" %(self.name,self.age))#单继承示例
class student(people):grade = ''def __init__(self,n,a,w,g):#调用父类的构函people.__init__(self,n,a,w)self.grade = g#覆写父类的方法def speak(self):print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))#另一个类,多重继承之前的准备
class speaker():topic = ''name = ''def __init__(self,n,t):self.name = nself.topic = tdef speak(self):print("我叫 %s,我是一个演说家,我演讲的主题是 %s"%(self.name,self.topic))#多重继承
class sample(speaker,student):a =''def __init__(self,n,a,w,g,t):student.__init__(self,n,a,w,g)speaker.__init__(self,n,t)test = sample("Tim",25,80,4,"Python")
test.speak() #方法名相同,默认调用的是在括号中排前的父类的方法
test.speak() #方法名相同,默认调用的是在括号中排前的父类的方法
5. 命名空间和作用域
- 内置名称(built-in names), Python 语言内置的名称,比如函数名 abs、char 和异常名称 BaseException、Exception 等等。
- 全局名称(global names),模块中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。
- 局部名称(local names),函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量。(类中定义的也是)
要点:
- 命名空间查找顺序: 局部的命名空间去 -> 全局命名空间 -> 内置命名空间
- 命名空间的生命周期:取决于对象的作用域,如果对象执行完成,则该命名空间的生命周期就结束
- 相同的对象名称可以存在于多个不同的命名空间中
- 有四种作用域:L(Local,局部变量),E(Enclosing,闭包函数外的函数),G(Global,当前模块的全局变量),B(Built-in,内建的变量/关键字)
g_count = 0 # 全局作用域
def outer():o_count = 1 # 闭包函数外的函数中def inner():i_count = 2 # 局部作用域
- 局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问
- 当内部作用域想修改外部作用域的变量时,需要依靠global和nonlocal关键字
- 全局变量使用global,嵌套作用域(enclosing 作用域,非全局作用域)变量使用 nonlocal
num = 1
def fun():# global numnum = 2
fun()
print(num)
没有声明global,fun中调用的num为新建的局部变量,输出结果为1
num = 1
def fun():global numnum = 2
fun()
print(num)
输出结果为2
本系列总目录:https://blog.csdn.net/wxlxy316/article/details/104246724
这篇关于[pygame] pygame设计联机对战桌游(三)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!