因为明天回学校了,所以项目也暂时不会动弹什么了,值得庆幸的是,总算在走之前初步写完了整个项目,虽然bug遗留下不少……嘛嘛,反正呢,这篇也就是类似于第一部大结局般的存在。
这篇讨论的内容是save和load功能。事先说明,这部分代码赶工出来的,并且我对这部分内容没有一点积累,代码质量很糟糕,并且由于没有大量的测试,也不好说会在哪里出现问题。事实上,就我测试的结果来看,有可能会出现index没法找到的情况,原因尚不明朗。站在现在的角度来看,使用状态机或许能解决不少逻辑混乱的问题,遗憾的是我这里没时间了……
言归正传,我这里谈谈自己的思路,请随意指出我的疏漏、不足和错误,如果有更好的解决思路和方法我会相当感激的。
一般来说,不管是save还是load,都是需要切换界面的。老实说,切换界面这个行为也就是把新界面的图片覆盖在屏幕上就好,真不太费事儿。但是要发生切换这个动作,就需要按下按钮不是么,所以首先从按钮开始。
这里给出我这里的效果图……如你所见,右边那两个丑丑的按钮就是了,当然为了完成这个目标,位置要反复调试好,且不多说了,代码很容易。其实也就是NodeItem每次渲染的时候多渲染两个按钮就好了……
def __initSettingButtons(self): dic_settingButtons = {} dic_settingButtons['save'] = Button((\ (SAVEBUTTONPOSX,SAVEBUTTONPOSY)),\ SETTINGBUTTONSIZE,'SLButton.png',os.path.join('FONT','hksn.ttf'),\ 'save',None,15) dic_settingButtons['load'] = Button((\ (LOADBUTTONPOSX,LOADBUTTONPOSY)),\ SETTINGBUTTONSIZE,'SLButton.png',os.path.join('FONT','hksn.ttf'),\ 'load',None,15) return dic_settingButtons
上面是关键代码,前因后果我都舍掉了。反正也就是每帧多绘制俩按钮。
然后是切换,我考虑了一下是否需要把触发函数写成一个抽象函数放在按钮超类中,子类化的时候再去完成这个触发函数。但是最后我懒得这样做,因为感觉挺麻烦的……好吧,实际上我没有去考证过这种写法是否会更好……
切换的时候,新的surface覆盖了当前的surface,就成了所谓的存/载档界面。我这里大致是这样的(渣设计):
当然我这个是最终完成的效果。一点一点说。
除了最下面的背景之外,其余的都是按钮,事实上,先前那个按钮类工作得很好。中间三个方形物体就是存/载档位置,原本都是如同第三个般的白色,但是由于有记录,所以加载了图片,这个技巧等会讨论。最左下角是退出按钮,点击会回到主界面。值得一提的是,这货是我自己画的……
先讨论如何进行主界面和存取界面的切换。
这里实质上发生了一个控制流的改变----貌似一下子高端了?简单说就是,本来鼠标点击应该是单纯进行帧切换的,但是点击了存取按钮之后,鼠标点击的效果也就改变了,帧切换也不再发生。
顺便说一句,可不是单击按钮之后覆盖图片就不管了这么简单哦。因为事实上在进行着1秒8帧的刷新,所以,单纯的覆盖的话,很快又会被重新覆盖掉。写到这里,我突然想到,貌似对galgame使用帧刷新是个败笔?毕竟不需要像一般游戏那样有运动的事物不是么,因为增加了刷新反倒很复杂?……
额,以后再研究这个问题,现在先无视掉。
回到上面,我不知道你们会怎么处理这个问题(刚切换的新界面被重新覆盖),我这里用了一个全局变量来freeze了刷新这个行为,这样。
然后是save行为和load行为,虽然我很想分开讨论,但是毕竟这两家伙的行为有太多相似之处,所以还是写在一个函数就好。但是还不止是这样,由于游戏是事件驱动的,所以还有一个一般的行为……这个很难详细解释,但是试验的结果就是这样。为了区分这三种状态,函数需要有一个特别的值:flag去区分到底当前是哪种状态。
如同这样:
if dict_settingButtons['save'].is_over(click_point):
freeze = True
button_settings(surface,click_point,frame,'save')
elif dict_settingButtons['load'].is_over(click_point):
freeze = True
button_settings(surface,click_point,frame,'load')
elif freeze:
button_settings(surface,click_point,frame)
def button_settings(surface,pos,Frame,flag=''):
上面的就是函数的定义和使用,不必深究每一行代码。
上面也说了,save和load存在某些通用的行为,具体来说,就是绘制背景和相关按钮,这部分就可以作为一般行为,save和load各自的行为使用if判断标识符就好了。
现在说一下save的特有行为。
save状态下,点击三个按钮中的任意一个,都会在按钮上覆盖当前帧的缩略图,并在本地保存当前帧的信息,比如图片,音乐,index之类的。
这里需要讨论一下,事实上,不光需要保存帧信息,还需要保存两个特殊的数据:数据被绑定在哪个按钮上,和当前的缩略图。
我借用了parser的一些方法,把被绑定按钮的index看作index来解析,缩略图看成background来解析,帧信息放在一个元组中,用pickle进行序列化。这样。然后save文件大致就是这个样子:
0
[background='0.png']
(I19
S'M2.png'
p0
S'BGM/2-10.ogg'
p1
(dp2
tp3
.
具体的也请参看源代码。
load实质上就是一个解包的过程,虽然这里说得很轻巧,但是如何引出数据是个很麻烦的问题,一把辛酸泪。最后还是用全局变量来弄了一遍,代码就很难看了。
总而言之,具体的思路就是这样,有兴趣参看源代码就好。需要注意的是,逻辑事实上比较乱……倒是,自己重新写反倒比较好理解?
## 这个函数负责解析出save文件,注意,save文件位置和名字都不能更改## 大致来说,就是借用了parser类## 使用parser的split方法,还有parser方法## 最后返回两个字典。## 两个字典的key都是按钮的index,三个按钮中,第一个index是0,## 以此类推## 第一个字典的value是载入好转化好的缩略图## 第二个字典的value是一个元组,第一项是图片名称## 第二项是指定的帧的index,供读取跳转def savefile_parser(): fullname = os.path.join('SAVE','save.dat') saveInfo = save_parser.split(fullname) saveInfos = [] ##saveInfos = [save_parser.parser(i).getSaveData() for i in saveInfo] try: for i in saveInfo: save_parser.parser(i) saveInfos.append(save_parser.getSaveData(i)) ## i->like this:(0,'xxx.jpg,25) ## saveSurfaces composed like this: ## [(0,),(1,...)] saveSurfaces = [(i[0],pygame.transform.scale(pygame.image.load(os.path.join('SAVE',i[1])).convert(),(200,200))) for i in saveInfos] dict_item = {} dict_name = {} for i in saveSurfaces: ## dict_item's value is an ## instance of Surface class dict_item[i[0]] = i[1] for i in saveInfos: ## dict_name is a dict ## like this {0:('xxx.jpg',25,xxxxxxxx)} dict_name[i[0]] = (i[1],i[2]) except AttributeError: dict_item = {} dict_name = {} return (dict_item,dict_name)## 负责把应当储存的数据写到磁盘中def format_save_data(saveDict,savefile): return '\n\n'.join([str(i[0])+'\n'+'''[background='''+"'"+ \ str(i[1][0])+"']" + '\n' \ + str(i[1][1]) for i in saveDict.items()])## 这个是主要的函数,负责界面渲染什么的## save和load的处理也在里面def button_settings(surface,pos,Frame,flag=''): global freeze global g_flag global now_frame global load_data ##save_image_surface = pygame.image.load(save_image).convert() ##save_image_surface = pygame.transform.scale(save_image_surface,(200,200)) ## need updating? ## 判断是处于哪种状态 if flag: now_frame = surface.copy() now_frame = pygame.transform.scale(now_frame,(200,200)) g_flag = flag ## init the setting screen ## 绘制背景 fullname = os.path.join('SYSTEM','SL.jpg') SLImage = pygame.image.load(fullname).convert() SLImage = pygame.transform.scale(SLImage,SIZE) surface.blit(SLImage,(0,0)) ## 绘制exit按钮 button_exit = NodeItems.Button(EXIT_POS,EXIT_SIZE,os.path.join('SYSTEM','exit.png'),os.path.join('FONT','hksn.ttf')) button_exit.render(surface) ## 绘制白色的储存按钮 white_buttons_pos = [150,400,650] white_buttons = [NodeItems.Button((i,300),(200,200),os.path.join('SYSTEM','white.png'),os.path.join('FONT','hksn.ttf')) for i in white_buttons_pos] for i in white_buttons: i.render(surface) ## get save data dict_items , dict_names = savefile_parser() ## 把缩略图放到应该的按钮上面 if dict_items and dict_names: for i in dict_items: surface.blit(dict_items[i],(50+250*i,200)) ##surface.blit(save_image_surface,(50,200)) ## save behavior ## save主要是对应该保存的信息进行获取和保存 if g_flag == 'save': for (count,i) in enumerate(white_buttons): if i.is_over(pos): ## Let screenshoting image be displayed screen.blit(now_frame,(50+250*count,200)) ## Save it pygame.image.save(now_frame,os.path.join('SAVE',str(count)+'.png')) ## Write it to dict pickle_elements = (Frame.getNodeIndex(),Frame.getBackground(),Frame.getBGM(),Frame.getPortraits()) pickled_data = pickle.dumps(pickle_elements) ## storage pickled data dict_names[count] = (str(count)+'.png',pickled_data) ## Update local datafile savefile = open(os.path.join('SAVE','save.dat'),'w') ## Write data savefile.write(format_save_data(dict_names,savefile)) savefile.close() break ## load去获取对应的数据,用一个叫load_data的全局变量传出去 ## 并切换回主界面 elif g_flag == 'load': for (count,i) in enumerate(white_buttons): if i.is_over(pos): next_data = dict_names[count][1] ## To be sure it is None load_data = None load_data = next_data freeze = False break ## To exit if button_exit.is_over(pos): freeze = Falsewhile True: for event in pygame.event.get(): if event.type == QUIT: exit() if event.type == MOUSEBUTTONDOWN: ## check where the click point on click_point = event.dict['pos'] surface = nodeItem.getScreen() frame = nodeItem ## ugly codes...who can tell me ## how to write these codes well? ## What I wrote were fucking complex ## 点击save时,冻结刷新操作,flag置为save if dict_settingButtons['save'].is_over(click_point): freeze = True button_settings(surface,click_point,frame,'save') ## 同上 elif dict_settingButtons['load'].is_over(click_point): freeze = True button_settings(surface,click_point,frame,'load') ## 正常状态 elif freeze: button_settings(surface,click_point,frame) if load_data: continue else: if nodeItem.getChoiceButtons(): dict_buttons = nodeItem.getChoiceButtons() ##Through over which button been clicked ##Surily if the point out of any button's ##area,we make it freeze :-) for key in dict_buttons.keys(): ## each dict_button is a tuple, ## like this (index,button) ## index is the next Node index ## to change the control stream. ## button is a instance of Button index = int(dict_buttons[key][0]) button = dict_buttons[key][1] if button.is_over(click_point): nodeItem.setNextIndex(index) break else: ## pick = pickle.Pickler(f) ## pick.dump((nodeItem.getBGM(),nodeItem.getBackground(),nodeItem.getPortraits())) ## 对load_data反序列化,获取值,使用parser类把值传入nodeitem ## 再更新 if load_data: pickle_data = pickle.loads(load_data) load_data = None load_index,load_bg,load_bgm,load_portr = pickle_data parser.setNodeIndex(load_index-1) parser.setBackground(os.path.basename(load_bg)) parser.setBGM(os.path.basename(load_bgm)) parser.setPortrait(load_portr) nodeItem.update(parser) nodeItem.setNextIndex() ##The following codes update screen if not freeze: NextIndex = nodeItem.getNextIndex() ##get key of one Node try: Node = dirNode[NextIndex] ##get a Node which is a string parser.parser(Node) nodeItem.update(parser) except KeyError: print 'Cannot search the index:',NextIndex Node = dirNode[ERRORINDEX] ## freeze the inscreasing of index parser.parser(Node) nodeItem.update(parser) else: pass
基本就是这样,没什么好说的了,拖拖拉拉的,把最后一点东西也弄完了。回头来看,这不算什么大项目,但是基本是我一个人独立完成的最大的一个了,写到这里还是颇有那么一点唏嘘的。站在现在的角度,发现其实很多都可以用更好的方法去解决的(或许)。不过完成就好不是么?总结的话:这玩意挺好玩的~~~
最后,恩,话说galgame最大的问题果然还是剧本,画师和音乐来着……