制作一款打飞机游戏42:敌人大脑
我们还有一个“敌人大脑”系统,
敌人行为系统的设计与实现
接下来,我们要谈谈如何设置一个系统,让敌人可以有不同的行为。这是一个复杂的问题,因为每个敌人行为都可能需要大量的代码。如果我们为每个敌人硬编码行为,那么很快就会用完标记。
因此,我计划创建一个脚本系统,通过数据来编码敌人的行为。每个敌人的大脑将是一个数组,包含一系列命令和参数。我们将编写一个解释器来读取这些命令并更新敌人的状态。
这样,我们就可以通过修改数据来轻松创建各种敌人行为,而无需编写大量的重复代码。我已经开始实现这个系统的基础部分,包括使用角度和速度来计算敌人的移动。
创建大脑数据库
首先,我们需要创建一个新的文本文件,我们将其命名为schmup brains
。这个名字听起来很有趣,尽管你也可以称之为AI或行为。现在,我们在这个文件中定义brains
,它应该是一个三维数组,因为每个大脑由多行组成,每行有多个条目。但是,处理三维数组有点复杂,所以我们决定将其简化为一个二维数组,其中每个大脑是一个长序列,每个命令及其参数连续存储。
编辑器的调整
我们的编辑器目前将所有输入都转换为数字,这不利于可读性。因此,我们需要对编辑器进行一些调整,以便当输入为nil
或nothing
时,删除该单元格,否则保留输入内容。这样,我们就可以在大脑中输入可读的命令和参数了。
创建简单的大脑
接下来,我们创建了两个简单的大脑。每个大脑包含一条命令,例如head
,后面跟着两个参数:角度和速度。这样,我们就可以控制敌人的移动方向和速度了。
将大脑集成到游戏中
现在,我们需要将这些大脑集成到我们的游戏中。当创建敌人时,我们从大脑数据库中选择一个大脑,并将其与敌人关联。然后,在游戏循环中,我们根据大脑中的命令和参数来更新敌人的行为。
扩展大脑功能
为了使大脑系统更加灵活,我们添加了一个新的命令wait
,它允许敌人在执行下一条命令之前等待一段时间。为了实现这个功能,我们在敌人实体中添加了一个weight
变量来记录等待时间,并在游戏循环中递减这个变量。当weight
大于0时,敌人不会执行新的命令。
优化代码结构
为了使代码更加清晰和易于维护,我们将大脑相关的逻辑封装在一个doBrain
函数中。这个函数负责从大脑中读取命令和参数,并更新敌人的状态。同时,我们还添加了一些调试信息,以便在开发过程中跟踪敌人的行为。
pico-8 cartridge // http://www.pico-8.com
version 41
__lua__
--show cursor
--move the cursor
--backspacefunction _init()--- customize here ---#include shmup_brains.txtfile="shmup_brains.txt"arrname="brains"data=brains----------------------debug={}msg={}_drw=draw_table_upd=update_tablemenuitem(1,"export",export)reload(0x0,0x0,0x2000,"cowshmup.p8")curx=1cury=1scrolly=0scrollx=0poke(0x5f2d, 1)
endfunction _draw()_drw()if #msg>0 thenbgprint(msg[1].txt,64-#msg[1].txt*2,80,14)msg[1].t-=1if msg[1].t<=0 thendeli(msg,1)end end-- debug --cursor(4,4)color(8)for txt in all(debug) doprint(txt)end
endfunction _update60()dokeys()mscroll=stat(36)_upd()
endfunction dokeys()if stat(30) thenkey=stat(31)if key=="p" thenpoke(0x5f30,1)endelsekey=nilendend
-->8
--drawfunction draw_table()cls(2)--spr(0,0,0,16,16)if menu thenfor i=1,#menu dofor j=1,#menu[i] dolocal mymnu=menu[i][j]local c=mymnu.c or 13if i==cury and j==curx thenc=7if _upd==upd_type thenc=0endendbgprint(mymnu.w,mymnu.x+scrollx,mymnu.y+scrolly,13) bgprint(mymnu.txt,mymnu.x+scrollx,mymnu.y+scrolly,c) endendendif _upd==upd_type thenlocal mymnu=menu[cury][curx]local txt_bef=sub(typetxt,1,typecur-1)local txt_cur=sub(typetxt,typecur,typecur)local txt_aft=sub(typetxt,typecur+1)txt_cur=txt_cur=="" and " " or txt_cur if (time()*2)%1<0.5 thentxt_cur="\^i"..txt_cur.."\^-i"endlocal txt=txt_bef..txt_cur..txt_aftbgprint(txt,mymnu.x+scrollx,mymnu.y+scrolly,7)end--[[for i=1,#data dofor j=1,#data[i] dobgprint(data[i][j],2+18*j,2+8*i,7)endend]]
endfunction refresh_table()menu={}for i=1,#data dolocal lne={}local linemax=#data[i]if i==cury thenlinemax+=1 endadd(lne,{txt=i,w=" ",cmd="",x=4,y=-4+8*i,c=2 })for j=1,linemax doif j==#data[i]+1 thenadd(lne,{txt="+",w=" ",cmd="newcell",cmdy=i,x=-10+14*(j+1),y=-4+8*i, })elseadd(lne,{txt=data[i][j],cmd="edit",cmdx=j,cmdy=i,x=-10+14*(j+1),y=-4+8*i,w=" "})endendadd(menu,lne)endadd(menu,{{txt=" + ",w=" ",cmd="newline",x=4,y=-4+8*(#data+1), }})
end
-->8
--updatefunction update_table()refresh_table()if btnp(⬆️) thencury-=1endif btnp(⬇️) thencury+=1endcury=(cury-1)%#menu+1cury-=mscrollcury=mid(1,cury,#menu)if btnp(⬅️) thencurx-=1endif btnp(➡️) thencurx+=1endif cury<#menu thencurx=(curx-2)%(#menu[cury]-1)+2elsecurx=1endlocal mymnu=menu[cury][curx]if mymnu.y+scrolly>110 thenscrolly-=4endif mymnu.y+scrolly<10 thenscrolly+=4endscrolly=min(0,scrolly)if mymnu.x+scrollx>110 thenscrollx-=2endif mymnu.x+scrollx<20 thenscrollx+=2endscrollx=min(0,scrollx)if btnp(❎) thenlocal mymnu=menu[cury][curx]if mymnu.cmd=="edit" then_upd=upd_typetypetxt=tostr(mymnu.txt)typecur=#typetxt+1elseif mymnu.cmd=="newline" thenadd(data,{0}) elseif mymnu.cmd=="newcell" thenadd(data[mymnu.cmdy],0)endend
endfunction upd_type()if key thenif key=="\r" then-- enterlocal mymnu=menu[cury][curx]poke(0x5f30,1)local typeval=typetxtif typeval==nil or typeval=="" thenif mymnu.cmdx==#data[mymnu.cmdy] and typetxt=="" then--delete celldeli(data[mymnu.cmdy],mymnu.cmdx)if mymnu.cmdx==1 thendeli(data,mymnu.cmdy)end_upd=update_tablereturnend typeval=0enddata[mymnu.cmdy][mymnu.cmdx]=typeval_upd=update_tablereturnelseif key=="\b" then--backspaceif typecur>1 thenif typecur>#typetxt thentypetxt=sub(typetxt,1,#typetxt-1)elselocal txt_bef=sub(typetxt,1,typecur-2)local txt_aft=sub(typetxt,typecur)typetxt=txt_bef..txt_aftendtypecur-=1endelseif typecur>#typetxt thentypetxt..=keyelselocal txt_bef=sub(typetxt,1,typecur-1)local txt_aft=sub(typetxt,typecur)typetxt=txt_bef..key..txt_aftendtypecur+=1endendif btnp(⬅️) thentypecur-=1endif btnp(➡️) thentypecur+=1endtypecur=mid(1,typecur,#typetxt+1)
end
-->8
--toolsfunction bgprint(txt,x,y,c)print("\#0"..txt,x,y,c)
endfunction split2d(s)local arr=split(s,"|",false)for k, v in pairs(arr) doarr[k] = split(v)endreturn arr
end
-->8
--i/o
function export()local s=arrname.."=split2d\""for i=1,#data doif i>1 thens..="|"endfor j=1,#data[i] doif j>1 thens..=","ends..=data[i][j]endends..="\""printh(s,file,true)add(msg,{txt="exported!",t=120})--debug[1]="exported!"
end
__gfx__
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00700700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00077000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00700700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
__map__
0000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000