大多数游戏都有背包这个东西.
道具列表通常用 ScrollView 来实现.
这个ScrollView内部有一个Layout, 滑动都是由移动这个Layout来实现.
道具摆放通常从上往下, 从左到右.
假设你有一个道具数组, 你遍历这个数组来摆放道具.
因为数组长度是已知的, 你可以计算出Layout需要的尺寸, 再把道具摆上去.
这个实现是很容易的. 但是, 如果你提前不知道数组长度, 就是不知道道具数量,
可能随时会添加道具或者删除道具.
因为cocos2dx的坐标系是左下角为原点, 因此动态增加或删除都需要把所有的道具都移动位置,
光移动Layout是不行的.
%26nbsp;
说了一堆的废话.
1 -- 增加, 删除. 2 function scrollView:beginEditChilds(childWidth, childHeight) 3 self.contentSize = self:getContentSize(); 4 self.childCount = #(self:getChildren()); 5 self.colCount = math.floor(self.contentSize.width / childWidth); 6 self.childWidth = (self.contentSize.width - childWidth * self.colCount) / (self.colCount + 1) + childWidth; 7 self.childHeight = childHeight; 8 self.innerSize = self:getInnerContainerSize(); 9 self.innerOffsetY = self:getInnerContainerPosition().y + self.innerSize.height - self.contentSize.height; 10 end 11 12 function scrollView:endEditChilds() 13 local rowCount = math.ceil(self.childCount / self.colCount); 14 self.innerSize.height = math.max(self.contentSize.height, rowCount * self.childHeight); 15 self:setInnerContainerSize(self.innerSize); 16 self:setInnerContainerPosition( 17 cc.p(0, math.min(0, self.contentSize.height + self.innerOffsetY - self.innerSize.height))); 18 19 local offsetY = self.innerSize.height - self.childHeight; 20 self.childs = self:getChildren(); 21 for i = self.childCount, 1, -1 do 22 local row = math.floor((i - 1) / self.colCount); 23 local col = math.floor((i - 1) % self.colCount); 24 local x = col * self.childWidth + self.childWidth * 0.5; 25 local y = row * self.childHeight + self.childHeight * 0.5; 26 self.childs[i]:setPosition(x, self.innerSize.height - y); 27 self.childs[i].__pos = i - 1; 28 end 29 end 30 31 function scrollView:appendChild(child) 32 child:setAnchorPoint(cc.p(0.5, 0.5)); 33 self.childCount = self.childCount + 1; 34 self:addChild(child); 35 end 36 37 function scrollView:deleteChild(child) 38 self.childCount = self.childCount - 1; 39 self:removeChild(child); 40 end
scrollView 是指 ccui.ScrollView:create() 返回的对象.
可以通过一个工厂函数给对象扩展成员函数. 这个下面在贴代码.
使用方法就是, 在add, del之前调用 begin, 之后调用end.
begin和end的目的是, 避免每一次 add, del 都要全部排列节点, 并且省去了每次数值计算.
这段代码实现了ccui.ScrollView动态增加|删除子节点.
在end函数里面, 还调整了Layout的坐标, 每次修改不会察觉到Layout的坐标变化.
%26nbsp;
%26nbsp;
有些用ccui.ScrollView做城镇地图, 可以缩放, 顶点缩放.
直接缩放Layout会影响拖动效果, 这个问题直接修改引擎或者继承这个对象.
1 float Widget::getLeftBoundary() const 2 { 3 return getPosition().x - getAnchorPoint().x * _contentSize.width * _scaleX; 4 } 5 6 float Widget::getBottomBoundary() const 7 { 8 return getPosition().y - getAnchorPoint().y * _contentSize.height * _scaleY; 9 } 10 11 float Widget::getRightBoundary() const 12 { 13 return getLeftBoundary() + _contentSize.width * _scaleX; 14 } 15 16 float Widget::getTopBoundary() const 17 { 18 return getBottomBoundary() + _contentSize.height * _scaleY; 19 }
%26nbsp;
下面是顶点缩放, 直接缩放Layout会把锚点作为中心,
我们这个缩放也是以锚点作为中心, 但是会缩放的同时移动坐标, 效果就达到了.
1 -- 获取内容缩放值. 2 function scrollView:getInnerContainerScale() 3 return self:getInnerContainer():getScale(); 4 end 5 6 -- 获取内容高宽. 7 function scrollView:getInnerContainerSize() 8 return self:getInnerContainer():getContentSize(); 9 end 10 11 -- 获取内容位置. 12 function scrollView:getInnerContainerPosition() 13 return cc.p(self:getInnerContainer():getPosition()); 14 end 15 16 -- 坐标转换为内容内部坐标. 17 function scrollView:convertToInnerContainer(point) 18 return self:getInnerContainer():convertToNodeSpace(point); 19 end 20 21 function scrollView:setInnerContainerScale(scale) 22 self:getInnerContainer():setScale(scale); 23 end 24 25 function scrollView:setInnerContainerSize(size) 26 self:getInnerContainer():setContentSize(size); 27 end 28 29 -- 设置内容位置. 30 function scrollView:setInnerContainerPosition(point) 31 self:getInnerContainer():setPosition(point); 32 end 33 34 -- 矫正内容位置. 35 function scrollView:adjustmentInnerContainerPosition() 36 local curPoint = self:getInnerContainerPosition(); 37 local viewSize = self:getContentSize(); 38 local innerSize = self:getInnerContainerSize(); 39 local curScale = self:getInnerContainerScale(); 40 41 if curPoint.x %26gt; 0 then curPoint.x = 0 end; 42 if curPoint.y %26gt; 0 then curPoint.y = 0 end; 43 if curPoint.x %26lt; viewSize.width - innerSize.width * curScale then 44 curPoint.x = viewSize.width - innerSize.width * curScale; 45 end 46 if curPoint.y %26lt; viewSize.height - innerSize.height * curScale then 47 curPoint.y = viewSize.height - innerSize.height * curScale; 48 end 49 50 self:setInnerContainerPosition(curPoint); 51 end 52 53 -- 定点缩放. 54 function scrollView:scaleByPoint(worldPoint, scale) 55 local function callChildScaleHandler(node, scale) 56 for k, child in pairs(node:getChildren()) do 57 if child.onScaleHandler then 58 child:onScaleHandler(scale); 59 end 60 callChildScaleHandler(child, scale); 61 end 62 end 63 64 local viewSize = self:getContentSize(); 65 local curScale = self:getInnerContainerScale(); 66 local localPoint = self:convertToInnerContainer(worldPoint); 67 68 -- 计算缩放. 69 local newScale = curScale + scale; 70 if newScale %26lt; 0.5 then newScale = 0.5 end; 71 if newScale %26gt; 2 then newScale = 2 end; 72 73 -- 实际增加的缩放值. 74 scale = scale - ((curScale + scale) - newScale); 75 if scale ~= 0 then 76 local diffWidth = localPoint.x * scale; 77 local diffHeight = localPoint.y * scale; 78 local curPoint = self:getInnerContainerPosition(); 79 curPoint.x = curPoint.x - diffWidth; 80 curPoint.y = curPoint.y - diffHeight; 81 82 self:setInnerContainerScale(newScale); 83 self:setInnerContainerPosition(curPoint); 84 self:adjustmentInnerContainerPosition(); 85 86 callChildScaleHandler(self, newScale); 87 end 88 end
callChildScaleHandler 这个函数是后来补充上的,
因为父节点缩放会导致子节点缩放,
地图上摆放房子, 房子上会有名字. scrollView是父节点, 房子摆在上面就是子节点, 房子上的名字也是子节点.
如果不做控制, 名字也会跟着缩放, 于是就看不见了, 或者模糊了.
然后通过callChildScaleHandler递归调用子节点onScaleHandler函数.
子节点在父节点每次缩放时处理这个缩放值.
1 function utils.transformScrollView(scrollView) 2 3 -- 获取内容缩放值. 4 function scrollView:getInnerContainerScale() 5 return self:getInnerContainer():getScale(); 6 end 7 8 -- 获取内容高宽. 9 function scrollView:getInnerContainerSize() 10 return self:getInnerContainer():getContentSize(); 11 end 12 13 -- 获取内容位置. 14 function scrollView:getInnerContainerPosition() 15 return cc.p(self:getInnerContainer():getPosition()); 16 end 17 18 -- 坐标转换为内容内部坐标. 19 function scrollView:convertToInnerContainer(point) 20 return self:getInnerContainer():convertToNodeSpace(point); 21 end 22 23 function scrollView:setInnerContainerScale(scale) 24 self:getInnerContainer():setScale(scale); 25 end 26 27 function scrollView:setInnerContainerSize(size) 28 self:getInnerContainer():setContentSize(size); 29 end 30 31 -- 设置内容位置. 32 function scrollView:setInnerContainerPosition(point) 33 self:getInnerContainer():setPosition(point); 34 end 35 36 -- 矫正内容位置. 37 function scrollView:adjustmentInnerContainerPosition() 38 local curPoint = self:getInnerContainerPosition(); 39 local viewSize = self:getContentSize(); 40 local innerSize = self:getInnerContainerSize(); 41 local curScale = self:getInnerContainerScale(); 42 43 if curPoint.x %26gt; 0 then curPoint.x = 0 end; 44 if curPoint.y %26gt; 0 then curPoint.y = 0 end; 45 if curPoint.x %26lt; viewSize.width - innerSize.width * curScale then 46 curPoint.x = viewSize.width - innerSize.width * curScale; 47 end 48 if curPoint.y %26lt; viewSize.height - innerSize.height * curScale then 49 curPoint.y = viewSize.height - innerSize.height * curScale; 50 end 51 52 self:setInnerContainerPosition(curPoint); 53 end 54 55 -- 定点缩放. 56 function scrollView:scaleByPoint(worldPoint, scale) 57 local function callChildScaleHandler(node, scale) 58 for k, child in pairs(node:getChildren()) do 59 if child.onScaleHandler then 60 child:onScaleHandler(scale); 61 end 62 callChildScaleHandler(child, scale); 63 end 64 end 65 66 local viewSize = self:getContentSize(); 67 local curScale = self:getInnerContainerScale(); 68 local localPoint = self:convertToInnerContainer(worldPoint); 69 70 -- 计算缩放. 71 local newScale = curScale + scale; 72 if newScale %26lt; 0.5 then newScale = 0.5 end; 73 if newScale %26gt; 2 then newScale = 2 end; 74 75 -- 实际增加的缩放值. 76 scale = scale - ((curScale + scale) - newScale); 77 if scale ~= 0 then 78 local diffWidth = localPoint.x * scale; 79 local diffHeight = localPoint.y * scale; 80 local curPoint = self:getInnerContainerPosition(); 81 curPoint.x = curPoint.x - diffWidth; 82 curPoint.y = curPoint.y - diffHeight; 83 84 self:setInnerContainerScale(newScale); 85 self:setInnerContainerPosition(curPoint); 86 self:adjustmentInnerContainerPosition(); 87 88 callChildScaleHandler(self, newScale); 89 end 90 end 91 92 -- 将内容位置移至中心. 93 function scrollView:lockByPoint(point, isFade) 94 local viewSize = self:getContentSize(); 95 local viewCenter = cc.size(viewSize.width / 2, viewSize.height / 2); 96 local curPoint = self:getInnerContainerPosition(); 97 curPoint.x = curPoint.x - (curPoint.x + point.x - viewCenter.width); 98 curPoint.y = curPoint.y - (curPoint.y + point.y - viewCenter.height); 99 100 if isFade then 101 -- 这里可以实现一个移动动画. 102 else 103 self:setInnerContainerPosition(curPoint); 104 self:adjustmentInnerContainerPosition(); 105 end 106 end 107 108 -- 增加, 删除. 109 function scrollView:beginEditChilds(childWidth, childHeight) 110 self.contentSize = self:getContentSize(); 111 self.childCount = #(self:getChildren()); 112 self.colCount = math.floor(self.contentSize.width / childWidth); 113 self.childWidth = (self.contentSize.width - childWidth * self.colCount) / (self.colCount + 1) + childWidth; 114 self.childHeight = childHeight; 115 self.innerSize = self:getInnerContainerSize(); 116 self.innerOffsetY = self:getInnerContainerPosition().y + self.innerSize.height - self.contentSize.height; 117 end 118 119 function scrollView:endEditChilds() 120 local rowCount = math.ceil(self.childCount / self.colCount); 121 self.innerSize.height = math.max(self.contentSize.height, rowCount * self.childHeight); 122 self:setInnerContainerSize(self.innerSize); 123 self:setInnerContainerPosition( 124 cc.p(0, math.min(0, self.contentSize.height + self.innerOffsetY - self.innerSize.height))); 125 126 local offsetY = self.innerSize.height - self.childHeight; 127 self.childs = self:getChildren(); 128 for i = self.childCount, 1, -1 do 129 local row = math.floor((i - 1) / self.colCount); 130 local col = math.floor((i - 1) % self.colCount); 131 local x = col * self.childWidth + self.childWidth * 0.5; 132 local y = row * self.childHeight + self.childHeight * 0.5; 133 self.childs[i]:setPosition(x, self.innerSize.height - y); 134 self.childs[i].__pos = i - 1; 135 end 136 end 137 138 function scrollView:appendChild(child) 139 child:setAnchorPoint(cc.p(0.5, 0.5)); 140 self.childCount = self.childCount + 1; 141 self:addChild(child); 142 end 143 144 function scrollView:deleteChild(child) 145 self.childCount = self.childCount - 1; 146 self:removeChild(child); 147 end 148 149 end
这是完整的代码.%26nbsp;全部函数封装在utils.transformScrollView工厂函数中.
%26nbsp;
这里面还少了一个手势缩放的功能.
这个功能只有城镇会用到, 所以我把它单独移出来.
同样的, 写一个独立的工厂函数, 为scrollView扩展.
1 function utils.transformScaleScrollView(parent, scrollView) 2 local touchs = {}; 3 local preDistance = 0; 4 local function onTouchBegan(touch, event) 5 local touchId = touch:getId(); 6 local point = touch:getLocation(); 7 if touchs[1] == nil then 8 touchs[1] = {id = touchId, point = point}; 9 elseif touchs[2] == nil then 10 touchs[2] = {id = touchId, point = point}; 11 preDistance = cc.pGetDistance(touchs[1].point, touchs[2].point); 12 end 13 return touchs[1] and touchId == touchs[1].id or touchs[2] and touchId == touchs[2].id; 14 end 15 16 local function onTouchMoved(touch, event) 17 local touchId = touch:getId(); 18 if touchId == touchs[1].id then 19 touchs[1].point = touch:getLocation(); 20 else 21 touchs[2].point = touch:getLocation(); 22 end 23 24 if touchs[1] and touchs[2] then 25 local distance = cc.pGetDistance(touchs[1].point, touchs[2].point); 26 local diff = distance - preDistance; 27 local curScale = scrollView:getInnerContainerScale(); 28 local lockPos = cc.pMidpoint(touchs[1].point, touchs[2].point); 29 scrollView:scaleByPoint(lockPos, diff * 0.0025); 30 preDistance = distance; 31 end 32 end 33 34 local function onTouchEnded(touch, event) 35 local touchId = touch:getId(); 36 if touchs[1].id == touchId then 37 touchs[1] = touchs[2]; 38 end 39 touchs[2] = nil; 40 end 41 42 -- 手指点击缩放. 43 local listener = cc.EventListenerTouchOneByOne:create(); 44 listener:registerScriptHandler(onTouchBegan, cc.Handler.EVENT_TOUCH_BEGAN); 45 listener:registerScriptHandler(onTouchMoved, cc.Handler.EVENT_TOUCH_MOVED); 46 listener:registerScriptHandler(onTouchEnded, cc.Handler.EVENT_TOUCH_ENDED); 47 listener:registerScriptHandler(onTouchEnded, cc.Handler.EVENT_TOUCH_CANCELLED); 48 cc.Director:getInstance():getEventDispatcher():addEventListenerWithSceneGraphPriority(listener, parent); 49 end
原理很简单, 确定两点,
已两点的中心为缩放点,
已当前移动的距离和上次移动的距离只差为缩放值, 这个缩放值可能有点大, 我把它乘以 0.0025, 效果刚刚好.
然后就可以实现海盗旗兵那种手势缩放效果了...
%26nbsp;
%26nbsp;