UI2D物理系统
我们有时会遇到在2D界面中模拟物理运动, 例如一个物体自由落体, 掉到斜坡上开始滑行, 最后因为摩擦力过大停止移动等等效果. 引擎提供了一套完善的2D物理系统.
我们可以设置物理场景的重力等属性, 然后将UI场景与物理场景进行绑定, 从而实现UI模拟物理运动的效果.
可以参考Box2D.
1. 名词解释
- Shape: 形状是2D几何对象, 常见的形状为圆形和多边形.
- Rigid Body: 刚体 一种坚固的物质, 用于在物理世界中表示物质.
- Fixture: 夹具 用于赋予物质的物理属性, 例如形状, 密度, 摩擦力, 弹性等属性.
2. 2D物理的使用
2.1. 实现原理
我们在UI场景中创建一个UI控件, 那么就在物理场景的相同位置创建一个和UI控件等大的形状, 在形状变化时同步UI控件的位置, 旋转和缩放等属性来实现UI控件模拟物理运动的功能.
2.2. 代码是这样写的
获取2D物理管理器
---获取引擎实例
local pEngineIns = xe.Director:GetInstance():GetEngine()
---通过引擎实例获取2D物理管理器
local p2DPhysicalManager = pEngineIns:Get2DPhysicalManager()
---初始化2D物理管理器
p2DPhysicalManager:Init()
创建物理场景
---创建物理场景
self.pPhyScene = p2DPhysicalManager:CreateScene("PhyWorld")
---设置场景重力
self.pPhyScene:SetGravity(XVECTOR2(0, -9.8))
创建一个形状
这里设置形状大小的时候建议根据UI控件大小按照一个比例设置形状的大小, 这里我们可以假设屏幕宽度对应物理场景为WORLD_WIDTH米, 那么UI控件在物理场景中的大小可以设置为vPhySize = vUISize * (UIWidth / WORLD_WIDTH)
---创建一个圆形
---@type IX2DCircleShape
local pCircleShape = X2DShapeFactory:CreateShape(IX2DShape.X_CIRCLE)
pCircleShape = XECast(pCircleShape, IX2DCircleShape.XType)
---设置圆形半径 这里WORLD_UI_SCALE = UIWidth / WORLD_WIDTH
pCircleShape:SetRadius(vSize.x * WORLD_UI_SCALE / 2)
创建一个夹具描述
创建夹具描述为了给刚体赋予物理性质. (这里给夹具描述设置形状)
---创建夹具描述 刚体和刚体接触后的参数
local pFixture = X2DFixtureDesc()
---设置形状
pFixture.pShape = pCircleShape
---设置密度
pFixture.fDensity = 1.0
---设置摩擦力
pFixture.fFriction = 0.05
---设置弹性
pFixture.fRestitution = 0.1
创建刚体
这里根据上边创建好的夹具描述创建一个刚体对象 在物理世界中参与物理运动.
这里在设置刚体位置的时候依旧需要UI场景的位置和物理场景位置的一个转换
---创建刚体描述
local pBodyDesc = X2DRigidBodyDesc()
---刚体类型 运动的
pBodyDesc.eType = X_DYNAMIC
---设置位置
---将球的位置和刚体位置放一起
pBodyDesc.vPosition = vUIPos2PhyPos
---根据描述创建刚体
local pRigidBody = self.pPhyScene:CreateRigidBody(pBodyDesc)
---设置夹具类型
pRigidBody:CreateFixture(pFixture)
将UI控件与刚体建立映射关系, 这里可以用一个table记录下来, 或者给UI控件添加一个Lua属性. (Lua语言是十分灵活的)
---记录刚体和XUINode的对应关系
self:BindRigid2UINode(pRigidBody, pUINode)
如果我们希望在游戏中区分刚体与刚体之间的碰撞, 例如玩家打怪, 怪物不希望碰到怪物, 但是玩家需要碰到怪物. 这时我们需要给夹具设置碰撞过滤.
在刚体主动运动时, 碰到其他刚体时会用自己设置的折罩标识位与对方的类别标识位进行与运算, 值大于0将进行碰撞, 如果值等于0则不会进行碰撞, 所以我们可以给每个刚体设置折罩标识位和类别标识位
---创建碰撞过滤类型 设置这个东西后刚体之间就可以进行合法的碰撞了, 再也不需要担心见谁碰谁了
local pFilter = X2DFilter()
---告诉别人 我是什么东西
pFilter.categoryBits = 2
---标记我想找谁碰瓷
pFilter.maskBits = 4
pFixture.filter = pFilter
设置了这么多, 我们终于把刚体创建好了, 接下来我们需要让物理场景和UI场景动起来!
---这是在onTick方法中
---这里我们得调用物理场景的Tick方法
self.pPhyScene:Tick(fDelta)
---然后通过刚体刷新控件位置
---这里通过物理场景与UI场景进行位置转换, 计算出UI控件的位置
for pRigidBody, pNode in pairs(self.tbRigid2UINode) do
local vPos = pRigidBody:GetPosition()
local fAngle = pRigidBody:GetAngle()
local nPosX = (WINDOW_WIDTH / WORLD_WIDTH) * (vPos.x)
local nPosY = (1 - vPos.y / (WORLD_WIDTH * WINDOW_HEIGHT / WINDOW_WIDTH)) * WINDOW_HEIGHT
local mt = pNode:GetLocalToScreenTransform():GetInverse()
local v3 = mt:TransformCoord(XVECTOR3(nPosX, nPosY, 1.0))
pNode:SetPosition(pNode:GetPosition() + XVECTOR2(v3.x, v3.y))
pNode:SetRotation(fAngle)
end
让我们看看效果!
(那个做自由落体的小球才是重点, 最后落在了下边的板上!)
有时我们需要给刚体赋予一个速度, 让它运动起来, 这边我们可以给刚体一个线速度和角速度
---设置一个初速度
pRigidBody:SetLinearVelocity(XVECTOR2(1.5, 0))
然后我们看看效果
可以发现, 我们的小球开始做平抛运动了!
另外, 我们可以给刚体设置一些其他的属性例如:
---线速度阻尼
pBodyDesc.fLinearDamping = 0
---角速度阻尼
pBodyDesc.fAngularDamping = 0
---修改重力缩放
pBodyDesc.fGravityScale = 1
---设置固定旋转
pBodyDesc.bFixedRotation = false