用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的,怎么辦呢?我的邏輯是:
- wx.login()獲取code
- 通過code獲取openid
- 通過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,我的解決方法是關閉工具並刪除如下這個文件夾
總結下來大致就這些破問題,避免踩坑,如果還不行的話,刪庫跑路也未嘗不可,畢竟家里的幾畝地在那閑着呢,要不搬磚的隨時歡迎!扯遠了,其實只要多角度,多方面去思考問題,解決的方法還是很多的。