一、前言
数码暴龙机(电波暴龙机)是万代公司发售的一系列与《数码兽》系列相关的液晶玩具商品。这些产品融合了养成和对战元素,为玩家提供了一种虚拟养成和战斗的娱乐体验。也是很多人的童年回忆。最近在B站刷到讲解暴龙通关的教程和视频,感觉暴龙机程序制作还是非常用心和精良的,在32*32黑白的点阵中,居然可以做出那么完整的动画。
最近正在学习PySide6桌面程序开发,继而萌生了用Python做一款桌面版数码暴龙机(电波暴龙机)的想法,经过几周的研究和探索,最后使用pixilart画像素画、使用PySide制作桌面宠物,使用Python代码控制点阵显示逻辑,终于完成了数码暴龙机(电波暴龙机)windows桌面彩色复刻版。接下来详细分享一下制作过程。
二、数码暴龙机(电波暴龙机)彩色复刻版成果展示
2.1 数码暴龙机(电波暴龙机)彩色复刻版展示
2.2 数码暴龙机(电波暴龙机)战斗动画展示
2.3 数码暴龙机(电波暴龙机)机身拖拽及右键弹出菜单展示
2.4 数码暴龙机(电波暴龙机)系统托盘展示
托盘截图:
菜单截图:
三、学习和研究过程分享
3.1 寻找数码暴龙机(电波暴龙机)素材
要完成复刻,首先要找到原版,通过网页搜索,发现初代电波暴龙机的图片和视频比较多,那就先尝试一下做初代的吧。
在浏览众多暴龙机相关网站后,最终选择了这个网址【游戏咖啡馆】来当一次被选召的孩子!初代电波暴龙机玩法简介的视频的进行复刻,这个视频讲的很详细,视频清晰可以很方便博主截图后进行研究,以下是该视频的截图。
3.2 获取数码暴龙机(电波暴龙机)黑色点阵图片并用pixilart进行彩色重绘
Pixilart是一款高颜值、简单易用的在线像素画绘制工具。Pixilart提供了一个直观的平台,使用者无需深厚的设计背景即可轻松创建像素艺术。它的界面简洁,功能齐全,从制图到下载一气呵成,实现了即开即用,极大地方便了用户。①在线使用:Pixilart作为一个在线工具,无需下载安装任何软件,用户只需通过网络浏览器即可访问和使用,极大地提高了使用的便利性;②无需注册:与其他一些设计工具不同,Pixilart无需用户注册即可直接使用,省去了繁琐的账号创建过程,使得用户体验更为流畅。
找到初代电波暴龙机视频后,接下来就到选取准备复刻的数码暴龙机(电波暴龙机)画面,经过挑选,博主选定了视频中阿古兽站立、步行、战斗、胜利四个画面进行复刻。然后使用浏览器的截图功能把这四个画面的每一帧不同的图片进行截图和保存。最后使用pixilart进行彩色重绘,保存为32*32的位图文件(这里博主专门数了一下视频里的液晶屏幕点阵,还真是32*32),方便后续Python程序进行调用。
3.2.1 用Pixilart重绘动画帧
1)用Pixilart重绘阿古兽站立动画,如下图:
用Pixilart彩色桶工具进行上色,如下图:
画好第一张图的时候,博主还是很震撼的,原来的设计师真是厉害,可以把动画里的角色用点阵还原的那么生动,没有任何多余的像素点,多一个或者少一个都会让人觉得不舒服,实在佩服。
2)用Pixilart重绘阿古兽步行动画,如下图:
3)用Pixilart重绘战斗画面,这里战斗画面的帧数就比较多了,估计有几十帧,博主花了好长时间才一帧一帧绘制好,真心累o(╥﹏╥)o,过程如下图:
战斗遇敌画面:
PS:这里还专门上网找了下图这个数码兽叫啥,叫基刹兽,不然无法完成上色,博主果然很专业,哈哈。
战斗血量画面:
战斗爆炸画面,颜色是拍脑袋选的,感觉还可以:
4)用Pixilart重绘胜利画面,如下图:
把这四个画面都重新画好后就可以进入下一步了,开始用PySide6来开发用于显示这几个动画的Python程序,让他们真正的动起来。
四、设计和研发过程分享
4.1桌面宠物程序框架设计
由于以前没写过桌面宠物的代码,所以还是在网上找资源来修改比较省事,经过搜索在CSDN找到一个比较合适“魔改”的资源。有兴趣可以点击这个链接详细阅读->桌面宠物 ① 通过python制作属于自己的桌面宠物。
参考的资源是使用PyQt5来做的,而博主是基于PySide6来开发的,所以需要对原代码进行一定的修改,并加入数码暴龙机(电波暴龙机)的显示逻辑,最后通过一顿魔改操作后形成了自己的桌面宠物程序框架,主要包括机身显示、鼠标拖拽、动画显示、右键控制菜单、显示托盘控制菜单五个部分,如下图:
4.2用PySide6实现暴龙机显示
要复刻桌面版暴龙机,首先要实现机身的显示,我们可以找到一张暴龙机的机身透明PNG图片,然后通过PySide6的QWidget()、QPixmap()等组件显示即可。主要需要注意的是设置窗口的透明以及图片对齐。
class DigitalPet(QWidget)
新建一个DigitalPet类(窗体)并继承QWidget类(窗体),用于承载各类组件,并分别调用以下几个方法完成程序初始化。
①self.init()
初始化窗体;
②self.init_notification_area()
初始化托盘;
③self.init_mask()
初始化遮罩(机身);
④self.init_digital_pet_image()
初始化动画显示控件(液晶屏幕);
⑤self.init_digital_pet_action()
初始化动画任务(屏幕图像变化逻辑)。
主要代码:
class DigitalPet(QWidget):
def __init__(self, parent=None, **kwargs):
super(DigitalPet, self).__init__(parent)
# 控制面板相关区域
self.notification_area_menu = QMenu(self) # 新建一个菜单项控件
self.notification_area_icon = QSystemTrayIcon(self) # 设置系统托盘中的图标
# 遮罩区域
self.mask = QLabel(self) # 新建遮罩背景
self.btn_a = QPushButton(self) # 新建遮罩按钮
self.is_follow_mouse = None
self.mouse_drag_pos = None
# 显示动画区域
self.image = QLabel(self) # 新建QLabel对象,用于显示动画
self.image_list = [] # 用于存放所有动画文件地址的动画文件地址列表
self.movie_list = [] # 用于存放所有动画的对象的动画对象列表
self.cur_movie = QMovie() # 新建当前的动画的QMovie对象
self.cur_movie_accumulate_frame = 0 # 记录当前动画累计播放的帧数,用于监控动画结束
self.cur_movie_frequency = 1 # 记录当前动画需要循环的次数
self.image_opacity = 1 # 透明度,用于设置显示和隐藏
# 动画控制系统相关
self.state_list = ["stand", "run"] # 动画状态列表
self.cur_state = "stand" # 当前状态
self.task_queue = Queue(maxsize=100) # 显示动画任务队列
self.timer = QTimer() # 时钟
# 初始化
self.init() # 窗体初始化
self.init_notification_area() # 托盘初始化
self.init_mask() # 遮罩初始化
self.init_digital_pet_image() # 动画显示控件初始化
self.init_digital_pet_action() # 动画任务(逻辑)初始化
4.2.1 def init(self)
①def init(self)
方法用于初始化窗体,并做一些窗体的设置,包括设置窗口无标题栏且固定在最前面,设置无边框窗口,设置窗口总显示在最上面,设置窗口透明,设置窗体空间不透明等。
透明背景截图:
主要代码:
# 窗体初始化
def init(self):
# 初始化
# 设置窗口属性:窗口无标题栏且固定在最前面
# FrameWindowHint:无边框窗口
# WindowStaysOnTopHint: 窗口总显示在最上面
# SubWindow: 新窗口部件是一个子窗口,而无论窗口部件是否有父窗口部件
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.SubWindow)
# setAutoFillBackground(True)表示的是自动填充背景,False为透明背景
# self.setAutoFillBackground(False)
# 窗口透明,窗体空间不透明
self.setAttribute(Qt.WA_TranslucentBackground, True)
self.resize(300, 300)
# 重绘组件、刷新
self.repaint()
4.2.2 def init_notification_area(self)
②def init_notification_area(self)
方法用于初始化系统托盘,并在托盘中加入隐藏/显示、退出菜单。
托盘截图:
菜单截图:
主要代码:
# 托盘化设置初始化
def init_notification_area(self):
# 导入准备在托盘化显示上使用的图标
icons = os.path.join('cong.png')
# 菜单项显示,点击后调用showing函数
show_action = QAction(u'隐藏/显示', self, triggered=self.show_digital_pet)
# 设置右键显示最小化的菜单项,菜单项退出,点击后调用quit函数
quit_action = QAction('退出', self, triggered=self.quit)
# 设置这个点击选项的图片
quit_action.setIcon(QIcon(icons))
# 在菜单栏添加一个无子菜单的菜单项‘隐藏/显示’
self.notification_area_menu.addAction(show_action)
# 在菜单栏添加一个无子菜单的菜单项‘退出’
self.notification_area_menu.addAction(quit_action)
# 设置托盘图标
self.notification_area_icon.setIcon(QIcon(icons))
# 设置托盘菜单
self.notification_area_icon.setContextMenu(self.notification_area_menu)
# 展示自定义托盘
self.notification_area_icon.show()
4.2.3 def init_mask(self)
③def init_mask(self)
方法用于初始化遮罩(暴龙机机身),使用QPixmap(“mask/D1_2.png”).scaled(QSize(240, 240))加载并缩放暴龙机机身,使用self.btn_a.clicked.connect(self.fight_action)绑定暴龙机按钮事项。
按钮和遮罩截图:
主要代码:
def init_mask(self):
# 初始化遮罩
qpm = QPixmap("mask/D1_2.png").scaled(QSize(240, 240))
self.mask.setPixmap(qpm)
# 初始化按钮
btn_a_qpb = QPixmap("mask/button_A.png")
self.btn_a.setFixedSize(71, 54)
self.btn_a.setIcon(QIcon(btn_a_qpb))
self.btn_a.setIconSize(QSize(60, 40))
self.btn_a.setFlat(True)
self.btn_a.move(165, 65)
self.btn_a.clicked.connect(self.fight_action)
4.2.4 def init_digital_pet_image(self)
④self.init_digital_pet_image()
初始化动画显示控件(液晶屏幕),该方法通过os.listdir(“image/”)遍历image文件夹下的所有图片,然后使用Python的GIF图片显示QMovie对象把GIF图片转换成Python的QMovie对象,最后统一添加到movie_list列表中,用于后续使用这些QMovie对象进行动画播放。
image文件夹中的GIF图片文件截图:
主要代码:
# 动画显示控件初始化
def init_digital_pet_image(self):
# 遍历image文件夹将所有动图存入图片列表
for file_name in os.listdir("image/"):
self.image_list.append("image/" + file_name)
print(file_name, end="|")
# QMovie是一个可以存放动态视频的类,一般是配合QLabel使用的,可以用来存放GIF动态图
# 根据图片列表存储的文件路径,生产对应的QMovie对象,并存入movie_list
for i in range(0, len(self.image_list)):
self.movie_list.append(QMovie(self.image_list[i]))
4.2.5 def init_digital_pet_action(self)
⑤self.init_digital_pet_action()
初始化动画任务(屏幕图像变化逻辑),该方法使用Python的先进先出队列Queue对象,控制task_queue队列里QMovie对象进出,在合适的时间显示不同的GIF动画,实现暴龙机动画的播放。
主要代码:
# 动画任务(逻辑)初始化
def init_digital_pet_action(self):
# 使用第一张图片作为初始movie
first_gif_movie = GifMovie(name="stand", movie=self.movie_list[9], frequency=3)
# 设置动图实际显示大小
self.cur_movie = first_gif_movie.movie
self.cur_movie.setCacheMode(QMovie.CacheAll)
self.cur_movie.setScaledSize(QSize(64, 64))
# 设置image对象,并启动gif播放
self.image.move(89, 88)
self.change_action(first_gif_movie)
# 展示
self.show()
# 根据动画队列,开始动画显示任务
def start_task(self):
# 判断当前动画队列是否为空
if self.task_queue.empty() is not True:
# 取出队列对象并删除队列中的该对象删除
cur_gif_movie = self.task_queue.get()
# 调用change_action方法更新动画
self.change_action(cur_gif_movie)
else:
cur_state = random.choice(self.state_list)
if cur_state == "stand":
self.stand_action()
if cur_state == "run":
self.run_action()
# 取出队列对象并删除队列中的该对象删除
cur_gif_movie = self.task_queue.get()
# 调用change_action方法更新动画
self.change_action(cur_gif_movie)
# 更新动画
def change_action(self, gif_movie):
# 根据获取的队列元素,更新cur_movie和cur_movie_frequency的值
print("action:", gif_movie.name, end=" ")
self.cur_state = gif_movie.name
self.cur_movie = gif_movie.movie
self.cur_movie_frequency = gif_movie.frequency
# 更新label组件
self.image.setMovie(self.cur_movie)
# 重置cur_movie_accumulate_frame为0
self.cur_movie_accumulate_frame = 0
# 动画帧变化信号绑定stop_frame方法
self.cur_movie.frameChanged.connect(self.stop_frame, Qt.UniqueConnection)
# 动画状态变化信号绑定check_movie_state方法
self.cur_movie.stateChanged.connect(self.check_movie_state, Qt.UniqueConnection)
# 启动动画
self.cur_movie.start()
# 自定义动画类
# name 动画名称
# movie 动画对象
# frequency 动画重复次数
class GifMovie:
def __init__(self, name, movie, frequency):
self.name = name
self.movie = movie
self.frequency = frequency
4.3用PySide6实现暴龙机鼠标拖拽
通过对DigitalPet添加def mousePressEvent(self, event)
(按下鼠标)、def mouseMoveEvent(self, event)
(移动鼠标)、def mouseReleaseEvent(self, event)
(松开鼠标)、def enterEvent(self, event)
(鼠标进入窗体)事件的处理代码,实现暴龙机鼠标拖拽。
拖拽截图:
主要代码:
# 在遮罩中点击鼠标左键时, 遮罩将和鼠标位置绑定
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.is_follow_mouse = True
# globalPos() 事件触发点相对于桌面的位置
# pos() 程序相对于桌面左上角的位置,实际是窗口的左上角坐标
self.mouse_drag_pos = QCursor.pos() - self.pos()
event.accept()
# 拖动时鼠标图形的设置
self.setCursor(QCursor(Qt.OpenHandCursor))
# 鼠标移动时调用,实现宠物随鼠标移动
def mouseMoveEvent(self, event):
# 如果鼠标左键按下,且处于绑定状态
if Qt.LeftButton and self.is_follow_mouse:
# 宠物随鼠标进行移动
self.move(QCursor.pos() - self.mouse_drag_pos)
event.accept()
# 鼠标释放调用,取消绑定
def mouseReleaseEvent(self, event):
self.is_follow_mouse = False
# 鼠标图形设置为箭头
self.setCursor(QCursor(Qt.ArrowCursor))
# 鼠标移进时调用
def enterEvent(self, event):
# 设置鼠标形状 Qt.ClosedHandCursor
self.setCursor(Qt.ClosedHandCursor)
4.5用PySide6实现暴龙机右键弹出菜单
通过def contextMenuEvent(self, event)
方法实现暴龙机右键弹出菜单,邮件弹出菜单包含退出和隐藏暴龙机功能。
右键弹出菜单截图:
主要代码:
# 宠物右键点击交互
def contextMenuEvent(self, event):
# 定义菜单
menu = QMenu(self)
# 定义菜单项
quitAction = menu.addAction("退出")
hide = menu.addAction("隐藏")
# 使用exec()方法显示菜单。从鼠标右键事件对象中获得当前坐标。mapToGlobal()方法把当前组件的相对坐标转换为窗口(window)的绝对坐标。
action = menu.exec(self.mapToGlobal(event.pos()))
# 点击事件为退出
if action == quitAction:
app.quit()
# 点击事件为隐藏
if action == hide:
# 通过设置透明度方式隐藏宠物
self.setWindowOpacity(0)
四、下一步计划
到这里,复刻数码暴龙机(电波暴龙机)就完成了,而且还为原来的黑色液晶添加了彩色,整个研究、设计、开发过程大概几周时间,看来Python的效率还是挺高的。这个程序只是用来研究可以学习,未来有时间再继续对这个程序进行完善和升级。