在前三篇文章的基礎上,爲基於Babylon.js的WebGL場景添加了相似戰棋遊戲的基本操做流程,包括從手中選擇單位放入棋盤、顯示單位具備的技能、選擇技能、不一樣單位經過技能進行交互、處理交互結果以及進入下一回合恢復棋子的移動力。由於時間有限,這一階段的目的只是實現基本規則的貫通,沒有關注場景的美觀性和操做的便捷性,也沒有進行充分的測試。html
1、顯示效果:node
一、訪問https://ljzc002.github.io/CardSimulate2/HTML/TEST4rule.html查看「規則測試頁面」:git
天空盒內有一個隨機生成的棋盤(將來計劃編寫更復雜棋盤的生成方法),棋盤上有兩個預先放置的棋子,屏幕中央是一個藍色邊框的準星。github
二、按alt鍵展開手牌,拉近手牌中的某個單位後會在屏幕左側顯示「落子」按鈕,點擊落子按鈕後,準星將變爲橙色邊框(再按alt返回手牌能夠將準星變回藍色),在橙色準星狀態下點擊地塊則可將選定的手牌放入棋盤,放入棋盤後(將來計劃一個單位在手牌中以卡牌方式顯示,放入棋盤後改成3D模型)當即顯示棋子的移動範圍,而且在屏幕的左上角顯示棋子的狀態和技能列表(計劃優化這個表格的佈局)。算法
選中手牌:編程
準星變爲橙色:canvas
落子後顯示移動範圍和狀態技能列表:數組
爲了能用鼠標選取技能,這裏調整了單位選取規則,如今只要選中單位,場景瀏覽方式就會從first_lock(鼠標鎖定在屏幕中心)切換爲first_pick(鼠標能夠在屏幕中自由移動選取)以釋放光標,取消單位選中後,自動切換回first_lock。瀏覽器
鼠標移入技能單元格,顯示技能的說明:緩存
在這種狀態下,點擊紅色範圍外的地塊或者按alt鍵或者點擊棋子自己,均可以解除棋子的選中狀態,並隱藏移動範圍和技能列表。
三、在移動棋子以後,棋子會從wait狀態變爲moved狀態,若是棋子具有nattack(普通攻擊)技能,將自動顯示棋子的普通攻擊範圍;按alt鍵,在手牌菜單裏點擊「下一回合」,將把全部棋子恢復爲wait狀態(這裏還須要一個明確的回合結束生效效果),而且增長鬚要冷卻的技能的裝填計數並減小持續時間有限的技能的持續時間(還沒有測試)。
完成移動以後:
右側的Octocat正處於moved狀態,它周圍是nattack技能的釋放範圍(能夠看到skill_current項顯示爲「nattack」,表示移動完成後默認選取了nattack技能),此時的Octocat不能再移動,能夠經過點擊沒有遮罩的地塊取消對他的選取。
點擊下一回合按鈕後,再選中Octocat單位:
發現Octocat又能夠再次移動,而且冷卻時間爲2的test2技能進行了一次裝填。
四、單位移動完畢以後會自動選擇nattack做爲當前技能,或者在技能列表裏點選技能作爲當前技能(目前只完成了nattack的編寫),選擇完畢後會在單位周圍用紅色遮罩標示技能的釋放範圍,點擊紅色遮罩,則以綠色遮罩顯示技能的影響範圍。再次點擊綠色遮罩,則在這個位置釋放當前技能,釋放技能時技能釋放者和釋放目標按順序執行相應的動畫效果。
五、當單位的血量耗盡時,會變成灰色返回手牌:
在手牌的末尾可以看到灰色的Octocat,它沒法被再次放入棋盤。
六、AOE技能:
能夠看到,技能範圍內的單位都受到AOE影響
七、說明:
事實上,上面的遊戲規則代碼已經被前人用各類方式實現不少遍,能夠說每個成熟的遊戲開發團隊都有其精雕細琢的規則代碼,但絕大部分這類代碼都是閉源或者存在獲取障礙的,所以我本身用JavaScript實現了這一套規則代碼並把它開源。其實,Babylon.js的開發團隊也在作相似的事情——將各類商業3D引擎的成熟技術移植到WebGL平臺並開源。
有人會問,花費不少精力用低效的方式作一個別人作過屢次的「輪子」有什麼用?確實,和成熟的商業3D引擎相比,WebGL技術在性能和操做性上還存在明顯的缺陷,但WebGL技術的兩個獨有特性是傳統商業引擎所沒法比擬的:一是網頁端應用的強制開源性,由於全部JavaScript代碼最終都以明文方式在瀏覽器中執行,因此任何人都可以獲取WebGL程序的代碼並直接使用瀏覽器進行調試,這使得WebGL中用到的技術和知識能夠不受壟斷的自由傳播;其二,JavaScript語言的學習難度和傳統的3D開發語言C++不在同一量級,瀏覽器也爲開發者解決了適配各類運行環境時遇到的諸多難題,WebGL技術的出現使得3D編程的入門史無前例的簡單。
對於擁有大量高端人才、以盈利爲目的商業性遊戲公司,強制開源和低技術門檻並無太大意義,因此WebGL技術註定難以成爲商業遊戲開發的主流,可是對於不以盈利爲目的的人士和非職業編程者來講WebGL技術正預示着一種新的、不受現有條框束縛的表達方式,而準確且豐富的表達正是人們相互理解進而平等相待的基礎之一。使用WebGL技術,學生、教師、傳統信息系統操做員乃至沒法忍受劣質商業化遊戲的玩家均可能作出兼具外在表象和內在邏輯的3D程序。
2、代碼實現:
一、整理前面的代碼:
在編寫規則代碼以前,首先對https://www.cnblogs.com/ljzc002/p/9660676.html和https://www.cnblogs.com/ljzc002/p/9778855.html中創建的工程進行整理,通過整理後的js文件結構以下:
首先把BallMan、CameraMesh、CardMesh三個類分離到三個單獨的js文件裏,置於Character文件夾中,用以實例化場景中比較複雜的幾種物體;
接着把全部和鍵盤鼠標響應有關的代碼放到Control.js中;
FullUI.js裏包含全部與Babylon.js GUI和Att7.js Table相關的內容;
Game.js改動不大,仍起到全局變量管理的做用;
HandleCard2.js裏是和手牌有關的規則代碼;
Move.js是CameraMesh的移動控制方法;
rule.js是一部分和場景初始化和GUI操做有關的規則代碼;
tab_carddata.js裏是卡牌定義;
tab_skilldata.js裏是技能定義,而且包含了和技能有關的規則代碼;
tab_somedata.js裏是一些其餘定義;
Tiled.js是和棋盤有關的規則代碼。
整理以後的部分文件內容以下:(只總結了前兩篇文章裏的內容)
圖一:
圖二:
圖中列出了每一個文件中的屬性和方法,大部分能夠在前兩篇文章中找到對應的說明,若是哪裏沒有說清,請在評論區留言。由於時間有限,新增長的規則代碼並無畫入,由於手機性能有限,有些文字略顯模糊。
二、手牌管理:https://www.cnblogs.com/ljzc002/p/9660676.html
三、從手牌放入棋盤:
a、在FullUI.js中添加「落子」按鈕
1 var UiPanel2 = new BABYLON.GUI.StackPanel();
2 UiPanel2.width = "220px"; 3 UiPanel2.fontSize = "14px"; 4 UiPanel2.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT; 5 UiPanel2.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_CENTER; 6 UiPanel2.color = "white"; 7 advancedTexture.addControl(UiPanel2); 8 var button3 = BABYLON.GUI.Button.CreateSimpleButton("button3", "落子"); 9 button3.paddingTop = "10px"; 10 button3.width = "100px"; 11 button3.height = "50px"; 12 button3.background = "green"; 13 button3.isVisible=false;//這個按鈕默認不可見,選中並放大一張手牌後可見 14 button3.onPointerDownObservable.add(function(state,info,coordinates) { 15 if(MyGame.init_state==1&&card_Closed&&card_Closed.workstate!="dust")//若是完成了場景的虛擬化 16 { 17 Card2Chess();//將當前選中的手牌和光標關聯起來,換回first_lock,並改變光標的顏色,點擊空白地塊時落下棋子, 18 } 19 });
b、按下按鈕後將準星顏色改成橙色(考慮更改準星形狀?),在rule.js文件中
1 function Card2Chess()//將當前選中的手牌設爲手中棋子 2 { 3 MyGame.player.centercursor.color="orange"; 4 MyGame.player.changePointerLock2("first_lock");//將瀏覽方式改成first_lock 5 HandCard(1);//通過動畫隱藏手牌 6 7 //切換回first_lock狀態 8 }
c、在準星邊緣爲橙色時點擊地塊,則把手牌轉化爲棋子放入棋盤裏:
首先在Tiled.js文件的PickTiled方法裏響應地塊點擊:
1 if(MyGame.player.centercursor.color=="orange")//若是當前是落子狀態 2 {//mesh是棋盤中的一個地塊 3 if(card_Closed&&!TiledHasCard(mesh))//若是存在選定的手牌而且點擊的格子沒有其餘棋子,則把棋子放到這個格子裏 4 { 5 Card2Chess2(mesh);//具體代碼在rule.js裏 6 } 7 else 8 { 9 MyGame.player.centercursor.color=="blue"//點已經有棋子的地方,則取消落子 10 } 11 }
而後在rule.js里正式將棋子放入棋盤:
1 function Card2Chess2(mesh)//將手中棋子放在棋盤上 2 { 3 if(card_Closed.num_group>-1&&card_Closed.num_group<5)//若是卡片在手牌的某個分組中 4 {//從小組裏刪除 5 delete arr_cardgroup[card_Closed.num_group][card_Closed.mesh.name]; 6 /*if(Object.getOwnPropertyNames(arr_cardgroup[card.num_group]).length==0) 7 { 8 arr_mesh_groupicon[card.num_group].isVisible=false; 9 }*/ 10 } 11 card_Closed.mesh.parent=null;//card_Closed是手牌中選中的對象, 12 card_Closed.mesh.parent=mesh_tiledCard; 13 card_Closed.mesh.scaling=new BABYLON.Vector3(0.1,0.1,0.1); 14 card_Closed.mesh.position=mesh.position.clone();//棋子放在地塊位置。 15 card_Closed.mesh.position.y=0; 16 card_Closed.workstate="wait"; 17 noPicked(card_Closed); 18 card_Closed2=card_Closed;//將它設爲棋盤中的一個棋子 19 card_Closed2.display();//將棋子設爲可見 20 PickCard2(card_Closed2);//將它設爲選中的棋子 21 card_Closed=null;//取消手牌中的選中對象 22 MyGame.player.centercursor.color="blue";//準星從新變藍 23 }
四、棋子移動:https://www.cnblogs.com/ljzc002/p/9778855.html
五、選中棋子:
a、HandleCard2.js文件中PickCard2方法以棋子對象爲參數,用來在棋盤上選中棋子:
1 function PickCard2(card)//點擊一下選中,高亮邊緣,再點擊也不放大?-》再點擊則拉近鏡頭後恢復first_lock!! 2 //同時還要在卡片附近創建一層藍色或紅色的半透明遮罩網格,表示移動及影響範圍 3 {//若是再次點擊有已選中卡片,則把相機移到卡片面前 4 if(card.isPicked) 5 { 6 GetCardClose2(card);//將相機拉近到選中卡牌面前,並取消卡牌的選定 7 //規定點擊藍色遮罩時計算到達路徑,點擊空處時清空範圍,點擊其餘卡牌時切換範圍,切換成手牌時清空範圍 8 } 9 else//若是這個棋子沒有被選中 10 { 11 12 if(card.workstate=="wait")//若是棋子正等待移動,則顯示棋子的移動範圍 13 { 14 DisplayRange(card);//這裏麪包含了清除已有遮罩而且保證棋子的選中 15 } 16 else if(card.workstate=="moved")//若是棋子已經移動,但還未工做 17 { 18 //首先要檢查是否有已經顯示的遮罩 19 if(arr_DisplayedMasks.length>0)//清空全部遮罩和棋子選定以及技能列表 20 { 21 HideAllMask();//這裏也會清空card_Closed2 22 } 23 card_Closed2=card; 24 getPicked(card_Closed2); 25 card.isPicked=true; 26 if(card_Closed2.skills["nattack"]) 27 {//若是這個單位具備普通攻擊技能,則顯示普通攻擊範圍 28 skill_current=card_Closed2.skills["nattack"];//若是單位具備nattack技能 29 document.getElementById("str_sc").innerHTML="nattack"; 30 canvas.style.cursor="crosshair"; 31 DisplayRange2(card_Closed2,card_Closed2.skills["nattack"].range);//默認顯示nattack技能的範圍 32 } 33 } 34 //若是是worked則什麼也不作->仍是要顯示信息的 35 else if(card.workstate=="worked")//若是已經工做過 36 { 37 if(arr_DisplayedMasks.length>0) 38 { 39 HideAllMask();//這裏也會清空card_Closed2 40 } 41 card_Closed2=card; 42 getPicked(card_Closed2); 43 card.isPicked=true; 44 document.getElementById("str_sc").innerHTML="Worked"; 45 } 46 MyGame.player.changePointerLock2("first_pick");//若是棋子沒有被選中,則瀏覽方式改成first_pick 47 DisplayUnitUI();//同時也要顯示棋子操縱ui->這裏使用html dom table 48 } 49 }
b、DisplayUnitUI方法顯示當前選中棋子的技能列表,其代碼位於FullUI.js文件中:
1 function DisplayUnitUI() 2 { 3 //MyGame.SkillTable 4 if(card_Closed2)//若是這時已經有選中的單位,則顯示單位的效果列表 5 { 6 document.getElementById("all_base").style.display="block";//使技能列表元素可見 7 var data=MyGame.SkillTable.data;//獲取技能列表的數據 8 data.splice(4);//清空舊的技能列表 9 var card=card_Closed2; 10 document.getElementById("str_chp").innerHTML=card.chp;//當前hp 11 document.getElementById("str_thp").innerHTML=card.hp;//總hp 12 document.getElementById("str_cmp").innerHTML=card.cmp;//當前mp 13 document.getElementById("str_tmp").innerHTML=card.mp;//總mp 14 document.getElementById("str_atk").innerHTML=card.attack;//攻擊 15 document.getElementById("str_speed").innerHTML=card.speed;//移動力 16 //document.getElementById("str_range").innerHTML=card.range; 17 var skills=card.skills; 18 for(key in skills)//遍歷顯示單位全部的技能 19 { 20 var skill=skills[key];//單位如今具備的技能 21 var skill2=arr_skilldata[key];//技能列表裏的技能描述 22 var str1=key,str2="full"; 23 if(skill.last!="forever")//若是不是永久持續,要在括號裏顯示持續時間 24 { 25 str1+=("("+skill.last+")"); 26 } 27 if(skill.reload!="full")//若是沒有裝填完成,要顯示裝填進度 28 { 29 str2=skill.reload+"/"+skill2.reloadp; 30 } 31 data.push([str1 32 ,str2]); 33 } 34 MyGame.SkillTable.draw(data,0);//繪製表格 35 requestAnimFrame(function(){MyGame.SkillTable.AdjustWidth();}); 36 } 37 }
對應的,DisposeUnitUI方法用來隱藏技能列表:
1 function DisposeUnitUI() 2 { 3 skill_current=null;//清空當前選中的技能 4 document.getElementById("str_sc").innerHTML="";//當前技能 5 canvas.style.cursor="default"; 6 arr_cardTarget=[];//清空當前選擇的技能目標 7 fightDistance=0; 8 if(document.getElementById("div_thmask"))//刪除鎖定表頭的遮罩層 9 { 10 var div =document.getElementById("div_thmask"); 11 div.parentNode.removeChild(div); 12 } 13 if(document.getElementById(MyGame.SkillTable.id))//刪除表體 14 { 15 var tab =document.getElementById(MyGame.SkillTable.id); 16 tab.parentNode.removeChild(tab); 17 } 18 document.getElementById("all_base").style.display="none";//隱藏表格 19 }
c、FullUI.js文件中還設置了技能列表的單元格的鼠標響應:
鼠標移入:
1 function SkillTableOver()//在鼠標移入時先隱藏可能存在的舊的描述文字,而後顯示懸浮顯示描述文字 2 { 3 //console.log("SkillTableOver"); 4 var evt=evt||window.event||arguments[0]; 5 cancelPropagation(evt); 6 var obj=evt.currentTarget?evt.currentTarget:evt.srcElement; 7 delete_div("div_bz"); 8 Open_div("", "div_bz", 240, 120, 0, 0, obj, "div_tab"); 9 document.querySelectorAll("#div_bz")[0].innerHTML = MyGame.SkillTable.html_onmouseover;//向彈出項裏寫入結構 10 document.querySelectorAll("#div_bz .div_inmod_lim_content")[0].innerHTML = card_Closed2.skills[obj.innerHTML.split("(")[0]].describe;//顯示描述文字 11 }
鼠標移出:
1 function SkillTableOut()//鼠標移出時隱藏全部描述文字 2 { 3 //console.log("SkillTableOut"); 4 var evt=evt||window.event||arguments[0]; 5 cancelPropagation(evt); 6 delete_div("div_bz"); 7 }
點擊技能單元格:
1 function SkillTableClick()//點擊時觸發技能的eval 2 { 3 var evt=evt||window.event||arguments[0]; 4 cancelPropagation(evt); 5 var obj=evt.currentTarget?evt.currentTarget:evt.srcElement; 6 delete_div("div_bz"); 7 if(card_Closed2.workstate!="worked")//若是單位尚未進行工做 8 { 9 var skillName=obj.innerHTML.split("(")[0];//從單元格中提取技能名 10 if(card_Closed2.cmp>=card_Closed2.skills[skillName].cost)//若是有足夠的mp 11 { 12 skill_current=card_Closed2.skills[skillName];//skill_current表示當前技能對象 13 document.getElementById("str_sc").innerHTML=skillName; 14 //console.log("SkillTableClick"); 15 //還要顯示這個技能的釋放範圍 16 var len=arr_DisplayedMasks.length; 17 for(var i=0;i<len;i++)//隱藏已有的遮罩 18 { 19 arr_DisplayedMasks[i].material=MyGame.materials.mat_alpha_null;//這個數組裏存的真的只是遮罩 20 } 21 arr_DisplayedMasks=[]; 22 canvas.style.cursor="crosshair"; 23 DisplayRange2(card_Closed2,skill_current.range);//在單位周圍顯示當前技能的釋放範圍 24 } 25 26 } 27 28 }
六、顯示當前技能的影響範圍,並查找範圍內的可能目標:
a、在Tiled.js中響應地塊點擊事件:
1 if(skill_current!=null)//若是當前技能不爲空 2 { 3 if(mesh.mask.material.name == "mat_alpha_red")//若是點擊的是紅色遮罩 4 { 5 //有選擇的單位和技能,點擊紅色遮罩,則先清空已選擇目標,以點擊位置爲中心顯示綠色遮罩羣表示瞄準,若是瞄準範圍內存在單位,則放入target 6 arr_cardTarget=[]; 7 var len=arr_DisplayedMasks.length; 8 for(var i=0;i<len;i++)//隱藏全部遮罩 9 { 10 arr_DisplayedMasks[i].material=MyGame.materials.mat_alpha_null; 11 } 12 arr_DisplayedMasks=[]; 13 DisplayRange2(card_Closed2,skill_current.range);//從新顯示一次釋放範圍 14 DisplayRange3(mesh);//根據當前技能,在瞄準地塊周圍顯示綠色遮罩羣表示影響範圍,要先調用一次DisplayRange2, 15 } 16 else if(mesh.mask.material.name == "mat_alpha_green") 17 {//若是點擊的是綠色遮罩 18 if (card_Closed2.workstate == "wait"||card_Closed2.workstate == "moved") 19 {//若是單位尚未工做 20 card_Closed2.cmp-=skill_current.cost;//消耗mp 21 document.getElementById("str_cmp").innerHTML=card_Closed2.cmp; 22 eval(skill_current.eval);//執行技能效果 23 fightDistance=arr_noderange3[mesh.name].cost;//fight雙方的距離 24 //HideAllMask(); 25 //MyGame.player.changePointerLock2("first_lock"); 26 27 } 28 } 29 else//點擊影響範圍外的點 30 { 31 HideAllMask();//取消選中 32 MyGame.player.changePointerLock2("first_lock"); 33 } 34 }
b、DisplayRange3方法的參數是地塊的網格,表示在這個地塊釋放當前選中技能時的影響範圍:
1 function DisplayRange3(mesh) 2 { 3 //var card=card_Closed2; 4 var range=0; 5 range=skill_current.range2;//range2是技能的影響範圍,注意不要和釋放範圍range混淆 6 //算法和前兩個名稱相似的方法類似 7 var node_start=mesh; 8 arr_noderange3={}; 9 arr_noderange3[node_start.name]={cost:0,path:[node_start.name],node:node_start}; 10 var costg=0; 11 //var range=card.range; 12 var list_noderange=[node_start]; 13 for(var i=0;i<list_noderange.length;i++) 14 { 15 var arr_node_neighbor=FindNeighbor(list_noderange[i]); 16 var len=arr_node_neighbor.length; 17 for(var j=0;j<len;j++) 18 { 19 costg=arr_noderange3[list_noderange[i].name].cost; 20 costg+=1; 21 if(costg>range) 22 { 23 break;//由於影響範圍的cost都是相同的,因此只要有一個鄰居超過限度,則全部鄰居都不可用 24 } 25 //若是沒有超限 26 var nextnode = arr_node_neighbor[j]; 27 var path2=arr_noderange3[list_noderange[i].name].path.concat(); 28 path2.push(nextnode.name); 29 if(arr_noderange3[nextnode.name])//若是之前曾經到達這個節點 30 { 31 if(arr_noderange3[nextnode.name].cost>costg)//這裏仍是否有必要計算路徑?? 32 { 33 arr_noderange3[nextnode.name]={cost:costg,path:path2,node:nextnode}; 34 } 35 else 36 { 37 continue; 38 } 39 } 40 else 41 { 42 arr_noderange3[nextnode.name]={cost:costg,path:path2,node:nextnode}; 43 list_noderange.push(nextnode); 44 } 45 } 46 } 47 for(var key in arr_noderange3)//對於每個綠色遮罩的地塊 48 { 49 //if(arr_noderange3[key].cost>0) 50 //{ 51 arr_noderange3[key].node.mask.material=MyGame.materials.mat_alpha_green; 52 var mesh_unit = TiledHasCard(arr_noderange3[key].node);//若是這個綠色地塊中存在單位 53 if(mesh_unit)//若是瞄準範圍內存在一個單位,從理論上說也多是本身!!!! 54 { 55 arr_cardTarget.push(mesh_unit.card);//則把這個單位放入目標列表 56 } 57 //} 58 59 arr_DisplayedMasks.push(arr_noderange3[key].node.mask); 60 } 61 }
七、執行技能效果:
a、在tab_skilldata.js文件中定義了技能的eval屬性,它是以字符串形式存儲的可執行代碼,以普通攻擊技能爲例:
1 nattack: 2 { 3 name:"nattack" 4 ,ap:"a" 5 ,start:"wait" 6 ,end:"worked" 7 ,reloadp:0 8 ,range:1 9 ,range2:0 10 ,cost:0 11 ,eval:"func_skills.nattack()" 12 ,describe:"普通攻擊,是默認的影響方式" 13 }
其中,ap屬性是「主被動標記」,取值範圍以下:
a主動、p被動,p_all在全部環節生效,p_param影響單位屬性,p_work在工做環節生效,p_next在點擊下一回合時生效,p_weak在下一回合開始時生效(與p_next等效?),p_sleep在工做結束後當即生效,p_destoryed在被破壞時生效,p_beattack被影響時生效
b、nattack方法的代碼在下面:
1 nattack:function()//一次普通攻擊行爲 2 { 3 var len=arr_cardTarget.length; 4 //var count_ani=0 5 if(len>0)//若是目標數大於零 6 { 7 MyGame.flag_view="first_ani"; 8 card_Closed2.count_ani=len;//動畫計數器,認爲每個目標都有一系列的技能流程, 9 }//這一次行爲中的全部技能流程都結束,這個行爲才結束。 10 11 for(var i=0;i<len;i++)//對於每個目標,認爲普通攻擊只會有一個目標! 12 { 13 var target=arr_cardTarget[i]; 14 if(target.mesh.id==card_Closed2.mesh.id)//規定本身不能nattack本身?? 15 { 16 func_skills.ani_final();//什麼也不作,結束這個技能流程 17 continue; 18 } 19 var skills=card_Closed2.skills;//當前選中棋子的技能列表 20 var skillst=target.skills;//目標的技能列表 21 func_skills.beforeFight(target,skills,skillst);//執行一些在fight開始前生效的被動技能 22 //超多層function嵌套,有沒有更先進的解決方法?開始進入回調地獄 23 card_Closed2.ani_beat(target,function(){//撞擊動畫 24 target.chp-=card_Closed2.attack;//技能目標的當前hp減小量等於選中棋子的攻擊力 25 target.ani_floatstr("-"+card_Closed2.attack,[],function(){//文字上浮動畫 26 if(target.chp>0)//若是目標還活着 27 { 28 if(skillst["nattack"])//若是target具有nattack能力則反擊之 29 { 30 target.ani_beat(card_Closed2,function(){ 31 card_Closed2.chp-=target.attack; 32 card_Closed2.ani_floatstr("-"+target.attack,[],function(){ 33 if(card_Closed2.chp>0) 34 { 35 document.getElementById("str_chp").innerHTML=card_Closed2.chp;//更新當前hp顯示 36 card_Closed2.workstate="worked"; 37 func_skills.ani_final(target,skills,skillst);//結束這個技能流程 38 } 39 else 40 { 41 func_skills.unitDestory(card_Closed2,skills,skillst);//搶救 42 } 43 }); 44 }); 45 } 46 } 47 else 48 { 49 card_Closed2.workstate="worked";//當前狀態爲工做完畢 50 func_skills.unitDestory(target,skills,skillst);//在target死前檢查有沒有能夠自救的被動技能 51 } 52 }); 53 }); 54 55 if(target.range>=fightDistance)//若是在target的nattack範圍內 56 { 57 //card_Closed2.chp-=target.attack; 58 } 59 60 // 61 } 62 },
由於要等到一個動畫環節(好比撞擊)結束後才能進行下一個環節(好比上浮傷害數字),因此須要把後一個環節的調用放在前一個環節的回調函數裏,有人認爲這種連續回調環節不少時程序會很是難以閱讀,故將這種狀況稱爲「回調地獄」,可是我感受還好。
c、ani_final方法用來結束行爲中的一個流程:
1 ani_final:function(target,skills,skillst)//在全部效果動畫結束後恢復爲first_lock 2 { 3 4 card_Closed2.count_ani--; 5 if(card_Closed2.count_ani<=0)//假設一個aoe有多個回調線路,要確保每一個回調線路都結束,再斷定動做結束 6 { 7 /*if(target) 8 {//若是目標還活着 9 func_skills.afterFight(target,skills,skillst); 10 }*/ 11 HideAllMask();//動做結束解除全部鎖定 12 MyGame.player.changePointerLock2("first_lock");// 13 } 14 15 }
d、unitDestory方法會查看單位是否有自救技能:
1 unitDestory:function(target,skills,skillst) 2 { 3 for(key in skillst) 4 { 5 var skill=skillst[key]; 6 if(skill.ap=="p_destoryed"&&skill.eval) 7 { 8 eval(skill.eval);//若是有自救能力則跳到另外一個效果方法裏,nattack的效果則終結 9 } 10 } 11 if(target.chp<=0)//若是沒搶救過來 12 { 13 target.ani_destory(function(){//執行死亡動畫 14 func_skills.ani_final(target,skills,skillst); 15 }); 16 } 17 },
八、爲單位創建動畫:
在CardMesh.js文件裏爲卡牌型單位創建了幾種簡單的行爲動畫
a、撞擊目標:
1 //下面計劃要添加震動方法和被破壞方法 2 CardMesh.prototype.ani_beat=function(target,callback)// 3 { 4 var mesh=this.mesh; 5 mesh.animations=[]; 6 var animation=new BABYLON.Animation("animation","position",30,BABYLON.Animation.ANIMATIONTYPE_VECTOR3,BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT); 7 var pos1=mesh.position.clone(); 8 var pos2=target.mesh.position.clone(); 9 var keys=[{frame:0,value:pos1},{frame:15,value:pos2},{frame:30,value:pos1}]; 10 animation.setKeys(keys); 11 mesh.animations.push(animation); 12 scene.beginAnimation(mesh, 0, 30, false,1,function(){ 13 callback(); 14 }); 15 }
b、向目標發射一個「子彈」:
1 CardMesh.prototype.ani_fire=function(target,cursor,callback) 2 {//創建一個精靈對象(或者是粒子對象?),讓它飛向目標 3 var mesh=this.mesh; 4 var sprite_bullet=new BABYLON.Sprite("sprite_bullet", cursor);//cursor是MyGame.SpriteManager 5 sprite_bullet.parent=mesh.parent; 6 sprite_bullet.position =mesh.position.clone(); 7 sprite_bullet.position.y+=2 8 9 var animation=new BABYLON.Animation("animation","position",30,BABYLON.Animation.ANIMATIONTYPE_VECTOR3,BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT); 10 var pos1=sprite_bullet.position.clone(); 11 var pos2=target.mesh.position.clone(); 12 var keys=[{frame:0,value:pos1},{frame:30,value:pos2}]; 13 animation.setKeys(keys); 14 sprite_bullet.animations.push(animation); 15 scene.beginAnimation(sprite_bullet, 0, 30, false,1,function(){ 16 sprite_bullet.dispose(); 17 callback(); 18 }); 19 }
c、一個從單位身上飄起的字符串:
1 CardMesh.prototype.ani_floatstr=function(str,styles,callback) 2 {//創建一個基於canvas紋理的對象,讓它飄起來而後消失 3 var mesh=this.mesh; 4 str+="";//前面若是傳來的是數字,則取不到length!!-》顯示轉換爲字符串 5 var size_x=str.length*30; 6 var mesh_str = new BABYLON.MeshBuilder.CreateGround(this.name + "mesh_str", { 7 width: size_x/2.5, 8 height: 16 9 }, scene); 10 mesh_str.parent=mesh; 11 //mesh_str.position =new BABYLON.Vector3(0,0,0); 12 mesh_str.renderingGroupId = 3;//這些文字是特別強調內容,使用最高級的渲染組 13 var mat_str = new BABYLON.StandardMaterial(this.name + "mat_str", scene); 14 var texture_str = new BABYLON.DynamicTexture(this.name + "texture_str", { 15 width: size_x, 16 height: 40 17 }, scene); 18 mat_str.diffuseTexture = texture_str; 19 mesh_str.material = mat_str; 20 mesh_str.rotation.x = -Math.PI / 2; 21 mesh_str.isPickable = false; 22 texture_str.hasAlpha=true; 23 mat_str.useAlphaFromDiffuseTexture=true; 24 25 //通過測試發現,在Chrome中canvas的繪圖是以圖像的左上角定位的,而文字繪製則是以文字的左下角定位的!!!! 26 var context_comment = texture_str.getContext(); 27 context_comment.fillStyle = "rgba(255,255,255,0)";//"transparent"; 28 context_comment.fillRect(0, 0, size_x, 40); 29 //context_comment.fillStyle = "#ffffff"; 30 context_comment.fillStyle = "#ff0000"; 31 context_comment.font = "bold 30px monospace"; 32 var len=styles.length; 33 for(var i=0;i<len;i++) 34 { 35 context_comment[styles[i][0]]=styles[i][1]; 36 } 37 //newland.canvasTextAutoLine(str, context_comment, 1, 30, 35, 34); 38 context_comment.fillText(str,0,30);//y座標偏離一個字高 39 texture_str.update(); 40 41 var animation=new BABYLON.Animation("animation","position",30,BABYLON.Animation.ANIMATIONTYPE_VECTOR3,BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT); 42 //var pos1=mesh_str.position.clone(); 43 //var pos2=mesh_str.position.clone(); 44 //pos2.y+=2; 45 var keys=[{frame:0,value:new BABYLON.Vector3(0,0,0)},{frame:30,value:new BABYLON.Vector3(0,20,0)}]; 46 animation.setKeys(keys); 47 mesh_str.animations.push(animation); 48 scene.beginAnimation(mesh_str, 0, 30, false,1,function(){ 49 mesh_str.dispose(); 50 mat_str.dispose(); 51 texture_str.dispose(); 52 callback(); 53 }); 54 55 }
d、單位變成黑白色,而後昇天
1 CardMesh.prototype.ani_destory=function(callback) 2 {//先換成灰白色圖片,而後上浮 3 var mesh=this.mesh; 4 this.workstate="dust" 5 6 var mat_dust = new BABYLON.StandardMaterial(this.name + "mat_dust", this.scene);//測試用卡片紋理 7 mat_dust.diffuseTexture = new BABYLON.Texture(this.imagedust, this.scene);//實現已經準備好了黑白色的圖片,能夠用MakeDust.html工具生成 8 mat_dust.diffuseTexture.hasAlpha = false; 9 mat_dust.backFaceCulling = true; 10 mat_dust.useLogarithmicDepth = true;//使用對數式深度緩存避免「Z-fighting」 11 mat_dust.freeze(); 12 13 this.mesh_mainpic.material = mat_dust; 14 15 var animation=new BABYLON.Animation("animation","position",30,BABYLON.Animation.ANIMATIONTYPE_VECTOR3,BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT); 16 var pos1=this.mesh.position.clone(); 17 var pos2=this.mesh.position.clone(); 18 pos2.y+=2; 19 var keys=[{frame:0,value:pos1},{frame:30,value:pos2}]; 20 animation.setKeys(keys); 21 mesh.animations=[]; 22 mesh.animations.push(animation); 23 scene.beginAnimation(mesh, 0, 30, false,1,function(){ 24 //把dust的card收回手牌 25 noPicked(mesh.card); 26 mesh.parent=null; 27 mesh.parent=mesh_arr_cards; 28 mesh.scaling=new BABYLON.Vector3(0.1,0.1,0.1); 29 mesh.rotation.y=0; 30 mesh.card.num_group==999; 31 mesh.card.dispose(); 32 callback(); 33 }); 34 }
e、單位跳動一下:
1 CardMesh.prototype.ani_shake=function(callback)//上下晃動一下 2 { 3 var mesh=this.mesh; 4 mesh.animations=[]; 5 var animation=new BABYLON.Animation("animation","position",30,BABYLON.Animation.ANIMATIONTYPE_VECTOR3,BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT); 6 var pos1=mesh.position.clone(); 7 var pos2=mesh.position.clone(); 8 pos2.y+=1; 9 var keys=[{frame:0,value:pos1},{frame:15,value:pos2},{frame:30,value:pos1}]; 10 animation.setKeys(keys); 11 mesh.animations.push(animation); 12 scene.beginAnimation(mesh, 0, 30, false,1,function(){ 13 callback(); 14 }); 15 }
九、AOE
同時攻擊多個目標意味着要同時開啓多個回調流程,修改一下上面的nattack方法:
1 aoe:function(range2,atk,isSafe,arr_state)//形成aoe傷害,參數:影響距離、攻擊力、是否會傷害本方、[[給目標添加的效果一、生效的機率、持續的時間],[],[]] 2 { 3 var len=arr_cardTarget.length; 4 if(len>0) 5 { 6 MyGame.flag_view="first_ani"; 7 card_Closed2.count_ani=len;//有幾個目標,就設置幾個動畫計數 8 } 9 else 10 { 11 //return; 12 } 13 card_Closed2.ani_shake(function(){//本身先晃動一下表示發出aoe 14 var skills=card_Closed2.skills; 15 16 func_skills.beforeFight(null,skills,{})//若是下面的target使用var類型變量,由於js的變量提高特性,前面的target也會被自動聲明!!,可是let並不具有變量提高功能!!!! 17 for(var i=0;i<len;i++) 18 {//這裏要使用let型變量,不然全部的target變量都會被設爲最後定義的target致使程序出錯 19 let target=arr_cardTarget[i]; 20 if(isSafe&&target.belongto==card_Closed2.belongto)//若是是安全aoe則跳過本方單位 21 { 22 func_skills.ani_final(); 23 continue; 24 } 25 26 var skillst=target.skills; 27 //func_skills.beforeFight(target,{},skillst); 28 target.chp-=atk; 29 target.ani_floatstr("-"+atk,[],function() { 30 if(target.chp>0)//若是還活着 31 { 32 var len2=arr_state.length; 33 for(var j=0;j<len2;j++) 34 { 35 var state=arr_state[j] 36 if(newland.RandomBool(state[1]))//若是經過幾率斷定 37 { 38 if(skillst[state[0]])//若是已經有這一效果,則延長持續時間 39 { 40 skillst[state[0]].last+=state[2]; 41 } 42 else//不然添加這個效果 43 { 44 skillst[state[0]]={last:state[2],reload:"full"}; 45 } 46 } 47 } 48 func_skills.ani_final(target,skills,skillst); 49 } 50 else 51 { 52 53 func_skills.unitDestory(target,skills,skillst); 54 } 55 }); 56 57 } 58 if(card_Closed2.workstate!="dust") 59 { 60 document.getElementById("str_chp").innerHTML=card_Closed2.chp; 61 card_Closed2.workstate="worked"; 62 } 63 64 }) 65 66 67 },
十、進入下一回合:
a、在FullUI.js中添加「下一回合」按鈕:
1 var button4 = BABYLON.GUI.Button.CreateSimpleButton("button4", "下一回合"); 2 button4.paddingTop = "10px"; 3 button4.width = "100px"; 4 button4.height = "50px"; 5 button4.background = "green"; 6 button4.isVisible=false; 7 button4.onPointerDownObservable.add(function(state,info,coordinates) { 8 if(MyGame.init_state==1)//若是完成了場景的虛擬化 9 { 10 NextRound();//全部棋子的狀態變爲wait,特殊狀態的除外 11 } 12 }); 13 UiPanel2.addControl(button4); 14 UiPanel2.buttonnextr=button4;
b、NextRound方法位於rule.js文件中:
1 function NextRound()//將全部棋子的狀態置爲wait(後續添加對特殊狀態的處理) 2 { 3 var units=mesh_tiledCard._children; 4 var len=units.length; 5 for(var i=0;i<len;i++)//對於棋盤上的每一個棋子 6 { 7 var unit=units[i]; 8 card_Closed2=unit;//選中這個單位 9 var skills=unit.card.skills;//更新每一個reload和last的技能時間,還要令回合結束時的被動技能生效 10 for(var key in skills) 11 { 12 var skill=skills[key]; 13 var skill2=arr_skilldata[key]; 14 if(skill.ap=="p_next")//對於每個在跨越回合時生效的被動技能 15 { 16 if(skill.eval) 17 { 18 eval(skill.eval); 19 } 20 } 21 if(skill.reload!="full") 22 { 23 skill.reload++; 24 if(skill.reload>=skill2.reloadp)//若是裝填完畢 25 { 26 skill.reload="full" 27 } 28 } 29 if(skill.last!="forever"&&skill.ap!="p_wake") 30 { 31 skill.last--; 32 if(skill.last<=0)//若是持續時間結束 33 { 34 if(skill.eval2) 35 { 36 eval(skill.eval2); 37 } 38 39 delete skills[key];//刪除這個效果 40 } 41 } 42 } 43 44 unit.card.workstate="wait"; 45 /*for(var key in skills) 46 { 47 if(skill.ap=="p_wake")//p_wake的是觸發式的狀態,它的last由技能自身控制? 48 { 49 if(skill.eval) 50 { 51 eval(skill.eval); 52 skill.last--; 53 if(skill.last==0)//若是持續時間結束 54 { 55 if(skill.eval2) 56 { 57 eval(skill.eval2); 58 } 59 60 delete skills[key];//刪除這個效果 61 } 62 } 63 } 64 }*/ 65 } 66 card_Closed2=null;//解除選定 67 }
可是如今的進入下一回合還缺乏足夠醒目的回合提示。
這樣就完成了一個最基礎的相似戰棋遊戲的操做流程,由於時間有限只介紹了主幹代碼,更多的細節還須要經過調試獲取。目前尚未聲音、AI和網絡的設定,將來應該會添加WebSocket多人聯網功能和Babylon.js內置的3D音效功能,但AI很難說。