(小白)尝试用Visual Studio梳理python《植物大战僵尸》源码002 20200910
相关配置
Visual Studio:1.46.1
python:3.8
源码地址:WX:amdy-liuhao 备注:植物大战僵尸
- 今天看到的是tool.Control()里的setup_states
这里设置了一些基本参数;
def main():
game = tool.Control()
state_dict = {c.MAIN_MENU: mainmenu.Menu(),
c.GAME_VICTORY: screen.GameVictoryScreen(),
c.GAME_LOSE: screen.GameLoseScreen(),
c.LEVEL: level.Level()}
game.setup_states(state_dict, c.MAIN_MENU)
game.main()
- 先看 state_dict
这里又调用到了c.MAIN_MENU/c.GAME_VICTORY/c.GAME_LOSE/c.LEVEL等,所以我们去:from . import constants as c 里的constants看看 - 我们打开constants
__author__ = 'marble_xu'
START_LEVEL_NUM = 1
ORIGINAL_CAPTION = 'Plant VS Zombies Game'
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
SCREEN_SIZE = (SCREEN_WIDTH, SCREEN_HEIGHT)
GRID_X_LEN = 9
GRID_Y_LEN = 5
GRID_X_SIZE = 80
GRID_Y_SIZE = 100
WHITE = (255, 255, 255)
NAVYBLUE = ( 60, 60, 100)
SKY_BLUE = ( 39, 145, 251)
BLACK = ( 0, 0, 0)
LIGHTYELLOW = (234, 233, 171)
RED = (255, 0, 0)
PURPLE = (255, 0, 255)
GOLD = (255, 215, 0)
GREEN = ( 0, 255, 0)
SIZE_MULTIPLIER = 1.3
#GAME INFO DICTIONARY KEYS
CURRENT_TIME = 'current time'
LEVEL_NUM = 'level num'
#STATES FOR ENTIRE GAME
MAIN_MENU = 'main menu'
LOAD_SCREEN = 'load screen'
GAME_LOSE = 'game los'
GAME_VICTORY = 'game victory'
LEVEL = 'level'
MAIN_MENU_IMAGE = 'MainMenu'
OPTION_ADVENTURE = 'Adventure'
GAME_LOOSE_IMAGE = 'GameLoose'
GAME_VICTORY_IMAGE = 'GameVictory'
#MAP COMPONENTS
BACKGROUND_NAME = 'Background'
BACKGROUND_TYPE = 'background_type'
INIT_SUN_NAME = 'init_sun_value'
ZOMBIE_LIST = 'zombie_list'
MAP_EMPTY = 0
MAP_EXIST = 1
BACKGROUND_OFFSET_X = 220
MAP_OFFSET_X = 35
MAP_OFFSET_Y = 100
#MENUBAR
CHOOSEBAR_TYPE = 'choosebar_type'
CHOOSEBAR_STATIC = 0
CHOOSEBAR_MOVE = 1
CHOSSEBAR_BOWLING = 2
MENUBAR_BACKGROUND = 'ChooserBackground'
MOVEBAR_BACKGROUND = 'MoveBackground'
PANEL_BACKGROUND = 'PanelBackground'
START_BUTTON = 'StartButton'
CARD_POOL = 'card_pool'
MOVEBAR_CARD_FRESH_TIME = 6000
CARD_MOVE_TIME = 60
#PLANT INFO
PLANT_IMAGE_RECT = 'plant_image_rect'
CAR = 'car'
SUN = 'Sun'
SUNFLOWER = 'SunFlower'
PEASHOOTER = 'Peashooter'
SNOWPEASHOOTER = 'SnowPea'
WALLNUT = 'WallNut'
CHERRYBOMB = 'CherryBomb'
THREEPEASHOOTER = 'Threepeater'
REPEATERPEA = 'RepeaterPea'
CHOMPER = 'Chomper'
CHERRY_BOOM_IMAGE = 'Boom'
PUFFSHROOM = 'PuffShroom'
POTATOMINE = 'PotatoMine'
SQUASH = 'Squash'
SPIKEWEED = 'Spikeweed'
JALAPENO = 'Jalapeno'
SCAREDYSHROOM = 'ScaredyShroom'
SUNSHROOM = 'SunShroom'
ICESHROOM = 'IceShroom'
HYPNOSHROOM = 'HypnoShroom'
WALLNUTBOWLING = 'WallNutBowling'
REDWALLNUTBOWLING = 'RedWallNutBowling'
PLANT_HEALTH = 5
WALLNUT_HEALTH = 30
WALLNUT_CRACKED1_HEALTH = 20
WALLNUT_CRACKED2_HEALTH = 10
WALLNUT_BOWLING_DAMAGE = 10
PRODUCE_SUN_INTERVAL = 7000
FLOWER_SUN_INTERVAL = 22000
SUN_LIVE_TIME = 7000
SUN_VALUE = 25
ICE_SLOW_TIME = 2000
FREEZE_TIME = 7500
ICETRAP = 'IceTrap'
#PLANT CARD INFO
CARD_SUNFLOWER = 'card_sunflower'
CARD_PEASHOOTER = 'card_peashooter'
CARD_SNOWPEASHOOTER = 'card_snowpea'
CARD_WALLNUT = 'card_wallnut'
CARD_CHERRYBOMB = 'card_cherrybomb'
CARD_THREEPEASHOOTER = 'card_threepeashooter'
CARD_REPEATERPEA = 'card_repeaterpea'
CARD_CHOMPER = 'card_chomper'
CARD_PUFFSHROOM = 'card_puffshroom'
CARD_POTATOMINE = 'card_potatomine'
CARD_SQUASH = 'card_squash'
CARD_SPIKEWEED = 'card_spikeweed'
CARD_JALAPENO = 'card_jalapeno'
CARD_SCAREDYSHROOM = 'card_scaredyshroom'
CARD_SUNSHROOM = 'card_sunshroom'
CARD_ICESHROOM = 'card_iceshroom'
CARD_HYPNOSHROOM = 'card_hypnoshroom'
CARD_REDWALLNUT = 'card_redwallnut'
#BULLET INFO
BULLET_PEA = 'PeaNormal'
BULLET_PEA_ICE = 'PeaIce'
BULLET_MUSHROOM = 'BulletMushRoom'
BULLET_DAMAGE_NORMAL = 1
#ZOMBIE INFO
ZOMBIE_IMAGE_RECT = 'zombie_image_rect'
ZOMBIE_HEAD = 'ZombieHead'
NORMAL_ZOMBIE = 'Zombie'
CONEHEAD_ZOMBIE = 'ConeheadZombie'
BUCKETHEAD_ZOMBIE = 'BucketheadZombie'
FLAG_ZOMBIE = 'FlagZombie'
NEWSPAPER_ZOMBIE = 'NewspaperZombie'
BOOMDIE = 'BoomDie'
LOSTHEAD_HEALTH = 5
NORMAL_HEALTH = 10
FLAG_HEALTH = 15
CONEHEAD_HEALTH = 20
BUCKETHEAD_HEALTH = 30
NEWSPAPER_HEALTH = 15
ATTACK_INTERVAL = 1000
ZOMBIE_WALK_INTERVAL = 70
ZOMBIE_START_X = SCREEN_WIDTH + 50
#STATE
IDLE = 'idle'
FLY = 'fly'
EXPLODE = 'explode'
ATTACK = 'attack'
ATTACKED = 'attacked'
DIGEST = 'digest'
WALK = 'walk'
DIE = 'die'
CRY = 'cry'
FREEZE = 'freeze'
SLEEP = 'sleep'
#LEVEL STATE
CHOOSE = 'choose'
PLAY = 'play'
#BACKGROUND
BACKGROUND_DAY = 0
BACKGROUND_NIGHT = 1
可以看到,这里都是设置的一些基本参数,作者,标题,屏幕的大小,颜色,一些标准字符串,生命值,冰冻时间,僵尸各个掉血阶段的样子变化(这里有些恐怖的是:僵尸头没了,但还是活的……)
- 看完constants,回头重新看setup_states
其实就是把一些默认的参数设置给了我们的游戏;
def main():
game = tool.Control()
state_dict = {c.MAIN_MENU: mainmenu.Menu(),
c.GAME_VICTORY: screen.GameVictoryScreen(),
c.GAME_LOSE: screen.GameLoseScreen(),
c.LEVEL: level.Level()}
game.setup_states(state_dict, c.MAIN_MENU)
game.main()
这里设置的有:菜单/胜利/失败/等级几项
5.先看菜单from .state import mainmenu, screen, level
发现这里是tool和mainmenu相互调用,
import pygame as pg
from .. import tool
from .. import constants as c
class Menu(tool.State):
def __init__(self):
tool.State.__init__(self)
def startup(self, current_time, persist):
self.next = c.LEVEL
self.persist = persist
self.game_info = persist
self.setupBackground()
self.setupOption()
def setupBackground(self):
frame_rect = [80, 0, 800, 600]
self.bg_image = tool.get_image(tool.GFX[c.MAIN_MENU_IMAGE], *frame_rect)
self.bg_rect = self.bg_image.get_rect()
self.bg_rect.x = 0
self.bg_rect.y = 0
def setupOption(self):
self.option_frames = []
frame_names = [c.OPTION_ADVENTURE + '_0', c.OPTION_ADVENTURE + '_1']
frame_rect = [0, 0, 165, 77]
for name in frame_names:
self.option_frames.append(tool.get_image(tool.GFX[name], *frame_rect, c.BLACK, 1.7))
self.option_frame_index = 0
self.option_image = self.option_frames[self.option_frame_index]
self.option_rect = self.option_image.get_rect()
self.option_rect.x = 435
self.option_rect.y = 75
self.option_start = 0
self.option_timer = 0
self.option_clicked = False
def checkOptionClick(self, mouse_pos):
x, y = mouse_pos
if(x >= self.option_rect.x and x <= self.option_rect.right and
y >= self.option_rect.y and y <= self.option_rect.bottom):
self.option_clicked = True
self.option_timer = self.option_start = self.current_time
return False
def update(self, surface, current_time, mouse_pos, mouse_click):
self.current_time = self.game_info[c.CURRENT_TIME] = current_time
if not self.option_clicked:
if mouse_pos:
self.checkOptionClick(mouse_pos)
else:
if(self.current_time - self.option_timer) > 200:
self.option_frame_index += 1
if self.option_frame_index >= 2:
self.option_frame_index = 0
self.option_timer = self.current_time
self.option_image = self.option_frames[self.option_frame_index]
if(self.current_time - self.option_start) > 1300:
self.done = True
surface.blit(self.bg_image, self.bg_rect)
surface.blit(self.option_image, self.option_rect)
- 所以我们再回看tool
class State():
def __init__(self):
self.start_time = 0.0
self.current_time = 0.0
self.done = False
self.next = None
self.persist = {}
@abstractmethod
def startup(self, current_time, persist):
'''abstract method'''
def cleanup(self):
self.done = False
return self.persist
@abstractmethod
def update(self, surface, keys, current_time):
'''abstract method'''
tool.State中把开始时间,当前时间等进行了初始设置
- mainmenu.Menu()
我们在mainmenu.py中并没有找到,Menu()
发现它是继承了tool.State,所以我们再去tool.State里看看
可是整个tool里都没Menu这个方法
仔细思考,其实这个.Menu()应该就是class Menu(tool.State)这个类
是我自己想多了,毕竟前面引入的只是mainmenu.py,并没有定位到内部的class,但在下一点中,我又直接看到了screen.GameVictoryScreen(),直接调用了,说实在的,我没看明白
My Mark
- 我们看c.GAME_VICTORY: screen.GameVictoryScreen(),
这里直接调用了screen.GameVictoryScreen(),
class GameVictoryScreen(Screen):
def __init__(self):
Screen.__init__(self)
def getImageName(self):
return c.GAME_VICTORY_IMAGE
def set_next_state(self):
return c.LEVEL
这里应该是获取了胜利的一种图片名称,并且返回了一个等级名称
LEVEL = 'level'
GAME_VICTORY_IMAGE = 'GameVictory'
- 上面没什么,我们继续看main.py文件
又调用了screen.GameLoseScreen(),
跟上面差不多
class GameLoseScreen(Screen):
def __init__(self):
Screen.__init__(self)
def getImageName(self):
return c.GAME_LOOSE_IMAGE
def set_next_state(self):
return c.MAIN_MENU
获取了
GAME_LOOSE_IMAGE = 'GameLoose'
MAIN_MENU = 'main menu'
- 接下来是c.LEVEL: level.Level()
这里我还是能理解的,和第8点一样,所以还是不能理解的就是第9点
这么一来,game.setup_states(state_dict, c.MAIN_MENU)就看完了 - 接着是: game.main(),也就是tool里Control的main
def main(self):
while not self.done:
self.event_loop()
self.update()
pg.display.update()
self.clock.tick(self.fps)
print('game over')
这里的self.done应该是游戏结束的意思吧,init中设置成了False
理解的意思应该就是如果游戏没结束,就是不断的循环和更新,按照自己指定的速度去更新
- self.event_loop()
def event_loop(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.done = True
elif event.type == pg.KEYDOWN:
self.keys = pg.key.get_pressed()
elif event.type == pg.KEYUP:
self.keys = pg.key.get_pressed()
elif event.type == pg.MOUSEBUTTONDOWN:
self.mouse_pos = pg.mouse.get_pos()
self.mouse_click[0], _, self.mouse_click[1] = pg.mouse.get_pressed()
print('pos:', self.mouse_pos, ' mouse:', self.mouse_click)
这里可以看出,done确实就是游戏开始和结束的意思了
if event.type == pg.QUIT:
self.done = True
这两句是当键盘按下和松开时的记录
elif event.type == pg.KEYDOWN:
self.keys = pg.key.get_pressed()
elif event.type == pg.KEYUP:
self.keys = pg.key.get_pressed()
最后一个是记录鼠标的
elif event.type == pg.MOUSEBUTTONDOWN:
self.mouse_pos = pg.mouse.get_pos()
self.mouse_click[0], _, self.mouse_click[1] = pg.mouse.get_pressed()
print('pos:', self.mouse_pos, ' mouse:', self.mouse_click)
self.mouse_pos = pg.mouse.get_pos()是鼠标的位置
self.mouse_click[0], _, self.mouse_click[1] = pg.mouse.get_pressed()
这句话需要琢磨一下:
print(pg.mouse.get_pressed())
print('pos:', self.mouse_pos, ' mouse:', self.mouse_click)
结果:
(1, 0, 0)
pos: (351, 305) mouse: [1, 0]
问度娘查了下:
链接:https://blog.csdn.net/qq_41556318/article/details/86304810
可以理解到这里的get_pressed()返回的三个值分别是左键,滚轮和右键
而这个游戏中不需要用到滚轮,所以用_代替了,也就是没存下来
mouse: [1, 0]中代表的也就是左击
- self.update()
def update(self):
self.current_time = pg.time.get_ticks()
if self.state.done:
self.flip_state()
self.state.update(self.screen, self.current_time, self.mouse_pos, self.mouse_click)
self.mouse_pos = None
self.mouse_click[0] = False
self.mouse_click[1] = False
度娘说:
https://blog.csdn.net/qq_41556318/article/details/86305008
pg.time.get_ticks()返回的是一个:获取以毫秒为单位的时间
我的理解应该就是一个时间戳吧
-
self.clock.tick(self.fps)
发现度娘上CSDN上的文章确实多
https://blog.csdn.net/lunzi3775/article/details/78185387
这个是设置最大帧率的 -
这里就结束?感觉并没有调用生成其它的功能和界面啊~突然想起来,之前写python的时候,import的文件,其实都自动执行了,也就是执行程序可能在其它文件里。
回顾主程序接口:
import pygame as pg
from source.main import main
if __name__=='__main__':
main()
pg.quit()
这里用到了source里的main()
而这个main又作了如下的引入:
from . import tool
from . import constants as c
from .state import mainmenu, screen, level
constants 看过了,就是一些基本参数,不考虑
先看tool
import os
import json
from abc import abstractmethod
import pygame as pg
from . import constants as c
tool里好像也没有什么其它的执行文件
这里自己觉得后面还是没有的话,这个
class State():
def __init__(self):
self.start_time = 0.0
self.current_time = 0.0
self.done = False
self.next = None
self.persist = {}
@abstractmethod
def startup(self, current_time, persist):
'''abstract method'''
def cleanup(self):
self.done = False
return self.persist
@abstractmethod
def update(self, surface, keys, current_time):
'''abstract method'''
这里的@abstractmethod 值得注意,之前有大概了解过,不过不记得了,先记在这儿,回头再来看。
那接着看mainmenu
import pygame as pg
from .. import tool
from .. import constants as c
这里也没什么
screen呢
import pygame as pg
from .. import tool
from .. import constants as c
也没什么
最好的level了
import os
import json
import pygame as pg
from .. import tool
from .. import constants as c
from ..component import map, plant, zombie, menubar
有点多,关键可能还在tool,不过这里有
from ..component import map, plant, zombie, menubar
先看map
import random
import pygame as pg
from .. import tool
from .. import constants as c
这里好像也没什么
然后plant
import random
import pygame as pg
from .. import tool
from .. import constants as c
也没什么
再来zombie
import pygame as pg
from .. import tool
from .. import constants as c
依然……最后一个:menubar
import random
import pygame as pg
from .. import tool
from .. import constants as c
看来都没找到,也可能是我遗漏了,再看一下之前想到的tool里的问题吧
- from abc import abstractmethod
@abstractmethod
度娘给的答案:
https://www.cnblogs.com/idontknowthisperson/p/10090012.html
结果似懂非懂,跟我们的程序运行好像没什么关系;
琢磨了半天也没有找到问题所在,试着再梳理一遍吧
-
感觉问题应该在update()和event_loop()那,因为之前没有细致的去了解,现在了解一下
event_loop()是用于监测点击事件的,所以应该在update()上 -
update
def update(self):
self.current_time = pg.time.get_ticks()
print("update state",self.state)
if self.state.done:
self.flip_state()
self.state.update(self.screen, self.current_time, self.mouse_pos, self.mouse_click)
self.mouse_pos = None
self.mouse_click[0] = False
self.mouse_click[1] = False
测试发现,确实在这里,界面打开时,会不断更新
print(“update state”,self.state)会不断执行
当游戏没有结束 的时候,会不断更新
进入游戏主界面:
- 现在进入打开游戏功能
这里单击是能检测出单击事件,但这个界面如何切换,这个主界面又是如何设计的,还没找到对应的页面,所以接下来查找这个页面的设置。
今天就先到这里,下次继续!