【h5游戏开发】egret引擎p2物理引擎(2) - 小球碰撞地面搞笑的物理现象

  • 重力的方向和地面的问题

    p2中默认的方向是从上到下,如果重力默认是正数的话,物体放到世界中是会从上面往下面飘的

    p2plane地面默认的方向是y轴的方向,而在p2y轴的方向默认是从上往下

    首先来看,重力gravity是正数,并且plane地面在视图中间,小球是怎么运动

    // 创建世界
        this.world = new p2.World()
        this.world.sleepMode = p2.World.BODY_SLEEPING
        this.world.gravity = [0,5]
        // 设置弹性属性
        this.world.defaultContactMaterial.restitution = 1;
    
        // 创建地面
        this.planeBody = new p2.Body({
          position:[egret.MainContext.instance.stage.stageWidth/2,egret.MainContext.instance.stage.stageHeight/2]
        })
        let plane = new p2.Plane()
    

    4.gif
    image

    小球受重力的影响从上往下掉,没有问题,并且即使是在地面下面,地面的方向也是从上往下,小球下到地面之后依旧是从上往下运动

    下面把世界的重力改成从下往上,相当于漂浮的感觉

    5.gif
    image

    可以看到plane下方,也就是在p2引擎中的y轴正方向地面上的小球依照物理定律,自由落地,从下往上落到地面上

    plane上方,也就是p2物理引擎中的地下世界,是会奇怪的往地面上跑

    总体来说:在p2物理世界中,地面上的物体,遵循自由落体和牛顿引力相关运动原理,但是默认方向是从上往下的Y轴方向,所以如果地面在最下面的话,需要将plane地面水平翻转180‘,并且此时重力可以设置为正数,达到从上往下的效果

    正确的效果是这样的:

    6.gif
    image

    实现代码

    // 创建世界
        this.world = new p2.World()
        this.world.sleepMode = p2.World.BODY_SLEEPING
        this.world.gravity = [0,5]
        // 设置弹性属性
        this.world.defaultContactMaterial.restitution = 1;
    
        // 创建地面
        this.planeBody = new p2.Body({
          position:[egret.MainContext.instance.stage.stageWidth/2,egret.MainContext.instance.stage.stageHeight/2]
        })
        let plane = new p2.Plane()
        this.planeBody.angle = Math.PI  // 地面需要旋转180度,不然是从上往下
    
  • 生成阻碍方块,并且不允许方块有重叠区域

    实现原理

    纵坐标可以设定在同一水平线上,横坐标位置随机,通过随机数生成位置,并且通过随机数确定方块的大小,生成第二个随机方块的时候判断时候与前面生成的所有方块有重叠部分` 半径 只差的绝对值 < 半径值和`, 如果有重叠部分的话,嵌套调用,如果没有的话,即生成新的方块
    

    实现代码

    private randomNum(minNum,maxNum):number{  // 生成随机数的方法,借鉴自菜鸟教程
        switch(arguments.length){ 
            case 1: 
                return parseInt(Math.random() * minNum+1 + '',10); 
            break; 
            case 2: 
                return parseInt(Math.random()*(maxNum-minNum+1)+minNum,10); 
            break; 
                default: 
                    return 0; 
                break; 
        } 
      } 
      
      
      
      private genereteBox():void{
        /**
         * 生成随机个障碍盒子
         */
        let stageHeight = egret.MainContext.instance.stage.stageHeight  // 获取屏幕高度
        let stageWidth = egret.MainContext.instance.stage.stageWidth  // 获取屏幕宽度
        
        let count = this.randomNum(1,4) // 生成的障碍个数
    
        let arr = []
    
    
        let generatePosition = ()=>{  // 保证this指向问题
          let size = this.randomNum(30,70)
          let location  = this.randomNum(size,stageWidth-size)
          if(arr.length === 0){
            arr.push({
              size:size,
              location:location
            })
            return [size,location]
          }else{
            for(let index = 0;index<arr.length;index++){
              if((Math.abs(location-arr[index].location)) < (size + arr[index].size)){
                console.log(size,location)
                return generatePosition()
              }
            }
            arr.push({
              size:size,
              location:location
            })
            return [size,location]
          }
        }
        for(let index = 0;index<count;index++){
          let [size,location] = generatePosition()
          let react:egret.Shape = this.createBox(size)
          let reactShape: p2.Shape = new p2.Box({
            width:size,
            height:size
          })
          let reactBody:p2.Body = new p2.Body({
            type:p2.Body.STATIC,
            position:[
              location,stageHeight-size
            ],
          })
          reactBody.addShape(reactShape)
          reactBody.displays = [react]
          this.world.addBody(reactBody)
        } 
      }
    
  • 点击生成小球逻辑

    实现逻辑

    1.生成小球刚体,形状,及绑定贴图
    
    2.生成小球的位置为点击事件的位置
    
    3.绑定到`egret`的点击事件上
    

    实现代码

    //步骤1,2
    private generateBall(locationX:number,locationY:number):void{
        var ball:egret.Shape
        var ballShape:p2.Shape
        var ballBody:p2.Body
    
        ball = this.createBall()
        ballShape = new p2.Circle({
          radius:10
        })
        ballBody = new p2.Body({
          position:[locationX,locationY],
          mass:50,
          overlaps:false
        })
        ball.width = (<p2.Circle>ballShape).radius*2
        ball.height = (<p2.Circle>ballShape).radius*2
        ball.anchorOffsetX = ball.width/2
        ball.anchorOffsetY = ball.height/2
        ballBody.addShape(ballShape)
        ballBody.displays = [ball]
        this.world.addBody(ballBody)
      }
      
    // 步骤3
    
      this.addEventListener(egret.TouchEvent.TOUCH_TAP,(e:egret.TouchEvent)=>{
        this.generateBall(e.localX,e.localY)    // e.localX和e.localY 可以拿到点击事件的点击X或者y
      },this)
    

  • 小球碰撞出现锚点问题,即碰撞不是发生在边缘,而是感觉发生在球心或者什么位置

    原因分析

    1.可能是因为贴图的视觉中心和 刚体的中心没有设置在一起
    
    在`egret`贴图的中心,默认是在坐标左上角
    
    但是在`p2`物理引擎中,默认的图形锚点确是在刚体的中心
    
    所以在使用两个合并时需要将贴图的锚点中心设置到和刚体中心一样
    
    ![截图](attachment:8f15efc90e41b0d12e13731386c191a1)
    

    代码如下

    
    private createBox(width:number,angle:number):egret.Shape{
        var box = new egret.Shape()
        box.graphics.beginFill(0x0000ff)
        box.graphics.drawRect(0,0,width,width)
        box.graphics.endFill()
        box.anchorOffsetX = box.width/2
        box.anchorOffsetY = box.height/2
        box.rotation = angle  * 180 / Math.PI;
        console.log('box rotation : ' + box.rotation)
        this.addChild(box)
        return box
      }
    
  • 小球和方块出现重叠的现象,而且碰撞中心感觉只是在小球的中心点,并不是小球的边缘碰撞

    问题现象

    ![3.gif](attachment:774c5f928557ca980283141254dba5f8)
    

    检查原因

    原来是小球贴图`egret.shape`和小球物理引擎中刚体的大小不一样`p2.body`
    
    小球的刚体结构实际上是在贴图内部的一个小区域
    

    代码如下

    //修改后
    private generateBall(locationX:number,locationY:number):void{
        var ball:egret.Shape
        var ballShape:p2.Shape
        var ballBody:p2.Body
        var size:number  = 10
        ball = this.createBall(size*2)  // size是刚体半径的大小,生成小球贴图的时候需要两倍大小,因为egret drawCirle是直径
        ballShape = new p2.Circle({
          radius:size
        })
        ballBody = new p2.Body({
          position:[locationX,locationY],
          mass:50
        })
        ball.width = (<p2.Circle>ballShape).radius*2  // 小球宽度也是直径
        ball.height = (<p2.Circle>ballShape).radius*2 // 小球高度页是直径
        ball.anchorOffsetX = ball.width/2 //参考下一个问题
        ball.anchorOffsetY = ball.height/2
        ballBody.addShape(ballShape)
        ballBody.displays = [ball]
        this.world.addBody(ballBody)
      }
    
  • 贴图的旋转和刚体的旋转数值明明设置的是一样的,但是旋转角度和碰撞反应感觉角度并不正确

    问题原因:

    `egret`中表示旋转的`rotate`属性是弧度制
    
    而 `p2`中表示旋转的`angle`属性是角度制,即在`p2`中写多少度就是旋转多少度,但是在`egret`的贴图中需要将角度转换为弧度
    
    转换公式如下
    

数学上是用弧度而非角度,因为360的容易整除对数学不重要,而数学使用弧度更方便。角度和弧度关系是:2π弧度=360°。从而1°≈0.0174533弧度,1弧度≈57.29578°。

  1. 角度转换为弧度公式:弧度=角度÷180×π

2)弧度转换为角度公式: 角度=弧度×180÷π


**    代码如下:**

```typescript
protected freshFrame():void{
    this.world.step(1)
    var len:number = this.world.bodies.length
    for(let index = 0;index<len;index++){
      var body:p2.Body = this.world.bodies[index]
      var display: egret.DisplayObject = body.displays[0]
      display.x = body.position[0]
      display.y = body.position[1]
      display.rotation = body.angle * 180/ Math.PI
    }
  }

  • 在两侧填上阻止的墙壁,形成密闭的环境

posted @ 2022-02-08 22:49  三十男  阅读(758)  评论(0编辑  收藏  举报