红帽子AI游戏开发系列5:《坦克大战》
发表于 2024-11-07 15:42:50

        大家好,我是你们的InsCode老师红帽子先生,今天我将继续带领大家感受AI游戏开发的神奇之旅,希望在这一系列的游戏开发课程中,能够让大家认识到InsCode AI编程的魅力。

        下载安装 InsCode AI IDE

        在上一节教程中,我们讲解了《飞机大战》的开发过程,有了《飞机大战》,怎么能少了《坦克大战》呢?想当初在电视机前与小伙伴双人合作玩FC《坦克大战》是多么酣畅淋漓的游戏体验呀!

        本节课我们就学习如何使用InsCode AI IDE开发坦克游戏!先来看一下最终效果视频:

坦克大战视频

一、素材准备

        之前我们写的几款小游戏,都是单场景的,比较简单,这次我们来提升一下难度,加入多场景界面和地图系统。

        在本次《坦克大战》游戏中,我们让游戏启动后先显示一个开始界面,并显示两个菜单按钮让玩家选择,选择了“开始游戏”后播放一段剧情视频再进入战斗场景。在战斗场景中,我们也模仿FC游戏中的《坦克大战》加入地图格子系统,让地图按照地图文件设置,放置土墙、铁墙、以及木桶,击碎木桶还可以得到随机奖牌,并加入多种坦克敌人形态和血条、子弹形态。游戏结束时进入到一个结束结算界面,对战斗结果进行显示。

        想想这可真是个大工程啊!如果以传统的开发手段,没有几天时间可是搞不定的。

        开始之前,我们先准备一下素材,毕竟这么复杂的需求,素材是肯定少不了的!

        首先我们制作了两张图片作为开始界面和结束界面的背景图:

        当然,我们还需要提供一些按钮素材图片,并且是两种状态的,用来支持鼠标移到上面时有不同的效果。

       这里老师还剪辑了一段有关坦克的视频作为剧情动画:

        

下面我们来制作游戏场景的素材,这些素材包括三类:

  1. 各种格子元素:包括地表、物件等。

图片

图素编号

说明

1

地表

2

破损的墙,击中就消失。

3

完整的墙,击中后变成破损的墙。

4

钢铁的墙,击中无变化。

5

树林,坦克可以从中通过。

6

木桶,击中可以随机产生一个药水瓶。

7

我方指挥部,被敌人摧毁游戏结束。

8

蓝色药水,5秒内变的无敌。

9

红色药水,加满血。


2.各种坦克及其子弹。

图标说明

我方坦克,10滴血,运动速度一般,普通子弹,子弹威力1.

敌方快速坦克,1滴血,运动速度快,普通子弹,子弹威力1.

敌方精英坦克,3滴血,运动速度一般,加强子弹,子弹威力5.

敌方精英坦克,5滴血,运动速度慢,发射导弹,子弹威力10


3、特效图片:坦克爆炸的效果图。

        我们把《飞机大战》中的爆炸效果序列帧图片拷贝过来就可以。

        这样图片素材基本就准备的差不多了,下面我们来制作创建项目并制作场景地图。

二、格子场景地图

        我们创建好工程“TankWar”,并创建文件夹Resources,将后把资源文件都放置到其中事,我们来聊一聊游戏中的格子场景地图。

        “格子场景地图”是2D游戏中常见的一种场景制作方案,这种方案将场景划分为二维的格子,在这个二维网络里,场景制作者可以在任意格子里设置需要的图素,并通过多层网格的叠加来展现丰富的场景。比如我们根据屏幕大小800x600将坦克大战的场景切分成15行20列的网格,每个格子40x40像素大小,从下往上共创建三层网格:

  1. 地表层:设置地表。
  2. 物体层:放置墙、木桶、我方指挥部。
  3. 遮挡层:放置树,可穿过。

        有了这个规划,我们可以在当前项目所在目录下创建一个map文件夹,并在其中创建一个map1.json,按照json的格式,我们加入一个layer_0~layer_2列表,每个layer数据列表包括15行20列的数值列表,每个元素代表一个图片,默认值设为0,我们可以在需要的地方按照地表、物件的编号进行填充。

        当然这个过程是比较繁琐的,所以一般在游戏开发过程中会使用地图编辑器来可视化的制作地图文件,比如知名格子地图工具Tiled,它可以方便的创建多层次地图,并导入一个元素大图裁切成许多格子小块用于对格子元素进行设置,下图中间演示了这种能力,中间是正在编辑中的场景地图,右边上部分是层级列表,这里只建了一个背景层,然后右下边是图集格子,把一个图片也切成了二维的小图元,可以用鼠标点选图元并设置到中间格子地图的任意一个格子里。

        

        编写好map1.json后,我们在工程里创建一个gamescene.py文件,输入需求:“创建一段pygame的代码,可以创建一个图片字典,按1~9作为key值从Resources中加载对应图片1.png~9.png ,然后读取map文件夹下的map1.json的layer_0~layer_2三个列表,每个列表代表一层地图,由4个15行20列的格子数据组成,每个格子里指定了图片的key值,循环在屏幕中从左下向右下绘制这15行20列的格子中的图片,每个格子图片大小为40x40。

很快AI就会帮我们生成以下代码:

import pygame
import json

# 初始化pygame
pygame.init()

# 设置屏幕尺寸
screen_width = 800
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))

# 加载并调整图片大小
image_dict = {
    1: pygame.transform.scale(pygame.image.load('Resources/1.png'), (40, 40)),
    2: pygame.transform.scale(pygame.image.load('Resources/2.png'), (40, 40)),
    3: pygame.transform.scale(pygame.image.load('Resources/3.png'), (40, 40)),
    4: pygame.transform.scale(pygame.image.load('Resources/4.png'), (40, 40)),
    5: pygame.transform.scale(pygame.image.load('Resources/5.png'), (40, 40)),
    6: pygame.transform.scale(pygame.image.load('Resources/6.png'), (40, 40)),
    7: pygame.transform.scale(pygame.image.load('Resources/7.png'), (40, 40)),
    8: pygame.transform.scale(pygame.image.load('Resources/8.png'), (40, 40)),
    9: pygame.transform.scale(pygame.image.load('Resources/9.png'), (40, 40))
    # 添加更多图片
}

# 读取JSON文件
with open('map/map1.json', 'r') as f:
    map_data = json.load(f)

# 格子大小
tile_size = 40

# 主循环
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill((0, 0, 0))  # 清屏
    # 绘制地图
    for layer in range(3):
        for row in range(15):
            for col in range(20):
                key = map_data['layer_' + str(layer)][row][col]
                if key in image_dict:
                    screen.blit(image_dict[key], (col * tile_size, row * tile_size))
    pygame.display.flip()  # 更新屏幕

pygame.quit()

        运行一下,可以看到以下场景画面。

        这样游戏的格子地图就做好了。        

三、开始界面

        回到main.py,删除代码,按下CTRL+J输入开始界面需求:“基于pygame编写一个坦克大战游戏,所有的资源从Resources目录加载,要求启动游戏时先有个开始界面,显示startui.png,并出现两个选项按钮,按钮1显示btn_00.png,鼠标移到按钮1时切换为btn_01.png,点击时切换到战斗场景,按钮2显示btn_10.png,鼠标移到按钮2时切换为btn_11.png,点击时退出游戏。”。

        需求是明确的,所以生成的代码也比较顺利,甚至贴心的创建了一个battle_scene函数供我们编写战斗场景的显示处理,代码如下:

mport pygame
import sys
pygame.init()

# 设置窗口
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("坦克大战")

# 加载资源
start_ui = pygame.image.load("Resources/startui.png")
btn_00 = pygame.image.load("Resources/btn_00.png")
btn_01 = pygame.image.load("Resources/btn_01.png")
btn_10 = pygame.image.load("Resources/btn_10.png")
btn_11 = pygame.image.load("Resources/btn_11.png")

# 格子大小
tile_size = 40
# 按钮位置
btn1_rect = btn_00.get_rect(center=(WIDTH // 2, HEIGHT // 2 - 50))
btn2_rect = btn_10.get_rect(center=(WIDTH // 2, HEIGHT // 2 + 50))

def start_menu():
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

        mouse_pos = pygame.mouse.get_pos()

        # 检查按钮状态
        if btn1_rect.collidepoint(mouse_pos):
            screen.blit(start_ui, (0, 0))
            screen.blit(btn_01, btn1_rect.topleft)
        else:
            screen.blit(start_ui, (0, 0))
            screen.blit(btn_00, btn1_rect.topleft)

        if btn2_rect.collidepoint(mouse_pos):
            screen.blit(btn_11, btn2_rect.topleft)
        else:
            screen.blit(btn_10, btn2_rect.topleft)

        # 按钮点击事件
        if pygame.mouse.get_pressed()[0]:
            if btn1_rect.collidepoint(mouse_pos):
                battle_scene()
            elif btn2_rect.collidepoint(mouse_pos):
                pygame.quit()
                sys.exit()

        pygame.display.flip()

def battle_scene():
    # 战斗场景逻辑
    pass

start_menu()

        运行一下,可以看到开始界面背景图和两个按钮,鼠标移到按钮上时也有相应的切换效果。

        我们可以在最下面使用CTRL+J输入一个需求:“基于moviepy中的VideoFileClip将Resources文件夹下的story.mp4显示到pygame屏幕上。”然后将生成的代码复制到battle_scene函数的开头。接着再把gamescene.py中的部分代码移值到当前main.py中,比如加载图片元素字典、地图json文件,并在战斗场景显示的函数中显示地图网格。

        经过一番改造,游戏的流程基本完成,不过要注意的是需要安装moviepy,可以在终端处输入 进行安装:

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple moviepy

四、玩法完善

        要完成这么一个复杂的游戏,在玩法的开发上,我们不能一股脑的就把所有的策划案给到AI,这样很容易不够准确而令AI理解不到位。

        我们可以将玩法拆分为以下几个部分并分别递进式完善:

        1、坦克的生成

        下面我们开始对玩法进行完善,通过AI对话输入需求:“在战斗场景下方8行10列的位置产生我方坦克,对应Resources下的player.png,图片正面向上,通过上下左右键进行移动,移动时让图片旋转与运动方向保持一致,每按下一次空格键可以发射一颗子弹,子弹对应Resources下的zd_1.png”。完成后运行结果如下,我们可以控制坦克运动,当按下按键可以看到子弹快速的发射出去。

        下面我们来处理敌方坦克,首先我们通过提示词在顶角和顶部中央位置轮流生成敌方坦克:“战斗场景开始时,增加一个敌方坦克计数器,初始设为20,每4秒在场景地图的左上角,顶部中间,右上角位置轮流生成1个敌方坦克,坦克类型从tank_1.png~tank_3中随机产生一个,计数器减1,减到0不再生成敌方坦克,因为坦克图片向上,所以要保证动动时,图片也旋转相应的角度保持与方向一致。”。

        确认生成的结果后,运行截图如下:

        2、地图格子阻挡处理

        这时候坦克并没有与地图之间产生阻挡关系,下面我们来做第二步,就是让敌方坦克与地图格子之间产生阻挡。通过提示词“让敌方坦克移动时与格子地图中的layer_1对应格子进行判断,如果运动时前面的格子中的值不为0,或者是屏幕边界则不可向前运动,稍做停顿后随机向其它方向运动。”让敌方坦克具备与墙碰撞的处理。

        运行后,发现敌方坦克已经能与墙元素进行碰撞了,但是不太准确,我们可以找到代码中相应的函数做一些处理就好了。

        比如在这里我们可以看到,坦克碰撞处理是用自身的矩形包围盒去做碰撞判断的,我们可以跟据移动方向对碰撞计算的格子行列索引进行优化,同时也优化一下与边界的碰撞,以保证有更精确的碰撞效果。

        3、敌方坦克属性与子弹处理

        敌方坦克有三种嘛,每种是有不同的特性的,这对于游戏的玩法体现很重要,下面我们首先让敌方坦克类型能从3种中按机率生成,继续全选代码,按CTRL+J修改代码:“当前代码中敌方坦克出生时的类型逻辑有BUG,要求产生敌方坦克能随机从tank_1.png,tanke_2.png,tank_3.png中按照50%,30%,20%的机率选一种生成,而不是都从tank_1.png,设置这三种坦克各有一个类型属性1,2,3。”。

        这里我们给坦克加入一个属性值1,2,3来方便后面指定各种类型坦克的特性,否则后面就不太好描述。

        完成坦克类型的设置后,我们加入特性描述:

“让类型1的敌方坦克有2.0倍的运动速度,每3秒发射一颗向前运动的子弹,子弹速度为每秒150像素,子弹使用图片zd_1.png来表示。

让类型2的敌方坦克有1.5倍的运动速度,每4秒发射一颗向前运动的子弹,子弹速度为每秒100像素,子弹使用图片zd_2.png来表示。

让类型3的敌方坦克有1.5倍的运动速度,每5秒发射一颗向前运动的子弹,子弹速度为每秒80像素,子弹使用图片zd_3.png来表示,因为子弹图片默认向上方向,要修改为根据子弹运动速度方向来旋转保证与运动方向一致。”

        AI生成的效果还不错,现在已经能看到各种坦克在屏幕上运动并发射子弹。

        然后是为每个坦克加上血条,继续对代码进行AI对话,“设置所有坦克所在位置正上方都显示一个红色血条,血条长度为坦克图片宽度,血条颜色为红色,血条高度为5像素,血条初始值为坦克的生命值,我方坦克有10个生命值,敌方坦克类型1有1个生命值,坦克类型2有3个生命值,坦克类型3有5个生命值,子弹zd_1.png击中对方阵营坦克减少1个生命值,子弹zd_2.png击中对方阵营坦克减少2个生命值,子弹zd_3.png击中对方阵营坦克减少3个生命值,当坦克被子弹击中时,血条减少相应值,当血条减少到0时,播放序列帧动画effect文件夹中的explode_0.png~explode_7.png,同时播放音效dead.wav,并从场景中删除这个坦克。我方坦克消失后,跳转到结算场景函数。

        战斗越来越完善了,最后我们来继续完善了下子弹击中处理:“修改代码,让子弹飞行过程中与地图格子layer_1中的格子图片ID做碰撞判断,如果击中ID为2的图片时,则格子中ID值变为1并子弹消失,如果击中ID为3的图片时,格子中ID值变为2并子弹消失,如果击中ID为4的图片时子弹消失,如果击中ID为6的图片则格子中ID值随机变为8或9的并子弹消失,如果击中ID为7的图片,直接游戏结束进入结算界面场景。结算界面场景中显示finish.png作为背景。

        经过这样几条比较明确的对话修改,游戏已经基本实现了相应的战斗逻辑,坦克子弹击中  

 后会变成破损的 ,再击中一次就会消息,而击中木桶时,可以看到有药瓶产生,游戏结束后也会进入到结算界面。

        要注意的是,在运行过程中,有时会发现敌方子弹互相之间也会有伤害、或者播放爆炸音效时卡顿,以及爆炸序列帧渲染时进入一个循环把序列帧效果渲染完成才刷新等问题。

        我们可以通过提示词一步步的修改,比如“敌方坦克子弹只对我方坦克有伤害”,“设置使用多线程来播放爆炸音效”,“让爆炸序列帧动画按照每秒10帧的速度进行动画播放,游戏每帧刷新时只播放动画中对应帧图片”等。

        4、完善道具效果

        在游戏中我们提供了两种药瓶,一种是ID为8的无敌药水,另一种是ID为9的加血药瓶,归纳为对话提示词就是:“修改代码,让我方坦克移动时与格子地图中的layer_1对应格子进行判断,如果运动时前面的格子中的值为8,5秒内被敌方子弹击中不损失生命值,如果值为9,生命值恢复到10

五、结算界面

        游戏胜利或失败时,还需要显示一个结束界面进行结算,我们可以通过对话提示:“修改代码,设置全局变量记录玩家击杀的每种敌方坦克的数量,判断玩家击杀完所有敌方坦克为胜利,被敌方坦克击杀为失败,胜利或失败都进入结算界面,设置finish.png图片缩放为窗口大小作为结算界面背景,在结算界面根据胜利或失败显示You Win或GameOver,标题下方列出所有敌方坦克类型图片,以及玩家击杀这种坦克的数量,按下空格重加载地图文件开始战斗。

        尝试一下,当游戏结束时,果然会跳转到结束界面,并显示击杀的坦克数量:

        这样,我们就基本完成了今天的《坦克大战》,再修一修细节,比如地图格子渲染部分改成先渲染layer_0,layer_1,等最后再渲染layer_2,让坦克可以在树里穿行,以及进行格子碰撞时,格子的ID值哪些能碰撞,哪些能通过,还有就是吃到药水后,要让药水所在格子的图片ID设置0等,最后我们再次运行,游戏就变得非常完美啦!

六、注意事项

        在本节的学习中,涉及到非常多的知识,其中场景地图的制作是重点和难点,其中考虑到篇幅,没有特别讲地图编辑器的使用,如果大家有时间可以学习一下Tiled工具,学会它就会很方便的制作地图了。还有就是视频的播放,有时会出现渲染尺寸和方向不对的情况,要注意通过AI对播放效果进行完善

         最后、感兴趣的小伙伴可以点击这里 InsCode AI IDE 下载InsCode AI IDE,并扫描以下二维码加用户群和红帽子先生一起讨论游戏开发啦!

        

CSDN官方微信
扫描二维码,向CSDN吐槽
微信号:CSDNnews
微博关注
【免责声明:CSDN本栏目发布信息,目的在于传播更多信息,丰富网络文化,稿件仅代表作者个人观点,与CSDN无关。其原创性以及文中陈述文字和文字内容未经本网证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本网不做任何保证或者承诺,请读者仅作参考,并请自行核实相关内容。您若对该稿件有任何怀疑或质疑,请立即与CSDN联系,我们将迅速给您回应并做处理。】