这个需求来自于工作中的一个需求
两个城池之间的路径是美术事先画好的,武将需要沿着画好的曲线行进
经过查找资料,发现了这篇文章
用到了下面这个公式:
阶贝塞尔曲线可如下推断。给定点P0、P1、…、Pn,其贝塞尔曲线即
高阶曲线为建构高阶曲线,便需要相应更多的中介点。对于三次曲线,可由线性贝塞尔曲线描述的中介点Q0、Q1、Q2,和由二次曲线描述的点R0、R1所建构:
三次贝塞尔曲线
对于四次曲线,可由线性贝塞尔曲线描述的中介点Q0、Q1、Q2、Q3,由二次贝塞尔曲线描述的点R0、R1、R2,和由三次贝塞尔曲线描述的点S0、S1所建构:
四次贝塞尔曲线
更复杂的:
五次贝塞尔曲线
以下为lua的实现方式,参考了上文中的c++算法,并没有考虑在极为高阶的情况下的性能问题
代码基于quick-cocos2d-x 2.2.5编写(谁叫他快呢)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
-- -- Author: superyyl -- Date: 2014-08-22 17:31:48 -- tool = {} -- float to int function tool.toint(n) local s = tostring(n) local i, j = s:find('%.') if i then return tonumber(s:sub(1, i-1)) else return n end end -- 获取在ccnode屏幕的位置 function tool.getPositionInScreen(node) if not node then return 0,0 end local x,y = node:getPosition() local parent = node:getParent() while parent do local px,py = parent:getPosition() if not parent:isIgnoreAnchorPointForPosition() then local anchorPoint = parent:getAnchorPointInPoints() px = px - anchorPoint.x py = py - anchorPoint.y end x =x + px y = y + py parent = parent:getParent() end return x, y end -- 检查触摸x,y是否在摸个node上 function tool.checkIfTouch(sprite, x, y) if not sprite then return false end local px, py = tool.getPositionInScreen(sprite) local anchorPoint = sprite:getAnchorPointInPoints() local px = px - anchorPoint.x local py = py - anchorPoint.y local w = sprite:getContentSize().width local h = sprite:getContentSize().height local boundingBox = CCRect:new(px,py,w,h) if boundingBox:containsPoint(ccp(x,y)) then return true else return false end end --阶乘 function factorial(n) local result = 1 for i=1,n,1 do result = result * i end return result end --杨辉三角 function pascalTriangleRatio(n,i) return factorial(n)/(factorial(i)*factorial(n-i)) end --x的y次幂 function powf(x,y) if y == 0 then return 1 end local result = 1 for i=1,y,1 do result = result * x end return result end --贝塞尔 --[[ @param p 控制点table @param t ]] function bezieerat(p,t) local px,py = 0,0 local n = #p - 1 for i=0,n do local ratio = pascalTriangleRatio(n,i) px = px + ratio * p[i+1].x * powf(t,i) * powf(1-t, n-i) py = py + ratio * p[i+1].y * powf(t,i) * powf(1-t, n-i) end return ccp(px,py) end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
-- -- Author: superyyl -- Date: 2014-08-27 11:44:08 -- local General = class("General",function() return display.newSprite("res/ui/circle.png") end) General.STATE = { IDLE = 0, MOVE = 1 } function General:ctor() self:registerScriptHandler(function(e) if e == "enter" then self:onEnter() elseif e == "exit" then self:onExit() end end) self.state = General.STATE.IDLE end function General:startAction(p,t) self.controlPos = {} self.totalTime = t self.state = General.STATE.MOVE for idx,pos in pairs(p) do local newPos = ccp(pos.x,pos.y) self.controlPos[#self.controlPos+1] = newPos end self.number = #self.controlPos self.t = 0 end function General:update(dt) if self.state == General.STATE.MOVE then self.t = self.t + dt local p = bezieerat(self.controlPos, self.t/self.totalTime) self:setPosition(p) if self.t > self.totalTime then self.state = General.STATE.IDLE self:startAction(self.controlPos, self.totalTime) end end end function General:onEnter() self.updateSchedulerEntry = CCDirector:sharedDirector():getScheduler():scheduleScriptFunc(handler(self,self.update), 0, false) end function General:onExit() if self.updateSchedulerEntry then CCDirector:sharedDirector():getScheduler():unscheduleScriptEntry(self.updateSchedulerEntry) end end return General |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
-- -- Author: superyyl -- Date: 2014-08-27 10:20:02 -- local TestScene = class("TestScene", function() return display.newScene("TestScene") end) function TestScene:ctor() self.layer = CCLayerColor:create(ccc4(255, 255, 255, 255)) self:addChild(self.layer) self.pos = {} self.points = {} self.layer:registerScriptTouchHandler(handler(self,self.onTouch), false) self.layer:setTouchEnabled(true) local delete = CCControlButton:create( CCLabelTTF:create("删除最后一个节点", "Thonburi", 24), CCScale9Sprite:create("res/ui/btn_default_normal.png") ) delete:setPreferredSize(CCSize(200, 80)) delete:setPosition(120,display.height-60) delete:addHandleOfControlEvent( handler(self,self.onDeleteClick), CCControlEventTouchUpInside ) self:addChild(delete) end function TestScene:onDeleteClick() table.remove(self.pos,#self.pos) self:refresh() end function TestScene:onTouch(e,x,y) if e == "began" then self.touchLocation = ccp(x,y) self.dragPoint = nil self.dragIndex = nil for idx,point in pairs(self.points) do if tool.checkIfTouch(point,x,y) then self.dragPoint = point self.dragIndex = idx print("self.dragIndex",self.dragIndex) end end return true elseif e == "moved" then if self.dragPoint then local sprite = self.dragPoint local pos = ccp(sprite:getPositionX()+x-self.touchLocation.x,sprite:getPositionY()+y-self.touchLocation.y) self.dragPoint:setPosition(pos) self.touchLocation = ccp(x,y) end elseif e == "ended" then if self.dragPoint then self.pos[self.dragIndex] = ccp(x,y) self.dragPoint = nil self.dragIndex = nil self:refresh() else local pos = ccp(x,y) local distance = ccpDistance(self.touchLocation, pos) if distance < 25 then self.pos[#self.pos+1] = pos end self:refresh() end end end function TestScene:refresh() self.layer:removeAllChildrenWithCleanup(true) self.points = {} for i,pos in pairs(self.pos) do local point = CCSprite:create("res/ui/point.png") point:setPosition(pos) self.layer:addChild(point) local label = CCLabelTTF:create(tostring(i), "Thonburi", 20) label:setColor(ccc3(0, 0, 0)) label:setPosition(point:getContentSize().width/2,point:getContentSize().height+10) point:addChild(label) self.points[#self.points+1] = point end if #self.pos > 1 then self.general = require("app.models.General").new() self.general:setPosition(self.pos[1]) self.layer:addChild(self.general) self.general:startAction(self.pos, 4) local controlPos = {} for idx,pos in pairs(self.pos) do local newPos = ccp(pos.x,pos.y) controlPos[#controlPos+1] = newPos end local node = CCDrawNode:create() self.layer:addChild(node) local tracePoints = {} for t=0,1,0.001 do local point = bezieerat(controlPos, t) node:drawDot(point, 2, ccc4f(0, 255, 0, 128)) tracePoints[#tracePoints+1] = {} tracePoints[#tracePoints].point = point tracePoints[#tracePoints].percent = t end local distance = 0 for i=1,#tracePoints-1,1 do local p1 = tracePoints[i] local p2 = tracePoints[i+1] local d = ccpDistance(p1.point, p2.point) distance = distance + d end local len = 30 local pointInfo = {} local count = tool.toint(distance / len) local mod = distance % len len = len + mod / count pointInfo[1] = tracePoints[1] local remain = len for i=1,#tracePoints-1,1 do local p1 = tracePoints[i] local p2 = tracePoints[i+1] local d = ccpDistance(p1.point, p2.point) remain = remain - d if remain <= 0 then pointInfo[#pointInfo+1] = p2 remain = remain + len end end for idx,p in pairs(pointInfo) do local point = CCSprite:create("res/ui/trace.png") point:setPosition(p.point) print("percent:"..p.percent) self.layer:addChild(point) end print(string.format("长度:%s#%s#%s#%s",distance,count,mod,len)) end end return TestScene |
最后放一张效果图