开发工具

Python版本: 3.7.4

相关模块:

pygame模块;

以及一些python自带的模块。

环境搭建

安装Python并添加到环境变量,pip安装需要的相关模块即可。

原理简介

首先,我们打开原版游戏的开始界面,发现是这样的:

具体而言,我们的思路是先定义一个按钮类,来模拟原始游戏中的“开始游戏”,“游戏说明”和“离开游戏”这三个按键的功能:

'''按钮类'''
class Button(pygame.sprite.Sprite):def __init__(self, text, fontpath, fontsize, position, color_selected=(255, 0, 0), color_default=(255, 255, 255)):pygame.sprite.Sprite.__init__(self)self.text = textself.color_selected = color_selectedself.color_default = color_defaultself.font = pygame.font.Font(fontpath, fontsize)self.font_render = self.font.render(text, True, color_default)self.rect = self.font_render.get_rect()self.rect.center = position'''更新函数: 不断地更新检测鼠标是否在按钮上'''def update(self):mouse_pos = pygame.mouse.get_pos()if self.rect.collidepoint(mouse_pos):self.font_render = self.font.render(self.text, True, self.color_selected)else:self.font_render = self.font.render(self.text, True, self.color_default)'''绑定到屏幕上'''def draw(self, screen):screen.blit(self.font_render, self.rect)
复制代码

主要的原理就是不断检测鼠标是否在对应的按钮区域,如果在,则对按钮的颜色做出改变(这里是变成红色),否则按钮使用默认的颜色(这里是白色),以此来向玩家表明这是可点击的按钮。

然后,我们来实例化它三次来添加这三个按钮到游戏的开始界面中:

'''游戏开始界面'''
class StartGameInterface():def __init__(self, cfg):self.cfg = cfgself.play_btn = Button('开始游戏', cfg.FONTPATH_CN, 50, (cfg.SCREENSIZE[0]//2, cfg.SCREENSIZE[1] - 400))self.intro_btn = Button('游戏说明', cfg.FONTPATH_CN, 50, (cfg.SCREENSIZE[0]//2, cfg.SCREENSIZE[1] - 300))self.quit_btn = Button('离开游戏', cfg.FONTPATH_CN, 50, (cfg.SCREENSIZE[0]//2, cfg.SCREENSIZE[1] - 200))'''外部调用'''def run(self, screen):# 魔塔font = pygame.font.Font(self.cfg.FONTPATH_CN, 80)font_render_cn = font.render('魔塔', True, (255, 255, 255))rect_cn = font_render_cn.get_rect()rect_cn.center = self.cfg.SCREENSIZE[0] // 2, 200# Magic Towerfont = pygame.font.Font(self.cfg.FONTPATH_EN, 80)font_render_en = font.render('Magic Tower', True, (255, 255, 255))rect_en = font_render_en.get_rect()rect_en.center = self.cfg.SCREENSIZE[0] // 2, 350# (Ver 1.12)font = pygame.font.Font(self.cfg.FONTPATH_CN, 40)font_render_version = font.render('(Ver 1.12)', True, (255, 255, 255))rect_ver = font_render_version.get_rect()rect_ver.center = self.cfg.SCREENSIZE[0] // 2, 400clock = pygame.time.Clock()while True:screen.fill((0, 0, 0))for event in pygame.event.get():if event.type == pygame.QUIT:pygame.quit()sys.exit(0)elif event.type == pygame.MOUSEBUTTONDOWN:if event.button == 1:mouse_pos = pygame.mouse.get_pos()if self.play_btn.rect.collidepoint(mouse_pos):return Trueelif self.quit_btn.rect.collidepoint(mouse_pos):pygame.quit()sys.exit(0)elif self.intro_btn.rect.collidepoint(mouse_pos):self.showgameintro(screen)for btn in [self.intro_btn, self.play_btn, self.quit_btn]:btn.update()btn.draw(screen)for fr, rect in zip([font_render_cn, font_render_en, font_render_version], [rect_cn, rect_en, rect_ver]):screen.blit(fr, rect)pygame.display.flip()clock.tick(self.cfg.FPS)'''显示游戏简介'''def showgameintro(self, screen):font = pygame.font.Font(self.cfg.FONTPATH_CN, 20)font_renders = [font.render('魔塔小游戏.', True, (255, 255, 255)),font.render('游戏素材来自: http://www.4399.com/flash/1749_1.htm.', True, (255, 255, 255)),font.render('游戏背景故事为公主被大魔王抓走, 需要勇士前往魔塔将其救出.', True, (255, 255, 255)),font.render('python工程狮.', True, (255, 255, 255)),font.render('微信公众号: python工程狮.', True, (255, 255, 255)),font.render('版权所有.', True, (255, 255, 255)),]rects = [fr.get_rect() for fr in font_renders]for idx, rect in enumerate(rects):rect.center = self.cfg.SCREENSIZE[0] // 2, 50 * idx + 100clock = pygame.time.Clock()while True:screen.fill((0, 0, 0))for event in pygame.event.get():if event.type == pygame.QUIT:pygame.quit()sys.exit(0)elif event.type == pygame.MOUSEBUTTONDOWN:if event.button == 1:mouse_pos = pygame.mouse.get_pos()if self.play_btn.rect.collidepoint(mouse_pos):return Trueelif self.quit_btn.rect.collidepoint(mouse_pos):pygame.quit()sys.exit(0)elif self.intro_btn.rect.collidepoint(mouse_pos):returnfor btn in [self.intro_btn, self.play_btn, self.quit_btn]:btn.update()btn.draw(screen)for fr, rect in zip(font_renders, rects):screen.blit(fr, rect)pygame.display.flip()clock.tick(self.cfg.FPS)
复制代码

其他额外的代码主要是显示游戏的标题等信息,都很简单,就不详细讨论了,会pygame的小伙伴肯定都能写出来

接下来,我们来看下游戏开始之后的界面是长什么样子的

具体而言,我们可以先在文本文件里定义游戏地图的样子,类似下图所示这样子,其中每个数字代表一种游戏元素:

游戏中的图片素材我也已经收集到了网上找到的别人整理好的游戏素材:

于是,我们可以写一个游戏地图文件的解析类,就像这样:

'''游戏地图解析类'''
class MapParser():def __init__(self, blocksize, filepath, element_images, offset=(0, 0), **kwargs):self.count = 0self.switch_times = 15self.image_pointer = 0self.offset = offsetself.blocksize = blocksizeself.element_images = element_imagesself.map_matrix = self.parse(filepath)'''解析'''def parse(self, filepath):map_matrix = []with open(filepath, 'r') as fp:for line in fp.readlines():line = line.strip()if not line: continuemap_matrix.append([c.strip() for c in line.split(',')])return map_matrix'''将游戏地图画到屏幕上'''def draw(self, screen):self.count += 1if self.count == self.switch_times:self.count = 0self.image_pointer = int(not self.image_pointer)for row_idx, row in enumerate(self.map_matrix):for col_idx, elem in enumerate(row):position = col_idx * self.blocksize + self.offset[0], row_idx * self.blocksize + self.offset[1]if elem+'.png' in self.element_images:image = self.element_images[elem+'.png'][self.image_pointer]image = pygame.transform.scale(image, (self.blocksize, self.blocksize))screen.blit(image, position)
复制代码

其中parse函数其实就是读取存放游戏地图信息的文本文件,然后draw函数其实就是根据读取的地图信息,将对应的游戏元素图片绑定到地图上进行显示。另外,image_pointer,switch_times,count这三个变量是为了实现原版地图中场景元素闪烁的效果,就像这样:

根据这个原理,我们可以轻松地画出魔塔所有层中的原始地图,定义的游戏地图文件如下图所示:

效果如下图所示:

ok,总结一下,就是这期主要实现了魔塔游戏中每一层的初始画面

我们实现了游戏的基础画面定义,类似这样:

细心的小伙伴肯定发现了,地图里怎么没有我们的勇士呢?没有他我们还怎么去拯救公主呀~别急,这期就带大家来实现这部分内容。

首先,我们来定义一下我们的英雄勇士类:

'''定义我们的主角勇士'''
class Hero(pygame.sprite.Sprite):def __init__(self, imagepaths, blocksize, position, fontpath=None):pygame.sprite.Sprite.__init__(self)# 设置基础属性self.font = pygame.font.Font(fontpath, 40)# 加载对应的图片self.images = {}for key, value in imagepaths.items():self.images[key] = pygame.transform.scale(pygame.image.load(value), (blocksize, blocksize))self.image = self.images['down']self.rect = self.image.get_rect()self.rect.left, self.rect.top = position# 设置等级等信息self.level = 1self.life_value = 1000self.attack_power = 10self.defense_power = 10self.num_coins = 0self.experience = 0self.num_yellow_keys = 0self.num_purple_keys = 0self.num_red_keys = 0'''将勇士绑定到屏幕上'''def draw(self, screen):screen.blit(self.image, self.rect)
复制代码

将其绑定到游戏主界面之后的效果如下:

看起来是不是哪里不对?没错,左边原来有文字显示勇士当前的状态呀!现在都没了!不过没关系,问题不大,我们写几行代码将英雄的信息显示在左边的面板上面即可:

font_renders = [self.font.render(str(self.level), True, (255, 255, 255)),self.font.render(str(self.life_value), True, (255, 255, 255)),self.font.render(str(self.attack_power), True, (255, 255, 255)),self.font.render(str(self.defense_power), True, (255, 255, 255)),self.font.render(str(self.num_coins), True, (255, 255, 255)),self.font.render(str(self.experience), True, (255, 255, 255)),self.font.render(str(self.num_yellow_keys), True, (255, 255, 255)),self.font.render(str(self.num_purple_keys), True, (255, 255, 255)),self.font.render(str(self.num_red_keys), True, (255, 255, 255)),
]
rects = [fr.get_rect() for fr in font_renders]
rects[0].topleft = (160, 80)
for idx in range(1, 6):rects[idx].topleft = 160, 127 + 42 * (idx - 1)
for idx in range(6, 9):rects[idx].topleft = 160, 364 + 55 * (idx - 6)
for fr, rect in zip(font_renders, rects):screen.blit(fr, rect)
复制代码

效果是这样子的:

完成了勇士类最基础的定义,接下来我们就该让他动起来啦,具体而言,我们先实现一个勇士行动的类函数:

'''行动'''
def move(self, direction):assert direction in self.imagesself.image = self.images[direction]move_vector = {'left': (-self.blocksize, 0),'right': (self.blocksize, 0),'up': (0, -self.blocksize),'down': (0, self.blocksize),}[direction]self.rect.left += move_vector[0]self.rect.top += move_vector[1]
复制代码

然后写个按键检测,并根据玩家按下的键值来决定勇士的行动方向:

key_pressed = pygame.key.get_pressed()
if key_pressed[pygame.K_w] or key_pressed[pygame.K_UP]:self.hero.move('up')
elif key_pressed[pygame.K_s] or key_pressed[pygame.K_DOWN]:self.hero.move('down')
elif key_pressed[pygame.K_a] or key_pressed[pygame.K_LEFT]:self.hero.move('left')
elif key_pressed[pygame.K_d] or key_pressed[pygame.K_RIGHT]:self.hero.move('right')
复制代码

如果你觉得我上面的代码写的没问题,大功告成了,这样写是有两个问题的。

首先,这样子写会导致玩家按一次上键,勇士就移动很多格,导致玩家不好控制勇士的位置,此时我们可以添加一个行动冷却变量:

# 行动冷却
self.move_cooling_count = 0
self.move_cooling_time = 5
self.freeze_move_flag = False
复制代码

在冷却中的时候进行计数:

if self.freeze_move_flag:self.move_cooling_count += 1if self.move_cooling_count > self.move_cooling_time:self.move_cooling_count = 0self.freeze_move_flag = False
复制代码

计数完成后英雄方可恢复行动能力。于是move可以重写成:

'''行动'''
def move(self, direction):if self.freeze_move_flag: returnassert direction in self.imagesself.image = self.images[direction]move_vector = {'left': (-self.blocksize, 0),'right': (self.blocksize, 0),'up': (0, -self.blocksize),'down': (0, self.blocksize),}[direction]self.rect.left += move_vector[0]self.rect.top += move_vector[1]self.freeze_move_flag = True
复制代码

感兴趣的小伙伴可以自行去掉这段代码实际感受一下键盘操作我们的勇士时是否会存在区别。

另外一个问题,也是最严重的问题,那就是行动会不合法,比如勇士会出现在这样的位置:

因此,我们需要再添加额外的移动是否合法的判断:

'''行动'''
def move(self, direction, map_parser):if self.freeze_move_flag: returnassert direction in self.imagesself.image = self.images[direction]move_vector = {'left': (-1, 0), 'right': (1, 0), 'up': (0, -1), 'down': (0, 1)}[direction]block_position = self.block_position[0] + move_vector[0], self.block_position[1] + move_vector[1]if block_position[0] >= 0 and block_position[0] < map_parser.map_size[1] and \block_position[1] >= 0 and block_position[1] < map_parser.map_size[0]:if map_parser.map_matrix[block_position[1]][block_position[0]] in ['0']:self.block_position = block_positionelif map_parser.map_matrix[block_position[1]][block_position[0]] in ['24']:self.dealcollideevent(elem=map_parser.map_matrix[block_position[1]][block_position[0]],block_position=block_position,map_parser=map_parser,)self.rect.left, self.rect.top = self.block_position[0] * self.blocksize + self.offset[0], self.block_position[1] * self.blocksize + self.offset[1]self.freeze_move_flag = True
复制代码

这里,为了方便判断,我们将原来采用的像素坐标改成了游戏地图中的元素块坐标(即上一期设计的游戏地图里,每个数字在地图矩阵中的位置索引)。另外,这里我们还需要想到的一个点是未来进一步复现游戏的过程中,我们需要在勇士和地图中一些元素发生碰撞时作出对应的响应,例如勇士和怪物进行决斗,捡到钥匙等等事件,因此我们也在上面的move函数中嵌入了dealcollideevent来处理这样的情况,一个简单效果展示如下:

当然,理论上按照原版的游戏这里应该是有一个背景故事的对话框的,这部分我们下一期再实现,本期我们主要实现一些基础的功能,比如一些简单事件的触发,包括遇到门,捡到钥匙等等:

'''处理撞击事件'''
def dealcollideevent(self, elem, block_position, map_parser):# 遇到不同颜色的门, 有钥匙则打开, 否则无法前进if elem in ['2', '3', '4']:flag = Falseif elem == '2' and self.num_yellow_keys > 0:self.num_yellow_keys -= 1flag = Trueelif elem == '3' and self.num_purple_keys > 0:self.num_purple_keys -= 1flag = Trueelif elem == '4' and self.num_red_keys > 0:self.num_red_keys -= 1flag = Trueif flag: map_parser.map_matrix[block_position[1]][block_position[0]] = '0'return flag# 捡到不同颜色的钥匙elif elem in ['6', '7', '8']:if elem == '6': self.num_yellow_keys += 1elif elem == '7': self.num_purple_keys += 1elif elem == '8': self.num_red_keys += 1map_parser.map_matrix[block_position[1]][block_position[0]] = '0'return True# 捡到宝石elif elem in ['9', '10']:if elem == '9': self.defense_power += 3elif elem == '10': self.attack_power += 3map_parser.map_matrix[block_position[1]][block_position[0]] = '0'return True# 遇到仙女, 进行对话, 并左移一格elif elem in ['24']:map_parser.map_matrix[block_position[1]][block_position[0] - 1] = elemmap_parser.map_matrix[block_position[1]][block_position[0]] = '0'return False
复制代码

最后,我们来实现一下勇士上下楼梯时切换当前游戏地图的效果,这咋听起来似乎有点难办,但其实不然,只需要将发生上下楼梯事件的命令返回到游戏主循环:

# 上下楼梯
elif elem in ['13', '14']:if elem == '13': events = ['upstairs']elif elem == '14': events = ['downstairs']return True, events'''行动'''
def move(self, direction, map_parser):# 判断是否冷冻行动if self.freeze_move_flag: returnassert direction in self.imagesself.image = self.images[direction]# 移动勇士move_vector = {'left': (-1, 0), 'right': (1, 0), 'up': (0, -1), 'down': (0, 1)}[direction]block_position = self.block_position[0] + move_vector[0], self.block_position[1] + move_vector[1]# 判断该移动是否合法, 并触发对应的事件events = []if block_position[0] >= 0 and block_position[0] < map_parser.map_size[1] and \block_position[1] >= 0 and block_position[1] < map_parser.map_size[0]:# --合法移动if map_parser.map_matrix[block_position[1]][block_position[0]] in ['0']:self.block_position = block_position# --触发事件elif map_parser.map_matrix[block_position[1]][block_position[0]] in ['2', '3', '4', '6', '7', '8', '9', '10', '13', '14', '24']:flag, events = self.dealcollideevent(elem=map_parser.map_matrix[block_position[1]][block_position[0]],block_position=block_position,map_parser=map_parser,)if flag: self.block_position = block_position# 重新设置勇士位置self.rect.left, self.rect.top = self.block_position[0] * self.blocksize + self.offset[0], self.block_position[1] * self.blocksize + self.offset[1]# 冷冻行动self.freeze_move_flag = True# 返回需要在主循环里触发的事件return events
复制代码

然后在主循环中进行响应即可:

# --触发游戏事件
for event in move_events:if event == 'upstairs':self.map_level_pointer += 1self.loadmap()elif event == 'downstairs':self.map_level_pointer -= 1self.loadmap()
复制代码

效果如下:

不知道大家有没有发现一个问题,就是勇士上楼之后所在的位置其实不对,理论上应该是在当前地图的下楼梯口附近的,而不是上一张游戏地图里勇士最后上楼时所在的位置,那么这部分应该如何实现呢?其实很简单,一个简单的解决方案是在定义游戏地图的时候,在上下楼梯处定义一个00变量:

画游戏地图的时候还是按照0元素去画:

if elem in self.element_images:image = self.element_images[elem][self.image_pointer]image = pygame.transform.scale(image, (self.blocksize, self.blocksize))screen.blit(image, position)
elif elem in ['00', 'hero']:image = self.element_images['0'][self.image_pointer]image = pygame.transform.scale(image, (self.blocksize, self.blocksize))screen.blit(image, position)
复制代码

但是上下楼梯切换游戏地图时,我们可以利用该标识符重置角色所在的位置:

# --触发游戏事件
for event in move_events:if event == 'upstairs':self.map_level_pointer += 1self.loadmap()self.hero.placenexttostairs(self.map_parser, 'down')elif event == 'downstairs':self.map_level_pointer -= 1self.loadmap()self.hero.placenexttostairs(self.map_parser, 'up')
复制代码

其中重置位置的函数实现如下:

'''放置到上/下楼梯口旁'''
def placenexttostairs(self, map_parser, stairs_type='up'):assert stairs_type in ['up', 'down']for row_idx, row in enumerate(map_parser.map_matrix):for col_idx, elem in enumerate(row):if (stairs_type == 'up' and elem == '13') or (stairs_type == 'down' and elem == '14'):if row_idx > 0 and map_parser.map_matrix[row_idx - 1][col_idx] == '00':self.block_position = col_idx, row_idx - 1elif row_idx < map_parser.map_size[0] - 1 and map_parser.map_matrix[row_idx + 1][col_idx] == '00':self.block_position = col_idx, row_idx + 1elif col_idx > 0 and map_parser.map_matrix[row_idx][col_idx - 1] == '00':self.block_position = col_idx - 1, row_idxelif col_idx < map_parser.map_size[1] - 1 and map_parser.map_matrix[row_idx][col_idx + 1] == '00':self.block_position = col_idx + 1, row_idxself.rect.left, self.rect.top = self.block_position[0] * self.blocksize + self.offset[0], self.block_position[1] * self.blocksize + self.offset[1]
复制代码

重新测试一下看看:

总结一下,主要就是实现了我们的勇士角色,以及他和地图中一些元素相遇后需要发生的一些简单的事件响应。

Python游戏开发,Pygame模块,Python从零开始带大家实现一个魔塔小游戏相关推荐

  1. 童年经典回忆 | 从零开始带大家用Python撸一个魔塔小游戏呀(3)

    导语 上一期我们主要带大家写了勇士类,以及勇士与一些简单的地图元素接触时所触发的事件: 童年经典回忆 | 从零开始带大家用Python撸一个魔塔小游戏呀(2) 这一期我们会带大家进一步复现我们的魔塔小 ...

  2. 童年经典回忆 | 从零开始带大家用Python撸一个魔塔小游戏呀(2)

    导语 上一期我们带大家完成了魔塔游戏每一层的初始化画面的制作: 童年经典回忆 | 从零开始带大家用Python撸一个魔塔小游戏呀(1) 这一期我们会带大家进一步复现我们的魔塔小游戏,主要内容包括英雄类 ...

  3. 童年经典回忆 | 从零开始带大家用Python撸一个魔塔小游戏呀(1)

    相关文件 相关游戏素材(图片和音频等)源于网络,侵歉删. 需要源代码的小伙伴私信小编,或者关注小编的公众号[Python日志] 开发工具 Python版本:3.7.4 相关模块: pygame模块: ...

  4. 怎么把4399小游戏的代码_25行代码带你爬取4399小游戏数据,看下童年的游戏是否还在...

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理. 还记得童年的网页小游戏吗?今天带大家爬取4399小游戏网站的数据,游戏名字+链接地址 目标网 ...

  5. Python游戏开发pygame模块,Python实现球球碰撞小游戏

    相信大家在初中电脑课上都偷偷玩过 Flash 游戏--是男人就坚持 100 秒,在游戏中无数的小球随机运动,玩家用鼠标控制大球,当大球碰撞到小球后,游戏结束,显示坚持的时间.今天我们一起来开发这个小游 ...

  6. Python游戏开发pygame模块,Python实现吃豆人,儿时的回忆

    老规矩,先上效果图 这是一个吃豆人的小游戏.我们8090后这一代人肯定会碰到过.黄点是我们自己,红点就是怪物们.这是最原始版的电子游戏. 然后我们可以在随便一个地方新建一个游戏代码,利用这个包的代码, ...

  7. c语言控制台小游戏是什么意思,闲来没事撸了一个控制台小游戏

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 #include #include #include #include #define MAXLEVEL 5 #define LEVELS 4 #defi ...

  8. python大作业数独_python做一个数独小游戏

    最近看了下python的一些知识,在这里记载一下. 1.首先是安装,在官网下载最新的版本3.6,安装的时候要注意在下面勾选上ADD TO PATH,安装的时候会自动写入到环境变量里面,如果没有勾选,可 ...

  9. Python游戏开发,Pygame模块,Python从零开始带大家实现魔塔小游戏

    前言 这一期我们会带大家进一步复现我们的魔塔小游戏,主要内容包括英雄类的定义与其基础行动的实现,行动过程中触发不同层的切换等功能. 废话不多说,让我们愉快地开始吧~ 开发工具 Python版本: 3. ...

最新文章

  1. 《C++ Primer 4th》读书笔记 第5章-表达式
  2. String、StringBuffer、StringBuilder三者的异同
  3. 驳“AJAX 的七宗罪”
  4. java中什么是递归_java中什么是递归
  5. 时隔七个月,我终于弄懂了汉诺塔的思想
  6. java访问器_ONGN和java字段访问器(get,set)
  7. 人物-商界-张茵:张茵
  8. 【前端工程师手册】JavaScript作用域拾遗
  9. BZOJ_1629_[Usaco2007_Demo]_Cow_Acrobats_(贪心)
  10. CMOS版图课程第七讲--可靠性设计,视频截图节选
  11. 数据清洗工具:OpenRefine的使用入坑DIY
  12. 四旋翼无人机飞行器基本知识(四旋翼无人机结构和原理+四轴飞行diy全套入门教程)
  13. 关于STVP写保护等级2的问题
  14. Android打开项目一直build的问题解决
  15. Java实现人脸识别登录、注册等功能
  16. 卸载计算机更新程序包,KB4343669更新包无法卸载的解决方案
  17. 三极管电路必懂的几种分析方法
  18. jk触发器改为四进制_这节课讲触发器
  19. websocket + tls + cdn 断流严重问题解决方法
  20. 华为云全球加速GA,让企业获得更优质的服务体验

热门文章

  1. 宏基因组实战7. bwa序列比对, samtools查看, bedtools丰度统计
  2. Python使用matplotlib可视化分布点图、自定义设置分布点图的中位数数据点的颜色(Distributed Dot Plot)
  3. pandas使用groupby函数计算dataframe数据中每个分组的N个数值的滚动计数个数(rolling count)、例如,计算某公司的多个店铺每N天(5天)的滚动销售额计数个数
  4. pandas移除dataframe字符串数据列中的前N个字符(remove the first n characters from values from column of dataframe)
  5. R语言编写自定义函数使用Wilcoxon符号秩检验(Wilcoxon signed rank)实现多分组非参数成对检验(pairwise)、并使用p.adjust函数调整概率值
  6. R语言ggplot2可视化柱状图并自定义柱体的宽度(通过变量指定条形的宽度)实战、条形图并自定义条形的宽度实战
  7. pandas使用pd.concat横向合并多个dataframe实战:多个dataframe的横向表拼接(行对齐)、多个dataframe的横向表拼接(指定join参数、交集还是并集)
  8. CellRanger单细胞基因表达分析基础流程
  9. <笔记1>matplotlib绘图工具笔记
  10. 包打包和解析过程 unity_解决Unity2018打包,提示 SDK Tools version 0.0.0 < 26.1.1