【毕业设计】仿谷歌浏览器小恐龙小游戏设计与实现 (源码)

本文主要是介绍【毕业设计】仿谷歌浏览器小恐龙小游戏设计与实现 (源码),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 0 项目简介
  • 1 游戏介绍
  • 2 实现效果
  • 3 Pygame介绍
  • 4 原理和实现
    • 4.1 环境配置
    • 4.2 游戏初始化
    • 4.3 创建游戏类
    • 4.4 云、路面以及仙人掌类
    • 4.5 计分板
    • 4.6 飞龙
    • 4.7 小恐龙
    • 4.8 游戏主循环
  • 5 最后


0 项目简介

🔥 Hi,各位同学好呀,这里是L学长!

🥇今天向大家分享一个今年(2022)最新完成的毕业设计项目作品

python小游戏毕设 仿谷歌浏览器小恐龙小游戏设计与实现 (源码)

🥇 学长根据实现的难度和等级对项目进行评分(最低0分,满分5分)

  • 难度系数:3分

  • 工作量:3分

  • 创新点:4分

  • 项目获取:https://gitee.com/sinonfin/system-sharing

1 游戏介绍

几年前,Google 给 Chrome 浏览器加了一个有趣的彩蛋:如果你在未联网的情况下访问网页,会看到 “Unable to connect to the Internet” 或 “No internet” 的提示,旁边是一只像素恐龙。

在这里插入图片描述
许多人可能觉得这只恐龙只是一个可爱的小图标,在断网的时候陪伴用户。但是后来有人按下空格键,小恐龙开始奔跑!

这个小彩蛋成为深受人们喜爱的小游戏,风靡至今。


今天我们用pygame做一个仿谷歌浏览器小恐龙游戏,游戏规则如下:

玩家通过↑↓控制小恐龙,如果玩家按下了空格键或者↑键,则小恐龙跳跃,如果玩家按下了↓键,则小恐龙低头,否则小恐龙正常向前冲。当小恐龙碰到这些障碍物时,小恐龙就死掉了,本局游戏也随之结束。

2 实现效果

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3 Pygame介绍

简介

Pygame是一系列专门为编写电子游戏而设计的Python模块(modules)。Pygame在已经非常优秀的SDL库的基础上增加了许多功能。这让你能够用Python语言编写出丰富多彩的游戏程序。

Pygame可移植性高,几乎能在任何平台和操作系统上运行。

Pygame已经被下载过数百万次。

Pygame免费开源。它在LGPL许可证(Lesser General Public License,GNU宽通用公共许可证)下发行。使用Pygame,你可以创造出免费开源,可共享,或者商业化的游戏。详情请见LGPL许可证。

优点

  • 能够轻松使用多核CPU(multi core CPUs) :如今双核CPU很常用,8核CPU在桌面系统中也很便宜,而利用好多核系统,能让你在你的游戏中实现更多东西。特定的pygame函数能够释放令人生畏的python GIL(全局解释器锁),这几乎是你用C语言才能做的事。

  • 核心函数用最优化的C语言或汇编语言编写:C语言代码通常比Python代码运行速度快10-20倍。而汇编语言编写的代码(assembly code)比Python甚至快到100多倍。

  • 安装便捷:一般仅需包管理程序或二进制系统程序便能安装。

  • 真正地可移植:支持Linux (主要发行版), Windows (95, 98, ME, 2000, XP, Vista, 64-bit Windows,), Windows CE, BeOS, MacOS, Mac OS X, FreeBSD, NetBSD, OpenBSD, BSD/OS, Solaris, IRIX, and QNX等操作系统.也能支持AmigaOS, Dreamcast, Atari, AIX, OSF/Tru64, RISC OS, SymbianOS and OS/2,但是还没有受到官方认可。你也可以在手持设备,游戏控制台, One Laptop Per Child (OLPC) computer项目的电脑等设备中使用pygame.

  • 用法简单:无论是小孩子还是大人都能学会用pygame来制作射击类游戏。

  • 很多Pygame游戏已发行:其中包括很多游戏大赛入围作品、非常受欢迎的开源可分享的游戏。

  • 由你来控制主循环:由你来调用pygame的函数,pygame的函数并不需要调用你的函数。当你同时还在使用其他库来编写各种各种的程序时,这能够为你提供极大的掌控权。

  • 不需要GUI就能使用所有函数:仅在命令行中,你就可以使用pygame的某些函数来处理图片,获取游戏杆输入,播放音乐……

  • 对bug反应迅速:很多bug在被上报的1小时内就能被我们修复。虽然有时候我们确实会卡在某一个bug上很久,但大多数时候我们都是很不错的bug修复者。如今bug的上报已经很少了,因为许多bug早已被我们修复。

  • 代码量少:pygame并没有数以万计的也许你永远用不到的冗杂代码。pygame的核心代码一直保持着简洁特点,其他附加物诸如GUI库等,都是在核心代码之外单独设计研发的。

  • 模块化:你可以单独使用pygame的某个模块。想要换着使用一个别的声音处理库?没问题。pygame的很多核心模块支持独立初始化与使用。

最小开发框架

import pygame,sys #sys是python的标准库,提供Python运行时环境变量的操控pygame.init()  #内部各功能模块进行初始化创建及变量设置,默认调用
size = width,height = 800,600  #设置游戏窗口大小,分别是宽度和高度
screen = pygame.display.set_mode(size)  #初始化显示窗口
pygame.display.set_caption("小游戏程序")  #设置显示窗口的标题内容,是一个字符串类型
while True:  #无限循环,直到Python运行时退出结束for event in pygame.event.get():  #从Pygame的事件队列中取出事件,并从队列中删除该事件if event.type == pygame.QUIT:  #获得事件类型,并逐类响应sys.exit()   #用于退出结束游戏并退出          pygame.display.update()  #对显示窗口进行更新,默认窗口全部重绘

代码执行流程

在这里插入图片描述

4 原理和实现

4.1 环境配置

  • Python版本:3.6.4
  • 相关模块:
  • pygame模块;
  • 以及一些Python自带的模块。

4.2 游戏初始化

# 游戏初始化
pygame.init()
screen = pygame.display.set_mode(cfg.SCREENSIZE)
pygame.display.set_caption('T-Rex Rush —— Charles的皮卡丘')
# 导入所有声音文件
sounds = {}
for key, value in cfg.AUDIO_PATHS.items():sounds[key] = pygame.mixer.Sound(value)

4.3 创建游戏类

首先,我们来明确一下该游戏包含哪些游戏精灵类:

  • 小恐龙:由玩家控制以躲避路上的障碍物;

  • 路面:游戏的背景;

  • 云:游戏的背景;

  • 飞龙:路上的障碍物之一,小恐龙碰上就会死掉;

  • 仙人掌:路上的障碍物之一,小恐龙碰上就会死掉;

  • 记分板:记录当前的分数和历史最高分。

4.4 云、路面以及仙人掌类

云,路面以及仙人掌来说,定义起来很简单,我们只需要加载对应的游戏元素图片:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

'''地板'''
class Ground(pygame.sprite.Sprite):def __init__(self, imagepath, position, **kwargs):pygame.sprite.Sprite.__init__(self)# 导入图片self.image_0 = pygame.image.load(imagepath)self.rect_0 = self.image_0.get_rect()self.rect_0.left, self.rect_0.bottom = positionself.image_1 = pygame.image.load(imagepath)self.rect_1 = self.image_1.get_rect()self.rect_1.left, self.rect_1.bottom = self.rect_0.right, self.rect_0.bottom# 定义一些必要的参数self.speed = -10'''更新地板'''def update(self):self.rect_0.left += self.speedself.rect_1.left += self.speedif self.rect_0.right < 0:self.rect_0.left = self.rect_1.rightif self.rect_1.right < 0:self.rect_1.left = self.rect_0.right'''将地板画到屏幕'''def draw(self, screen):screen.blit(self.image_0, self.rect_0)screen.blit(self.image_1, self.rect_1)'''云'''
class Cloud(pygame.sprite.Sprite):def __init__(self, imagepath, position, **kwargs):pygame.sprite.Sprite.__init__(self)# 导入图片self.image = pygame.image.load(imagepath)self.rect = self.image.get_rect()self.rect.left, self.rect.top = position# 定义一些必要的参数self.speed = -1'''将云画到屏幕上'''def draw(self, screen):screen.blit(self.image, self.rect)'''更新云'''def update(self):self.rect = self.rect.move([self.speed, 0])if self.rect.right < 0:self.kill()'''仙人掌'''
class Cactus(pygame.sprite.Sprite):def __init__(self, imagepaths, position=(600, 147), sizes=[(40, 40), (40, 40)], **kwargs):pygame.sprite.Sprite.__init__(self)# 导入图片self.images = []image = pygame.image.load(imagepaths[0])for i in range(3):self.images.append(pygame.transform.scale(image.subsurface((i*101, 0), (101, 101)), sizes[0]))image = pygame.image.load(imagepaths[1])for i in range(3):self.images.append(pygame.transform.scale(image.subsurface((i*68, 0), (68, 70)), sizes[1]))self.image = random.choice(self.images)self.rect = self.image.get_rect()self.rect.left, self.rect.bottom = positionself.mask = pygame.mask.from_surface(self.image)# 定义一些必要的变量self.speed = -10'''画到屏幕上'''def draw(self, screen):screen.blit(self.image, self.rect)'''更新'''def update(self):self.rect = self.rect.move([self.speed, 0])if self.rect.right < 0:self.kill()

4.5 计分板

记分板的定义类似,只不过它不需要移动,但是需要实时地更新当前 的分数:

'''记分板'''
class Scoreboard(pygame.sprite.Sprite):def __init__(self, imagepath, position, size=(11, 13), is_highest=False, bg_color=None, **kwargs):pygame.sprite.Sprite.__init__(self)# 导入图片self.images = []image = pygame.image.load(imagepath)for i in range(12):self.images.append(pygame.transform.scale(image.subsurface((i*20, 0), (20, 24)), size))if is_highest:self.image = pygame.Surface((size[0]*8, size[1]))else:self.image = pygame.Surface((size[0]*5, size[1]))self.rect = self.image.get_rect()self.rect.left, self.rect.top = position# 一些必要的变量self.is_highest = is_highestself.bg_color = bg_colorself.score = '00000''''设置得分'''def set(self, score):self.score = str(score).zfill(5)'''画到屏幕上'''def draw(self, screen):self.image.fill(self.bg_color)for idx, digital in enumerate(list(self.score)):digital_image = self.images[int(digital)]if self.is_highest:self.image.blit(digital_image, ((idx+3)*digital_image.get_rect().width, 0))else:self.image.blit(digital_image, (idx*digital_image.get_rect().width, 0))if self.is_highest:self.image.blit(self.images[-2], (0, 0))self.image.blit(self.images[-1], (digital_image.get_rect().width, 0))screen.blit(self.image, self.rect)

上面代码用is_highest变量来区分该记分板是否用于记录游戏最高分,还是只是记录当前的分数,做该区分的原因是游戏最高分前面有HI标识,所以占的空间更大:

4.6 飞龙

飞龙的定义就稍微复杂一些了,因为它不仅需要向左移动,还需要做出不停扇动翅膀的效果。具体而言,飞龙有两张图:

在这里插入图片描述

你需要做的就是每隔一段时间就切换一次当前的飞龙图片,以实现飞龙扇动翅膀的效果:

'''飞龙'''
class Ptera(pygame.sprite.Sprite):def __init__(self, imagepath, position, size=(46, 40), **kwargs):pygame.sprite.Sprite.__init__(self)# 导入图片self.images = []image = pygame.image.load(imagepath)for i in range(2):self.images.append(pygame.transform.scale(image.subsurface((i*92, 0), (92, 81)), size))self.image_idx = 0self.image = self.images[self.image_idx]self.rect = self.image.get_rect()self.rect.left, self.rect.centery = positionself.mask = pygame.mask.from_surface(self.image)# 定义一些必要的变量self.speed = -10self.refresh_rate = 10self.refresh_counter = 0'''画到屏幕上'''def draw(self, screen):screen.blit(self.image, self.rect)'''更新'''def update(self):if self.refresh_counter % self.refresh_rate == 0:self.refresh_counter = 0self.image_idx = (self.image_idx + 1) % len(self.images)self.loadImage()self.rect = self.rect.move([self.speed, 0])if self.rect.right < 0:self.kill()self.refresh_counter += 1'''载入当前状态的图片'''def loadImage(self):self.image = self.images[self.image_idx]rect = self.image.get_rect()rect.left, rect.top = self.rect.left, self.rect.topself.rect = rectself.mask = pygame.mask.from_surface(self.image)

4.7 小恐龙

最后,我们需要定义一下小恐龙类,也就是最复杂的一个游戏精灵类。它有低头,跳跃,普通前进三种状态。对于低头来说:

在这里插入图片描述

你只需要和飞龙扇动翅膀一样,不断切换两张低头的图片以实现小恐龙跑动的效果就可以了。

对于普通状态也是类似的:

在这里插入图片描述

对于跳跃状态,我们则可以通过初中学的上抛和自由落体运动公式来建模,从而计算小恐龙在竖直方向上的位置。具体而言,代码实现如下:

'''小恐龙'''
class Dinosaur(pygame.sprite.Sprite):def __init__(self, imagepaths, position=(40, 147), size=[(44, 47), (59, 47)], **kwargs):pygame.sprite.Sprite.__init__(self)# 导入所有图片self.images = []image = pygame.image.load(imagepaths[0])for i in range(5):self.images.append(pygame.transform.scale(image.subsurface((i*88, 0), (88, 95)), size[0]))image = pygame.image.load(imagepaths[1])for i in range(2):self.images.append(pygame.transform.scale(image.subsurface((i*118, 0), (118, 95)), size[1]))self.image_idx = 0self.image = self.images[self.image_idx]self.rect = self.image.get_rect()self.rect.left, self.rect.bottom = positionself.mask = pygame.mask.from_surface(self.image)# 定义一些必要的变量self.init_position = positionself.refresh_rate = 5self.refresh_counter = 0self.speed = 11.5self.gravity = 0.6self.is_jumping = Falseself.is_ducking = Falseself.is_dead = Falseself.movement = [0, 0]'''跳跃'''def jump(self, sounds):if self.is_dead or self.is_jumping:returnsounds['jump'].play()self.is_jumping = Trueself.movement[1] = -1 * self.speed'''低头'''def duck(self):if self.is_jumping or self.is_dead:returnself.is_ducking = True'''不低头'''def unduck(self):self.is_ducking = False'''死掉了'''def die(self, sounds):if self.is_dead:returnsounds['die'].play()self.is_dead = True'''将恐龙画到屏幕'''def draw(self, screen):screen.blit(self.image, self.rect)'''载入当前状态的图片'''def loadImage(self):self.image = self.images[self.image_idx]rect = self.image.get_rect()rect.left, rect.top = self.rect.left, self.rect.topself.rect = rectself.mask = pygame.mask.from_surface(self.image)'''更新小恐龙'''def update(self):if self.is_dead:self.image_idx = 4self.loadImage()returnif self.is_jumping:self.movement[1] += self.gravityself.image_idx = 0self.loadImage()self.rect = self.rect.move(self.movement)if self.rect.bottom >= self.init_position[1]:self.rect.bottom = self.init_position[1]self.is_jumping = Falseelif self.is_ducking:if self.refresh_counter % self.refresh_rate == 0:self.refresh_counter = 0self.image_idx = 5 if self.image_idx == 6 else 6self.loadImage()else:if self.refresh_counter % self.refresh_rate == 0:self.refresh_counter = 0if self.image_idx == 1:self.image_idx = 2elif self.image_idx == 2:self.image_idx = 3else:self.image_idx = 1self.loadImage()self.refresh_counter += 1

4.8 游戏主循环

最后写游戏主循环

游戏主循环的逻辑很简单,即每帧游戏画面,我们都需要检测一下玩家的操作,如果玩家按下了空格键或者↑键,则小恐龙跳跃,如果玩家按下了↓键,则小恐龙低头,否则小恐龙正常向前冲。

然后在游戏中,我们随机产生云,飞龙和仙人掌这些游戏场景和障碍物,并且和路面一起以相同的速度向左移动,从而实现小恐龙向右移动的视觉效果。在移动的过程中,我们需要对小恐龙和仙人掌,小恐龙和飞龙进行碰撞检测,当小恐龙碰到这些障碍物时,小恐龙就死掉了,本局游戏也随之结束。

需要注意的是我们应该使用collide_mask函数来进行更为精确的碰撞检测,而不是之前的collide_rect函数:

在这里插入图片描述

即当两个目标的最小外接矩形有重叠时,collide_rect就会判定两个目标有碰撞,这显然是不合理的,会给玩家带来较差的游戏体验。

另外,当分数每提高一千分,我们就和原版的游戏一样增加一点场景和障碍物向左移动的速度(也就是增加小恐龙向右移动的速度)。

最后,把当前所有的游戏元素绑定到屏幕上并更新当前的屏幕就ok了。

# 游戏主循环
clock = pygame.time.Clock()
while True:for event in pygame.event.get():if event.type == pygame.QUIT:pygame.quit()sys.exit()elif event.type == pygame.KEYDOWN:if event.key == pygame.K_SPACE or event.key == pygame.K_UP:dino.jump(sounds)elif event.key == pygame.K_DOWN:dino.duck()elif event.type == pygame.KEYUP and event.key == pygame.K_DOWN:dino.unduck()screen.fill(cfg.BACKGROUND_COLOR)# --随机添加云if len(cloud_sprites_group) < 5 and random.randrange(0, 300) == 10:cloud_sprites_group.add(Cloud(cfg.IMAGE_PATHS['cloud'], position=(cfg.SCREENSIZE[0], random.randrange(30, 75))))# --随机添加仙人掌/飞龙add_obstacle_timer += 1if add_obstacle_timer > random.randrange(50, 150):add_obstacle_timer = 0random_value = random.randrange(0, 10)if random_value >= 5 and random_value <= 7:cactus_sprites_group.add(Cactus(cfg.IMAGE_PATHS['cacti']))else:position_ys = [cfg.SCREENSIZE[1]*0.82, cfg.SCREENSIZE[1]*0.75, cfg.SCREENSIZE[1]*0.60, cfg.SCREENSIZE[1]*0.20]ptera_sprites_group.add(Ptera(cfg.IMAGE_PATHS['ptera'], position=(600, random.choice(position_ys))))# --更新游戏元素dino.update()ground.update()cloud_sprites_group.update()cactus_sprites_group.update()ptera_sprites_group.update()score_timer += 1if score_timer > (cfg.FPS//12):score_timer = 0score += 1score = min(score, 99999)if score > highest_score:highest_score = scoreif score % 100 == 0:sounds['point'].play()if score % 1000 == 0:ground.speed -= 1for item in cloud_sprites_group:item.speed -= 1for item in cactus_sprites_group:item.speed -= 1for item in ptera_sprites_group:item.speed -= 1# --碰撞检测for item in cactus_sprites_group:if pygame.sprite.collide_mask(dino, item):dino.die(sounds)for item in ptera_sprites_group:if pygame.sprite.collide_mask(dino, item):dino.die(sounds)# --将游戏元素画到屏幕上dino.draw(screen)ground.draw(screen)cloud_sprites_group.draw(screen)cactus_sprites_group.draw(screen)ptera_sprites_group.draw(screen)score_board.set(score)highest_score_board.set(highest_score)score_board.draw(screen)highest_score_board.draw(screen)# --更新屏幕pygame.display.update()clock.tick(cfg.FPS)# --游戏是否结束if dino.is_dead:break

5 最后

项目获取:https://gitee.com/sinonfin/system-sharing

这篇关于【毕业设计】仿谷歌浏览器小恐龙小游戏设计与实现 (源码)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/348978

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL