大多数游戏都有背包这个东西.
道具列表通常用 ScrollView 来实现.
这个ScrollView内部有一个Layout, 滑动都是由移动这个Layout来实现.
道具摆放通常从上往下, 从左到右.
假设你有一个道具数组, 你遍历这个数组来摆放道具.
因为数组长度是已知的, 你可以计算出Layout需要的尺寸, 再把道具摆上去.
这个实现是很容易的. 但是, 如果你提前不知道数组长度, 就是不知道道具数量,
可能随时会添加道具或者删除道具.
因为cocos2dx的坐标系是左下角为原点, 因此动态增加或删除都需要把所有的道具都移动位置,
光移动Layout是不行的.
说了一堆的废话.
-- 增加, 删除. function scrollView:beginEditChilds(childWidth, childHeight) self.contentSize = self:getContentSize(); self.childCount = #(self:getChildren()); self.colCount = math.floor(self.contentSize.width / childWidth); self.childWidth = (self.contentSize.width - childWidth * self.colCount) / (self.colCount + ) + childWidth; self.childHeight = childHeight; self.innerSize = self:getInnerContainerSize(); self.innerOffsetY = self:getInnerContainerPosition().y + self.innerSize.height - self.contentSize.height; end function scrollView:endEditChilds() local rowCount = math.ceil(self.childCount / self.colCount); self.innerSize.height = math.max(self.contentSize.height, rowCount * self.childHeight); self:setInnerContainerSize(self.innerSize); self:setInnerContainerPosition( cc.p(, , self.contentSize.height + self.innerOffsetY - self.innerSize.height))); local offsetY = self.innerSize.height - self.childHeight; self.childs = self:getChildren(); , - do ) / self.colCount); ) % self.colCount); local x = col * self.childWidth + self.childWidth * 0.5; local y = row * self.childHeight + self.childHeight * 0.5; self.childs[i]:setPosition(x, self.innerSize.height - y); self.childs[i].__pos = i - ; end end function scrollView:appendChild(child) child:setAnchorPoint(cc.p(0.5, 0.5)); self.childCount = self.childCount + ; self:addChild(child); end function scrollView:deleteChild(child) self.childCount = self.childCount - ; self:removeChild(child); end
scrollView 是指 ccui.ScrollView:create() 返回的对象.
可以通过一个工厂函数给对象扩展成员函数. 这个下面在贴代码.
使用方法就是, 在add, del之前调用 begin, 之后调用end.
begin和end的目的是, 避免每一次 add, del 都要全部排列节点, 并且省去了每次数值计算.
这段代码实现了ccui.ScrollView动态增加|删除子节点.
在end函数里面, 还调整了Layout的坐标, 每次修改不会察觉到Layout的坐标变化.
有些用ccui.ScrollView做城镇地图, 可以缩放, 顶点缩放.
直接缩放Layout会影响拖动效果, 这个问题直接修改引擎或者继承这个对象.
float Widget::getLeftBoundary() const { return getPosition().x - getAnchorPoint().x * _contentSize.width * _scaleX; } float Widget::getBottomBoundary() const { return getPosition().y - getAnchorPoint().y * _contentSize.height * _scaleY; } float Widget::getRightBoundary() const { return getLeftBoundary() + _contentSize.width * _scaleX; } float Widget::getTopBoundary() const { return getBottomBoundary() + _contentSize.height * _scaleY; }
下面是顶点缩放, 直接缩放Layout会把锚点作为中心,
我们这个缩放也是以锚点作为中心, 但是会缩放的同时移动坐标, 效果就达到了.
-- 获取内容缩放值. function scrollView:getInnerContainerScale() return self:getInnerContainer():getScale(); end -- 获取内容高宽. function scrollView:getInnerContainerSize() return self:getInnerContainer():getContentSize(); end -- 获取内容位置. function scrollView:getInnerContainerPosition() return cc.p(self:getInnerContainer():getPosition()); end -- 坐标转换为内容内部坐标. function scrollView:convertToInnerContainer(point) return self:getInnerContainer():convertToNodeSpace(point); end function scrollView:setInnerContainerScale(scale) self:getInnerContainer():setScale(scale); end function scrollView:setInnerContainerSize(size) self:getInnerContainer():setContentSize(size); end -- 设置内容位置. function scrollView:setInnerContainerPosition(point) self:getInnerContainer():setPosition(point); end -- 矫正内容位置. function scrollView:adjustmentInnerContainerPosition() local curPoint = self:getInnerContainerPosition(); local viewSize = self:getContentSize(); local innerSize = self:getInnerContainerSize(); local curScale = self:getInnerContainerScale(); end; end; if curPoint.x < viewSize.width - innerSize.width * curScale then curPoint.x = viewSize.width - innerSize.width * curScale; end if curPoint.y < viewSize.height - innerSize.height * curScale then curPoint.y = viewSize.height - innerSize.height * curScale; end self:setInnerContainerPosition(curPoint); end -- 定点缩放. function scrollView:scaleByPoint(worldPoint, scale) local function callChildScaleHandler(node, scale) for k, child in pairs(node:getChildren()) do if child.onScaleHandler then child:onScaleHandler(scale); end callChildScaleHandler(child, scale); end end local viewSize = self:getContentSize(); local curScale = self:getInnerContainerScale(); local localPoint = self:convertToInnerContainer(worldPoint); -- 计算缩放. local newScale = curScale + scale; if newScale < 0.5 then newScale = 0.5 end; end; -- 实际增加的缩放值. scale = scale - ((curScale + scale) - newScale); then local diffWidth = localPoint.x * scale; local diffHeight = localPoint.y * scale; local curPoint = self:getInnerContainerPosition(); curPoint.x = curPoint.x - diffWidth; curPoint.y = curPoint.y - diffHeight; self:setInnerContainerScale(newScale); self:setInnerContainerPosition(curPoint); self:adjustmentInnerContainerPosition(); callChildScaleHandler(self, newScale); end end
callChildScaleHandler 这个函数是后来补充上的,因为父节点缩放会导致子节点缩放,地图上摆放房子, 房子上会有名字. scrollView是父节点, 房子摆在上面就是子节点, 房子上的名字也是子节点.如果不做控制, 名字也会跟着缩放, 于是就看不见了, 或者模糊了.然后通过callChildScaleHandler递归调用子节点onScaleHandler函数.子节点在父节点每次缩放时处理这个缩放值.
function utils.transformScrollView(scrollView) -- 获取内容缩放值. function scrollView:getInnerContainerScale() return self:getInnerContainer():getScale(); end -- 获取内容高宽. function scrollView:getInnerContainerSize() return self:getInnerContainer():getContentSize(); end -- 获取内容位置. function scrollView:getInnerContainerPosition() return cc.p(self:getInnerContainer():getPosition()); end -- 坐标转换为内容内部坐标. function scrollView:convertToInnerContainer(point) return self:getInnerContainer():convertToNodeSpace(point); end function scrollView:setInnerContainerScale(scale) self:getInnerContainer():setScale(scale); end function scrollView:setInnerContainerSize(size) self:getInnerContainer():setContentSize(size); end -- 设置内容位置. function scrollView:setInnerContainerPosition(point) self:getInnerContainer():setPosition(point); end -- 矫正内容位置. function scrollView:adjustmentInnerContainerPosition() local curPoint = self:getInnerContainerPosition(); local viewSize = self:getContentSize(); local innerSize = self:getInnerContainerSize(); local curScale = self:getInnerContainerScale(); end; end; if curPoint.x < viewSize.width - innerSize.width * curScale then curPoint.x = viewSize.width - innerSize.width * curScale; end if curPoint.y < viewSize.height - innerSize.height * curScale then curPoint.y = viewSize.height - innerSize.height * curScale; end self:setInnerContainerPosition(curPoint); end -- 定点缩放. function scrollView:scaleByPoint(worldPoint, scale) local function callChildScaleHandler(node, scale) for k, child in pairs(node:getChildren()) do if child.onScaleHandler then child:onScaleHandler(scale); end callChildScaleHandler(child, scale); end end local viewSize = self:getContentSize(); local curScale = self:getInnerContainerScale(); local localPoint = self:convertToInnerContainer(worldPoint); -- 计算缩放. local newScale = curScale + scale; if newScale < 0.5 then newScale = 0.5 end; end; -- 实际增加的缩放值. scale = scale - ((curScale + scale) - newScale); then local diffWidth = localPoint.x * scale; local diffHeight = localPoint.y * scale; local curPoint = self:getInnerContainerPosition(); curPoint.x = curPoint.x - diffWidth; curPoint.y = curPoint.y - diffHeight; self:setInnerContainerScale(newScale); self:setInnerContainerPosition(curPoint); self:adjustmentInnerContainerPosition(); callChildScaleHandler(self, newScale); end end -- 将内容位置移至中心. function scrollView:lockByPoint(point, isFade) local viewSize = self:getContentSize(); , viewSize.height / ); local curPoint = self:getInnerContainerPosition(); curPoint.x = curPoint.x - (curPoint.x + point.x - viewCenter.width); curPoint.y = curPoint.y - (curPoint.y + point.y - viewCenter.height); if isFade then -- 这里可以实现一个移动动画. else self:setInnerContainerPosition(curPoint); self:adjustmentInnerContainerPosition(); end end -- 增加, 删除. function scrollView:beginEditChilds(childWidth, childHeight) self.contentSize = self:getContentSize(); self.childCount = #(self:getChildren()); self.colCount = math.floor(self.contentSize.width / childWidth); self.childWidth = (self.contentSize.width - childWidth * self.colCount) / (self.colCount + ) + childWidth; self.childHeight = childHeight; self.innerSize = self:getInnerContainerSize(); self.innerOffsetY = self:getInnerContainerPosition().y + self.innerSize.height - self.contentSize.height; end function scrollView:endEditChilds() local rowCount = math.ceil(self.childCount / self.colCount); self.innerSize.height = math.max(self.contentSize.height, rowCount * self.childHeight); self:setInnerContainerSize(self.innerSize); self:setInnerContainerPosition( cc.p(, , self.contentSize.height + self.innerOffsetY - self.innerSize.height))); local offsetY = self.innerSize.height - self.childHeight; self.childs = self:getChildren(); , - do ) / self.colCount); ) % self.colCount); local x = col * self.childWidth + self.childWidth * 0.5; local y = row * self.childHeight + self.childHeight * 0.5; self.childs[i]:setPosition(x, self.innerSize.height - y); self.childs[i].__pos = i - ; end end function scrollView:appendChild(child) child:setAnchorPoint(cc.p(0.5, 0.5)); self.childCount = self.childCount + ; self:addChild(child); end function scrollView:deleteChild(child) self.childCount = self.childCount - ; self:removeChild(child); end end
这是完整的代码. 全部函数封装在utils.transformScrollView工厂函数中.
这里面还少了一个手势缩放的功能.
这个功能只有城镇会用到, 所以我把它单独移出来.
同样的, 写一个独立的工厂函数, 为scrollView扩展.
function utils.transformScaleScrollView(parent, scrollView) local touchs = {}; ; local function onTouchBegan(touch, event) local touchId = touch:getId(); local point = touch:getLocation(); ] == nil then touchs[] = {id = touchId, point = point}; ] == nil then touchs[] = {id = touchId, point = point}; preDistance = cc.pGetDistance(touchs[].point, touchs[].point); end ] ].id ] ].id; end local function onTouchMoved(touch, event) local touchId = touch:getId(); ].id then touchs[].point = touch:getLocation(); else touchs[].point = touch:getLocation(); end ] ] then ].point, touchs[].point); local diff = distance - preDistance; local curScale = scrollView:getInnerContainerScale(); ].point, touchs[].point); scrollView:scaleByPoint(lockPos, diff * 0.0025); preDistance = distance; end end local function onTouchEnded(touch, event) local touchId = touch:getId(); ].id == touchId then touchs[] = touchs[]; end touchs[] = nil; end -- 手指点击缩放. local listener = cc.EventListenerTouchOneByOne:create(); listener:registerScriptHandler(onTouchBegan, cc.Handler.EVENT_TOUCH_BEGAN); listener:registerScriptHandler(onTouchMoved, cc.Handler.EVENT_TOUCH_MOVED); listener:registerScriptHandler(onTouchEnded, cc.Handler.EVENT_TOUCH_ENDED); listener:registerScriptHandler(onTouchEnded, cc.Handler.EVENT_TOUCH_CANCELLED); cc.Director:getInstance():getEventDispatcher():addEventListenerWithSceneGraphPriority(listener, parent); end
原理很简单, 确定两点,
已两点的中心为缩放点,
已当前移动的距离和上次移动的距离只差为缩放值, 这个缩放值可能有点大, 我把它乘以 0.0025, 效果刚刚好.
然后就可以实现海盗旗兵那种手势缩放效果了...