【h5遊戲開發】egret引擎p2物理引擎(2) - 小球碰撞地面搞笑的物理現象

Zmmm君發表於2022-02-08
  • 重力的方向和地面的問題

    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
    }
  }

  • 在兩側填上阻止的牆壁,形成密閉的環境

相關文章