微信小游戲egret開發包括p2引擎小結


用egret + p2 做一個類似投球的小游戲,坑大致如下:

1、p2引擎與egret坐標不同注意轉換,橫坐標沒什么,縱坐標egret.y = stageHeight - body.position[1]*factor

2、p2物體的原點為中心點,而egret顯示對象的原點為左上角,設置display.anchorOffsetX = display.width/2;display.anchorOffsetY = display.height/2

3、p2的單位與egret像素單位的factor設置為50,計算body坐標或display坐標時注意實時更新

4、p2的碰撞檢測代碼如下,注意一定要給body設置個id,而且id必須是number

this._world.on("beginContact", this.contact);
contact = (evt) => {
        var bodyA:p2.Body = evt.bodyA;
        var bodyB:p2.Body = evt.bodyB;

        if (!bodyA || !bodyB) {
            return;
        }
        var bodyIdA = bodyA.id;
        var bodyIdB = bodyB.id;
//這里一定要用id來確定碰撞的body
    }

  

5、摩擦系數、彈性等屬性可通過設置不同材質來設置,全局反彈系數用defaultContactMaterial,材質定義個id,然后body設置其material即可

private init(){
        // 創建物理世界
        this._world = new p2.World();
        this._world.gravity = [0, Constants.WORLD_GRAVITY];
        //默認所有彈性
        this._world.defaultContactMaterial.restitution = 0.5;
        //不同材質的系數
        let m1 = new p2.Material(Constants.BALL_MATERIAL); 
        let m2 = new p2.Material(Constants.NET_MATERIAL);
        let m3 = new p2.Material(Constants.BASKET_MATERIAL);        
        let m1_m2 = new p2.ContactMaterial(m1,m2,<p2.ContactMaterialOptions>{restitution:0.01,friction:0});
        this._world.addContactMaterial(m1_m2);
        let m1_m3 = new p2.ContactMaterial(m1,m3,<p2.ContactMaterialOptions>{restitution:0.6,friction:0.3});
        this._world.addContactMaterial(m1_m3);
    }

createBody(){
        // 創建球
        let box = new p2.Body({mass:1});      
        var boxShape:p2.Shape = new p2.Circle({
            radius: 10,
            material: new p2.Material(Constants.BALL_MATERIAL)
        });
        box.addShape(boxShape);
        let sp = new egret.Shape();
        sp.graphics.beginFill(0xfff000);
        sp.graphics.drawCircle(0,0,10);
        sp.graphics.endFill();
        sp.anchorOffsetX = sp.width/2;
        sp.anchorOffsetY = sp.height/2;
        box.displays = [sp];

        //創建籃筐
        let basket = new p2.Body({mass:1});      
        var basketShape:p2.Shape = new p2.Circle({
            radius: 10,
            material: new p2.Material(Constants.BASKET_MATERIAL)
        });
        basket.addShape(basketShape);
        let sp1 = new egret.Shape();
        sp1.graphics.beginFill(0xfff000);
        sp1.graphics.drawCircle(0,0,10);
        sp1.graphics.endFill();
        sp1.anchorOffsetX = sp1.width/2;
        sp1.anchorOffsetY = sp1.height/2;
        basket.displays = [sp1];
    }

6、可以將world.step()向前走一段時間,然后記錄剛體位置,畫出軌跡即可實現模擬運動,但是正常step的時候第一幀必須設置與模擬的step幀保持一致,否則可能會出現錯位和模擬與實際位置不符情況

/**
     * 模擬運動
     */
    private simulate_move():void {
        for (var i:number = 0; i < 10; i++) {
            this._world.step(40 / 1000);
            // 記錄位置
            this._ball.updateDisplayPosition();
        }
    }
//正常step
private loop(timestamp:number):boolean {
        var pass = 40;
        if (this._timestamp > 0) {
            pass = timestamp - this._timestamp;
        }

        this._timestamp = timestamp;
        if (pass < 10) {
            return;
        }

        if (pass > 1000) {
            return;
        }

        this.step(pass);
    }

7、可設置剛體的sleepSpeedLimit和sleepTimeLimit來判斷剛體是否可處於sleeping狀態,然后通過world.broadphase.result判斷sleeping狀態下與哪些body處於碰撞狀態,注意這個result是不斷變化的,最好在sleeping的第一次就判斷

if (this.sleepState == p2.Body.SLEEPING || this.sleepState == p2.Body.SLEEPY) {
                let arr = this.world.broadphase.result;
                let num = 0;
                for(let i = 0;i < arr.length;i++){
                    if(arr[i].id == Constants.BODY_ID.BALL){
                        num++;
                    }
                    if(arr[i].id == Constants.BODY_ID.BASKET_1 + Constants.BODY_ID.NET){
                        num++;
                    }
                    if(arr[i].id == Constants.BODY_ID.BASKET_1 + Constants.BODY_ID.TOPMASK){
                        num++;
                    }
                    
                }
                if(num >= 3){
                    console.log("sleepy and reset 了");
                    this.resetStatus();
                }
            }

8、可通過applyImpulse給body一個初始速度,來實現發射狀態,鋼體之間碰撞與不碰撞用this._world.enableBodyCollision(body1, body2);和this._world.disableBodyCollision(body1,body2);來設置

9、微信小游戲的sharedcanvas繪制可能會閃屏,解決方法game.js和egret都設置成60幀

10、微信小游戲sharedCanvas繪畫的時候最好用相對坐標,不然可能會出現縮放的問題,取windowWidth和windowHeight,然后根據主屏幕的寬高比計算排行榜的大小和位置,接着進行相對運算,下面的代碼是根據

屏幕寬度適配的(fixedWidth),為防止繪畫出現高低不齊,故保持了寬高比的windowHeight,這里的排行榜我是放到egret的一個UI里的,不是主屏幕,如果是整個貼在主屏幕則可以不用保持主舞台寬高比的windowHeight,

以下是整個openDataContext的index.js

/**
 * 資源加載組,將所需資源地址以及引用名進行注冊
 * 之后可通過assets.引用名方式進行獲取
 */
const assetsUrl = {
  line:"openDataContext/assets/line.png"
};

class openDataContextMain{
  constructor(){
    this.init();
  }
  
  init(){
    //獲取canvas渲染上下文
    this.context = sharedCanvas.getContext("2d");
    this.context.globalCompositeOperation = "source-over";
    this.sWidth = sharedCanvas.width;
    //根據寬度適配的 防止畫的排行榜出現向下錯位
    this.sHeight = this.sWidth*1330/750;
    this.assets = {};
    this.userData = null;
    this.friendData = null;
    this.groupData = null;
    if(!this.hasLoadRes){
      this.preloadAssets();
    }
    else{
      this.addOpenDataContextListener();
    }
  }
  //資源加載
  preloadAssets(){
    let preloaded = 0;
    let count = 0;
    for (let asset in assetsUrl) {
      count++;
      const img = wx.createImage();
      img.onload = () => {
        preloaded++;
        if (preloaded == count) {
          // console.log("加載完成");
          this.hasLoadRes = true;
          this.addOpenDataContextListener();
        }
      }
      img.src = assetsUrl[asset];
      this.assets[asset] = img;
    }
  }
  //添加監聽
  addOpenDataContextListener() {
    console.log('增加監聽函數')
    wx.onMessage((data) => {
      console.log(data);
      if(data.command == 'init'){
        //獲取用戶游戲信息
        this.ownOpenId = data.openid;
        
        //獲取用戶好友游戲信息
        if(!this.friendData){
          wx.getFriendCloudStorage({
            keyList:['score'],
            complete: res => {
              if (res.errMsg == "getFriendCloudStorage:ok"){
                this.friendData = this.sortFriendData(res.data);
              }
            }
          });
        }
        //獲取用戶群組里的排行
        // if(!this.groupData){
        //   wx.getGroupCloudStorage({
        //     success: res => {
        //       if (res.errMsg == "getGroupCloudStorage:ok"){
        //         this.groupData = data;
        //       }
        //     },
        //     fail:error=>{
        //       console.log(error);
        //     }
        //   });
        // }
      }
      else if (data.command == 'friend') {
          this.drawFriendRank(data.page);
      } else if (data.command == 'group') {
        this.drawGroupRank(data.page);
      }
    });
  }
  //對好友數據進行排序組裝
  sortFriendData(arr){
    if(!arr || arr.length == 0) return null;
    let len = arr.length;
    for(let i = 0;i < len;i++){
      let data = arr[i];
      let klist = data.KVDataList;
      for(let j = 0;j<klist.length;j++){
        let obj = klist[j];
        if(obj.key == 'score'){
          data[obj.key] = obj.value;
          break;
        }
      }
    }
    //排序
    arr.sort((a,b)=>{
      if (a.score > b.score) return -1;
      if (a.score < b.score) return 1;
      return 0;
    });

    return arr;
  }
  //獲取自己的排名信息
  getOwnRank(){
    let len = this.friendData.length;
    for(let i = 0;i < len;i++){
      if (this.friendData[i].openid == this.ownOpenId){
        return [i+1,this.friendData[i]];
      }
    }
    return null;
  }
  //畫好友排行榜
  drawFriendRank(page = 1){
    this.context.clearRect(0,0,this.context.canvas.width,this.context.canvas.height);
    if(!this.friendData || this.friendData.length == 0) return;
    let len = this.friendData.length;
    if(page > Math.ceil(len/7)){
      page--;
    }
    
    let itemWidth = this.sWidth * 2/3;
    let itemHeight = this.sHeight * 900/1330;
    let mid = itemHeight/9.8;
    let lx = itemWidth/600;
    let ly = mid/10;
    let fontSize = itemWidth/25;
    let i = (page-1)*7;
    let max = page*7;
    for(;i < max;i++){
      let obj = this.friendData[i];
      if(!obj) break;
      let yy = (i%7) * mid;
      this.context.fillStyle = '#65b5f7';
      this.context.font = fontSize + 'px Arial bold';
      this.context.textAlign = 'center';
      this.context.fillText(''+(i+1),20*lx,yy+ly*5);
      let image = wx.createImage();
      image.src = obj.avatarUrl;
      this.drawImage(image,80*lx,yy + ly*1,70*lx,70*lx);
      this.context.fillStyle = '#a1a1a1';
      this.context.fillText(obj.nickname,250*lx,yy+5*ly);
      this.context.fillText(obj.KVDataList[0].value,500*lx,yy+5*ly);
      this.drawImage(this.assets['line'], 0, yy+ly*9,560*lx,ly/5);
    }

    //繪制自己的排名
    let rank = this.getOwnRank();
    if(!rank) return;
    let robj = rank[1];
    let myy = 8.4*mid;
    this.context.fillText('' + rank[0], 20 * lx, myy + ly * 5);
    let image = wx.createImage();
    image.src = robj.avatarUrl;
    this.drawImage(image, 80 * lx, myy + ly * 1, 70 * lx, 70 * lx);
    this.context.fillStyle = '#a1a1a1';
    this.context.fillText(robj.nickname, 250 * lx, myy + 5 * ly);
    this.context.fillText(robj.KVDataList[0].value, 500 * lx, myy + 5 * ly);
    this.drawImage(this.assets['line'], 0, myy + ly * 9, 560 * lx, ly / 5);
  }
  //畫群排行榜
  drawGroupRank(page = 1){
    this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height);
    this.context.fillStyle = '#ff0000';
    this.context.font = '50px Arial';
    this.context.fillText('世界排行榜', 10, 10);
  }
  //畫圖片
  drawImage(image, x, y, width, height) {
    if (image.width != 0 && image.height != 0 && this.context)     {
      if (width && height) {
        this.context.drawImage(image, x, y, width, height);
      } else {
        this.context.drawImage(image, x, y);
      }
    }
  }
}

const openDCM = new openDataContextMain();
openDCM.addOpenDataContextListener();

11、微信的授權現在改成必須通過授權按鈕才能授權,很多情況下,與后端通信的邏輯應該是先獲取openId,然后傳給自己的服務器,服務器返回一個自己服務器的id,這個過程一般會保存用戶信息,但是wx.login()沒有返回

userinfo,只返回一個code,code用來獲取openid的,怎么辦呢?我的邏輯是:

  1. wx.login()獲取code
  2. 通過code獲取openid
  3. 通過wx.getSetting()判斷是否授權,如果授權則通過openid去自己服務器獲取userinfo信息,否則創建授權按鈕,在用戶點擊允許的時候把返回的userinfo更新到自己的服務器

部分代碼:

//前面省略login和get openid
wx.getSetting({complete:(res)=>{
            if(res.errMsg == 'getSetting:ok'){
                let obj = res.authSetting;
                if(obj['scope.userInfo'] == true){
                    // console.log('已經授權');
                }
                else{
                    createAuthorButton();
                }
                console.log("getSetting:%o",res);
            }
             else{
                 wx.showModal('錯誤信息',res.errMsg);
             }
        }});

createAuthorButton(x:number, y:number, width:number, height:number) {
        if (egret.Capabilities.runtimeType != "wxgame") {
            return;
        }

        let button = wx.createUserInfoButton({
            type: 'text',
            text: '',
            style: {
                left: x,
                top: y,
                width: width,
                height: height,
                backgroundColor: '#ff0000',
                color: '#ffffff',
                textAlign: 'center',
                fontSize: 16,
                opacity: 0
            }
        });
        button.onTap((res) => {
            if(res.errMsg == 'getUserInfo:ok'){
                button.hide();
                button.destroy();
                WxApi.showShareMenu();
                updateUserInfo(res);
            }
        });
    }

12、小游戲轉發帶參數與不帶參數注意參數格式,包括主動轉發和被動轉發,查api這個都知道

13、微信開發者工具預覽上傳的時候會報錯資源上傳失敗等,解決方法重啟開發者工具再預覽上傳

14、egret發布的小游戲,egret.js 和 eui.js庫比較大,解決方法是通過終端發布命令發布微信小游戲:egret publish --target wxgame

15、忘記一個p2的問題,p2可能會undefine,更改p2引擎暴露下p2

16、有時會遇到微信開發者工具不能編譯,不能運行,重啟右上角清緩存也不行,win10,我的解決方法是關閉工具並刪除如下這個文件夾

 

 

總結下來大致就這些破問題,避免踩坑,如果還不行的話,刪庫跑路也未嘗不可,畢竟家里的幾畝地在那閑着呢,要不搬磚的隨時歡迎!扯遠了,其實只要多角度,多方面去思考問題,解決的方法還是很多的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM