第一次使用PICO-8开发游戏,虽然跟Unity3d, cocos2d 比起来PICO-8实在是简陋,但是用起来却十分的令人着迷。8bit复古风的PICO-8的限制十分的多,比如128x128的分辨率,只有16种颜色,可以使用的sprite和地图的大小也有限制。但是在这种限制下反而可以更简单的去考虑游戏本身的玩法,而且PICO-8的分享修改机制可以快速的修改他人的游戏,尝试不同的玩法。总之PICO-8是一个可以用来快速实现并检验游戏创意的游戏引擎,对于想尝试开发游戏的新手也十分友好,仅凭官方文档中的简单介绍就可以立刻上手。下面我就分享下开发过程,先上下游戏截图:
在线版本地址:
https://www.lexaloffle.com/bbs/?pid=54230#p54229
使用其他代码编辑器
打开PICO-8未进入任何游戏之前是默认的空项目,点击ESC切换到编辑模式可以看到,代码/sprite/map/音效/音乐编辑器。
自带的编辑器只有大写使用起来十分不便,我采用自己的熟悉的编辑器(SublimeText)来进行代码编辑。
使用其他编辑器之前首先保存项目,ESC切换到控制台下,使用 save loop.p8 命令进行保存文件。保存后我们可以在PICO-8目录种找到保存的 `loop.p8`文件,不同的系统的默认路径不同:
Windows: C:/Users/Yourname/AppData/Roaming/pico-8/carts OSX: /Users/Yourname/Library/Application Support/pico-8/carts Linux: ~/.lexaloffle/pico-8/carts
找到p8文件后直接拖入想使用的文本编辑器即可,在外部编辑器编辑完成保存后切回PICO-8后,使用CTRL+R刷新即可看到结果(但有的时候代码错误,会导致刷新没有效果,这时候回到控制台模式输入load loop.p8 重新载入后再输入run命令重新运行)。
代码结构
PICO-8提供了3个特殊函数,只要代码中有这三个函数引擎会自动调用以完成游戏循环
_update() 更新函数,以30fps的速度执行,即每0.033s执行一次 _draw() 绘制函数每次update执行一次 _init() 初始化函数,游戏启动时执行一次
结构十分简单,在update中处理用户输入等变量,在draw中根据变量进行绘制
绘制cursor
我们需要一个cursor根据玩家输入进行移动,首先在PICO-8的编辑模式中绘制cursor。PICO-8中的sprite大小是8x8像素,我们需要的的cursor大小是16x16,可以将上图红框中的范围拖到第二格,就可以直接绘制16x16的范围了。
绘制好了之后就可以在_draw()中使用了,PICO-8提供了spr函数将sprite sheet中的图片绘制到游戏中,[ ]中的参数为可选参数:
spr n x y [w h] [flip_x] [flip_y] n: sprite的序号,可以在编辑模式中查看 x y: 绘制到屏幕上的坐标 w h:绘制到屏幕上的宽高默认1 1 flip_x flip_y:水平/垂直方向是否翻转
spr一次只能绘制一个8x8的sprite 而光标是16x16需要执行4次才可以绘制完成,好在还有另一个更方便的函数sspr
sspr sx sy sw sh dx dy [dw dh] [flip_x] [flip_y]
跟spr不同的是sspr不使用sprite序号定位,而是使用 sx sy sw sh ;这四个参数代表在sprite sheet中的sprite的 左上角坐标x,y和sprite的宽高,对于上图中的cursor这4个参数应该是 8,0,16,16;dx dy dw dh是绘制到屏幕上的坐标,dw dh不输入默认取sw sh。
sx sy sw sh这四个值是固定的,我们只需要根据用户输入在update中调整dx dy 这两个值就可以移动cursor了。为了在draw 和 update都可以调用位置信息,用一个全局变量储存cursor的信息。
cursor = {} cursor.x = 0 cursor.y = 0 function _draw() cls(1) sspr(8,0,16,16,cursor.x*16,cursor.y*16) end function _update() if btnp(0,0) then cursor.x -= 1 end if btnp(1,0) then cursor.x += 1 end if btnp(2,0) then cursor.y -= 1 end if btnp(3,0) then cursor.y += 1 end if cursor.x < 0 then cursor.x += 8 end if cursor.x > 7 then cursor.x -= 8 end if cursor.y < 0 then cursor.y += 8 end if cursor.y > 7 then cursor.y -= 8 end end
- cursor.x cursor.y存的不是坐标值,而是cursor当前所在格子的位置,这样每次移动cursor都会移动一格而不是一个像素。屏幕尺寸是128x128 ,格子的大小也就是cursor的大小是16x16 所以格子的尺寸应该是8x8,x,y取值[0-7]。
- _update函数中使用btnp来获取被按下的按键,btnp两个参数第一个参数是按钮代码,二个参数是玩家代码,取0就是第一个玩家。按键代码 0123分别是左右上下。
- 当按钮按下时移动cursor的x,y当到达屏幕边缘时,比如x<0将其 +8 移动到最右边。
- _draw函数中首先调用cls函数清空屏幕以便重新绘制,cls有一个可选参数表示使用指定颜色进行填充屏幕,从16个颜色中选。
使用同样的办法可以将游戏中的一个个旋转的图形绘制出来,这里遇到个问题,我没有找到旋转的函数。PICO-0只提供了flip_x flip_y进行翻转,有些旋转可以通过翻转代替,有些就没法做到了。所以有些图形我画了两个,这样就可以通过水平和垂直翻转实现4个方向旋转了。
关于游戏数据,我使用了2个数组,一个用来存图形的类型(一共5种)一个用来存储旋转方向,这两个数组长度都是64(8*8)。关于游戏数据生成,如何判断胜利的算法,代码比较长这里就不多说了,可以直接看代码。点击上面的在线版链接,在code中可以直接看到,有PICO-8的朋友也可以在splore中找到InfiniteLoop下载下来编辑。
如果有感兴趣的朋友可以留言,我再单独写一篇日志。
游戏菜单
我们想在游戏开始前有一个开始菜单用来展示游戏玩法。当然可以在_draw函数中判断游戏是否开始,未开始则绘制开始菜单,开始就绘制游戏画面。还有一种更好的方法,使用一个全局变量存储绘制的函数,在同状态下对此变量重新赋值,这样就把游戏和开始菜单的绘制代码分到两个函数中,不会都挤在_draw中,影响代码的阅读。
function draw_game() end function draw_menu() end function _init() scn.draw = draw_menu end function _update() if btnp(4,0) then if scn.draw == draw_menu then scn.draw = draw_game end end end function _draw() cls(1) scn.draw() end
- Lua中函数可以赋值给变量
发布游戏
游戏制作好以后使用run运行游戏,在想做游戏封面的地方按下F7进行截图;然后ESC到控制台模式,输入:save loop.p8.png
这时候就在carts目录里生成了一个loop.p8.png图片,这个类似游戏卡的神奇的图片就包含了游戏所有内容。将此图片提交到官网就可以在splore中找到你的游戏啦。简单几步就可以将游戏发布分享,供玩家欣赏或者修改。以下是官网游戏提交链接
https://www.lexaloffle.com/pico-8.php?page=submit
感谢作者的分享! 即使对于不太了解游戏编程的人来说,读起来也毫不困难,读完感觉受益匪浅,非常有意思。(另外插一句,国内购买pico-8不太方便,有个教育版,在web页面编程,虽然方便,但功能不够齐全。)