
这篇文章讲解如何通过Codea软件用Lua语言编写一个画线、删线的游戏。游戏要求单击屏幕不做任何动作。点击屏幕后拖动手指然后放开则在屏幕上添加线段。刚点击的位置作为线段起点放开的地方作为线段终点。线段保留在屏幕上直至手动删除。双击屏幕上有线段的位置则将线段删除。建议在阅读该教程前先阅读Codea入门。一、游戏所需的基本元素首先既然屏幕上要保留多个线段那就需要一个Lua表把线段都保留在表里。而这个表里的每一个元素都是线段。线段是由两个点组成的而每一个点都有xy坐标所以表示线段的元素也是一个表且有4个索引x1y1x2y2。除了保留的已画线段外还要有一个表示线段的表用于存放正在画的线段。如果不在画线其值就为nil。因此这些元素需要在游戏开始时就初始化。所以在程序刚开始就运行一次的setup()函数里要定义这些变量。-- Use this function to perform your initial setupfunctionsetup()lines_list{{[x1]200,[y1]200,[x2]500,[y2]500}}-- A list of all the lines that has drawncurrent_linenil-- The line that is currently drawingend在代码中表lines_list用于保留所有的线段。这里为了便于理解在最开始就有一个元素即从点(200, 200)到点(500, 500)的线段。另外current_line存放正在画的线段。或许有人会问把这些变量的定义都写在setup()函数里那么其它的函数能读到它们吗答案是可以。因为在Lua中除非有local声明否则任何函数里的所有变量都是全局变量。二、把表里的线段画在屏幕上除了设置背景色和线条颜色以及线条粗细外draw()函数还需根据lines_list里的内容显示出所有线段。此外如果正在画新线段则current_line不为nil因此也要显示出正在画的线段。lines_list里的每一个元素以及current_line都有x1y1x2y2索引因此代表从点(x1, y1)到点(x2, y2)的线段。-- This function gets called once every framefunctiondraw()-- This sets a dark background colorbackground(215,50,116)stroke(197,232,40)-- This sets the line thicknessstrokeWidth(5)-- Do your drawing herefor_,linpairs(lines_list)doline(l.x1,l.y1,l.x2,l.y2)-- draw the line in the lines_listendifcurrent_line~nilandcurrent_line.x1~nilandcurrent_line.y1~nilandcurrent_line.x2~nilandcurrent_line.y2~nilthen-- currently in drawing modeline(current_line.x1,current_line.y1,current_line.x2,current_line.y2)-- the line that is currently drawingendend在代码中for循环里的l就是lines_list里的每一个元素。所以每一个元素代表的线段都被显示出来了。同样如果current_line不为nil且四个索引都存在那么就显示出该线段。三、画出新线段以及删除线段一通过单击、拖动添加线段在该游戏中单击屏幕然后拖动松开后就保存新线段。这里涉及到touch输入的几个属性xy表示触摸屏幕的位置tapCount表示点击次数以及state表示触摸状态是已经松开还是刚点击还是在移动手指。touch的属性详情见Touch Gestures基本思路首先只有单击时才能生成线段。如果是双击或多击则不予生成。因此要把tapCount 1作为判断条件。当点击时新线段的初始位置就确定了。刚点击时state是BEGAN。此时current_line应当有x1和y1的值了。当手指在屏幕上移动时新线段的结束位置随着手指的位置的移动而不断变化。此时state是MOVING。current_line的x2和y2的值不断变化。当松手后新线段的结束位置就定了。此时state是ENDED。这时current_line有了x1y1x2y2的确定值已经完整所以应当加入lines_list。此时画新线的状态已经结束应当将current_line重置为nil。二通过双击删除线段在该游戏中要通过双击某线段来删除线段。这就涉及到判断点击屏幕处是在哪一条线段上的问题。当然点击屏幕处恰好落在线段上的概率为无限小所以应该是找到一条线段离双击屏幕处的距离最小且小于某个阈值。因此这个问题的很大一部分是计算点到线段的距离。关于这个距离的计算已经有现成的算法了只要把它写成Codea的Lua函数即可。这里算法参考点到线段的最短距离。这个算法涉及向量的运算。而Codea的Lua有向量运算的对象和方法。具体参见Vector。其中vec2()函数创建2维向量:dot()函数求点积:len()和:lenSqr()函数求向量的模以及模的平方。以下代码用于计算点P(x, y)到线段A(x1, y1)B(x2, y2)的距离。functiondist_to_segment(x,y,x1,y1,x2,y2)Avec2(x1,y1)Bvec2(x2,y2)Pvec2(x,y)ABB-A APP-A rAP:dot(AB)/(AB:lenSqr())dis100ifr0andr1thenPC(AB*r-AP)disPC:len()elseifr0thendisAP:len()elsePBB-P disPB:len()endreturndisend然后通过双击删除线段的基本思路是先要用tapCount判断点击次数是否为2。只有为2方可进行接下来的步骤。遍历lines_list里的所有元素找到离点击处距离最小且小于10的线段用函数dist_to_segment()计算距离然后将其从lines_list中删除。要注意一点当手指已经抬起即state为ENDED时无论点击数为多少都应当结束画新线状态。如果current_line完整就把新线加入。所以定义点击屏幕时的程序的代码functiontouched(touch)-- The following code shows how to add a lineiftouch.stateENDEDthenifcurrent_line~nilandcurrent_line.x1~nilandcurrent_line.y1~nilandcurrent_line.x2~nilandcurrent_line.y2~nilthentable.insert(lines_list,current_line)endcurrent_linenilendiftouch.tapCount1then-- if tapCount 1 then it is in add line mode-- drawing is finished so add the new line in list, then quit drawing modeiftouch.stateBEGANthen-- just begin with drawingcurrent_line{[x1]touch.x,[y1]touch.y,[x2]nil,[y2]nil}elseiftouch.stateMOVINGandcurrent_line~nilandcurrent_line.x1~nilandcurrent_line.y1~nilthen-- if moving then drawing mode of adding new linecurrent_line.x2touch.x current_line.y2touch.yendelseiftouch.tapCount2then--double tap, then delete the line closest to the tap placemin_dist10min_i0fori,linpairs(lines_list)dodist_to_thisdist_to_segment(touch.x,touch.y,l.x1,l.y1,l.x2,l.y2)ifdist_to_this10thenmin_distdist_to_this min_iiendendifmin_dist10thenlines_list[min_i]nilendendend在代码中if touch.state ENDED代码不在touch.tapCount的判断中。因为当点击已经结束了无论点击数多少都要退出画新线状态。而touch.tapCount 1则是画新线状态touch.tapCount 2则是删除线段。四、完整程序及效果-- Line drawing and deleting-- Use this function to perform your initial setupfunctionsetup()lines_list{{[x1]200,[y1]200,[x2]500,[y2]500}}-- A list of all the lines that has drawncurrent_linenil-- The line that is currently drawingend-- This function gets called once every framefunctiondraw()-- This sets a dark background colorbackground(215,50,116)stroke(197,232,40)-- This sets the line thicknessstrokeWidth(5)-- Do your drawing herefor_,linpairs(lines_list)doline(l.x1,l.y1,l.x2,l.y2)-- draw the line in the lines_listendifcurrent_line~nilandcurrent_line.x1~nilandcurrent_line.y1~nilandcurrent_line.x2~nilandcurrent_line.y2~nilthen-- currently in drawing modeline(current_line.x1,current_line.y1,current_line.x2,current_line.y2)-- the line that is currently drawingendendfunctiontouched(touch)-- The following code shows how to add a lineiftouch.stateENDEDthenifcurrent_line~nilandcurrent_line.x1~nilandcurrent_line.y1~nilandcurrent_line.x2~nilandcurrent_line.y2~nilthentable.insert(lines_list,current_line)endcurrent_linenilendiftouch.tapCount1then-- if tapCount 1 then it is in add line mode-- drawing is finished so add the new line in list, then quit drawing modeiftouch.stateBEGANthen-- just begin with drawingcurrent_line{[x1]touch.x,[y1]touch.y,[x2]nil,[y2]nil}elseiftouch.stateMOVINGandcurrent_line~nilandcurrent_line.x1~nilandcurrent_line.y1~nilthen-- if moving then drawing mode of adding new linecurrent_line.x2touch.x current_line.y2touch.yendelseiftouch.tapCount2then--double tap, then delete the line closest to the tap placemin_dist10min_i0fori,linpairs(lines_list)dodist_to_thisdist_to_segment(touch.x,touch.y,l.x1,l.y1,l.x2,l.y2)ifdist_to_this10thenmin_distdist_to_this min_iiendendifmin_dist10thenlines_list[min_i]nilendendendfunctiondist_to_segment(x,y,x1,y1,x2,y2)Avec2(x1,y1)Bvec2(x2,y2)Pvec2(x,y)ABB-A APP-A rAP:dot(AB)/(AB:lenSqr())dis100ifr0andr1thenPC(AB*r-AP)disPC:len()elseifr0thendisAP:len()elsePBB-P disPB:len()endreturndisend画新线删除线五、知识点这个小游戏主要涉及以下知识点Lua的表包括遍历添加、删除元素Lua的变量默认为全局变量Codea画线段Codea里的向量Codea里的点击屏幕输入