上一節中已經介紹了RPG遊戲中地圖怎麼實現,在RPG遊戲的地圖中通常有各種遮擋,比如人物站在房屋的後面的時候,房子應該遮擋住人物,這就涉及到各種建築物和人物的排序顯示。另外,上一節中我為了測試地圖,已經添加了一個簡單的人物類Character,這個類是我從其他遊戲中拷貝過來的,本次除了介紹地圖上的遮擋之外,也會詳細介紹一下一個人物類的具體實現方法,包括它的動作改變,方向變換以及行走。
下面首先來說說遮擋,第一種做法是把各個建築物分離出來,如下面圖中的建築物,
如果經過處理,按照一定的順序顯示到地圖上,就應該是下面的效果。
如果要實現中的效果,就需要把人物和建築物放到同一層,並且在人物移動的時候,即時的對地圖上所有的建築物和人物進行排序。
另外,關於地圖的遮擋,還有一種較為簡單的做法,就是把建築物層和人物層分離開來,將建築層放到人物層的上面,然後把建築物變為半透明,比如下面的圖片。
這樣,如果將它放到人物層的上方,那麼人物移動到相應的位置的時候,就會出現種的效果。
可以看到,當人物走到房子的後面的時候,被遮擋住的部分變成了半透明,有不少遊戲都採用了這種做法,喜歡玩RPG遊戲的朋友們是不是很熟悉?如何把這個透明層添加到遊戲中呢,在上一節種已經介紹了地圖的圖片如何加到遊戲中,這個透明層可以用同樣的方法來實現,我就不累贅了,稍後會放出代碼下載,大家可以自己看一下。
接下來,我來詳細說明一下如何來實現一個人物類Character,一個人物有多種動作,比如下面的圖。
這隻是一個人物動作的一部分,因為人物有站立,走動,跑動,坐下,每個動作都有八個方向,再加上戰鬥的時候,有攻擊,防禦,摔倒等等,如果這些圖片全部都一次性讀取的話,那麼圖片讀取的時間會很長,尤其是網頁開發。很明顯,這樣做是很不明智的,所以要想快速的顯示一個人物,我們應該唯讀取需要顯示的人物動作圖片,即使是上面的圖,雖然是一個跑步的動作,但是因為有八個方向,也不需要一次性讀取,所以我們可以把上面的圖片進行再分解,也就是說,把每個動作的八個方向都再次進行分解成八張圖片,比如下面,是一組向下走動的圖片:
那麼,這樣看起來是不是已經夠了?當然不是,因為每進入一張地圖之後,地圖上可能同時出現幾個或者幾十個人物,為了讓遊戲更快速的顯示,通常的做法是先選用一張特殊的圖片來代替,然後再讀取正確的圖片,當圖片讀取完之後,再把原來的圖片替換,其實這種做法我已經在之前的《遊戲指令碼的設計與開發》-(戰棋部分)2.2 軍隊降臨戰場一文中介紹過了,在這裡我再來簡單說明一下,下面的Action類可以顯示人物的一個動作,代碼中的chara-default是預先讀取好的一張圖片,先顯示然後再進行相應圖片的讀取,而之所以用LAnimationTimeline,是因為要顯示的人物都是動態,LAnimationTimeline可以很方便的播放一組圖片的動畫。
function Action(index,action,direction){var self = this;base(self,LSprite,[]);var list = LGlobal.divideCoordinate(5120,240,1,16);var data = new LBitmapData(LMvc.datalist["chara-default"],0,0,320,240);self.anime = new LAnimationTimeline(data,list);self.anime.speed = 1;self.addChild(self.anime);loader = new LLoader();loader.parent = self;loader.addEventListener(LEvent.COMPLETE,self.loadOver);loader.load(LMvc.IMG_PATH+"character/"+index+"/"+action+"-"+direction+".png","bitmapData");}Action.prototype.loadOver = function(event){var self = event.target.parent;self.anime.bitmap.bitmapData = new LBitmapData(event.currentTarget,0,0,320,240);};
之所以把人物的動作單獨做成一個類,是因為這樣的話,我就不需要事先讀取人物的所有動作,連判斷都省了,要顯示某個動作的話,我只需要new一個Action類的執行個體並傳入相應的參數,然後這個Action就會自動顯示預設的圖片,並進行圖片的讀取,省去了大量的判斷和處理。
下面來看Character類的實現,先來看下面的代碼。
var CharacterAction = {STAND:"stand",MOVE:"move"};var CharacterDirection = {DOWN:"down",LEFT:"left",RIGHT:"right",UP:"up",LEFT_DOWN:"left_down",RIGHT_DOWN:"right_down",LEFT_UP:"left_up",RIGHT_UP:"right_up"};function Character(index,w,h){var self = this;base(self,LSprite,[]);self.index = index;self.list = {};self.to = new LPoint(self.x,self.y);self.roads = [];self.layer = new LSprite();self.addChild(self.layer);self.layer.x = w*0.5-320*0.5;self.layer.y = h*0.5-240*0.5 - 50;self.w = w;self.h = h;self.step = self.moveStep = 4;self.moveBevelStep = self.moveStep*Math.sin(45*Math.PI/180);self.moveBevelStep = (self.moveBevelStep*100 >>> 0)/100;self.directionList = {"-1,-1":CharacterDirection.LEFT_UP,"-1,0":CharacterDirection.LEFT,"-1,1":CharacterDirection.LEFT_DOWN,"0,-1":CharacterDirection.UP,"0,1":CharacterDirection.DOWN,"1,-1":CharacterDirection.RIGHT_UP,"1,0":CharacterDirection.RIGHT,"1,1":CharacterDirection.RIGHT_DOWN};self.addEventListener(LEvent.ENTER_FRAME,self.onframe);self.setActionDirection(CharacterAction.STAND,CharacterDirection.DOWN);}
首先CharacterAction裡存放動作,CharacterDirection裡存放方向。
而人物類Character的構造器,我來解釋一下各個變數的功能。
1,layer是一個LSprite對象,主要是為了調整人物圖片的相對位置。
2,在上一節的例子當中,人物在水平和豎直上的移動速度與斜角上的移動速度是不一樣的,因為做法是x軸和y軸上移動的速度為v,當斜角移動的時候,我直接讓人物在x軸和y軸上同時進行移動,矩形的對角線長度當然是大於邊長的,這樣斜角上移動的速度很明顯就變大了,所以這次設定了moveStep和moveBevelStep兩個速度,moveBevelStep是通過moveStep計算而來的斜角移動時的速度。
3,to變數是移動目標的座標,當人物的當前座標和移動目標的座標不一樣的時候,會進行移動的處理。
4,roads變數是人物的移動路徑,當自動尋路完成之後,會把返回的路徑傳給它,人物會根據這個路徑做相應的移動處理。
5,list變數是為了儲存人物的各個動作而建立的。
6,directionList變數是為了在人物移動時,計算人物的移動方向而建的。
7,setActionDirection函數是設定人物的動作,具體看接下來的說明。
Character.prototype.setActionDirection = function(action,direction){var self = this;if(self.action == action && self.direction == direction)return;var key = action+"-"+direction;if(!self.list[key]){self.list[key] = new Action(self.index,action,direction);self.layer.addChild(self.list[key]);}if(self.actionObject){self.actionObject.visible = false;}self.actionObject = self.list[key];self.actionObject.visible = true;self.action = action;self.direction = direction;};Character.prototype.changeAction = function(action){var self = this;self.setActionDirection(action,self.direction);};Character.prototype.changeDirection = function(direction){var self = this;self.setActionDirection(self.action,direction);};
changeAction函數和changeDirection函數分別通過調用setActionDirection函數來改變人物的動作和方向,setActionDirection函數中首先判斷當前動作和要設定的動作是否相同,如果相同則不做任何處理,否則判斷動作是否已經載入,如果沒有載入則建立Action執行個體。
Character.prototype.setCoordinate = function(x,y){var self = this;self.x = self.to.x = x;self.y = self.to.y = y;};
setCoordinate函數直接設定人物的座標。
Character.prototype.getTo = function(){var self = this;return [self.to.x/self.w >>> 0,self.to.y/self.h >>> 0];};Character.prototype.setTo = function(){var self = this;var road = self.roads.shift();self.to.x = road.x*self.w;self.to.y = road.y*self.h;};
上面兩個函數分別是擷取和設定人物的移動目標的座標,最後來看看人物的移動處理,代碼如下。
Character.prototype.move = function(){var self = this;if(self.x == self.to.x && self.y == self.to.y)return;if(self.x != self.to.x && self.y != self.to.y){self.step = self.moveBevelStep;}else{self.step = self.moveStep;}var mx = self.getValue(self.x , self.to.x),my = self.getValue(self.y , self.to.y);self.x += self.step*mx;self.y += self.step*my;var cx = self.getValue(self.x , self.to.x),cy = self.getValue(self.y , self.to.y);if(mx != cx || my != cy){if(self.roads.length == 0){self.x = self.to.x;self.y = self.to.y;self.changeAction(CharacterAction.STAND);self.parent.parent.parent.controller.mapMove();return;}var next = self.roads[0];var nx = self.getValue(self.to.x , next.x),ny = self.getValue(self.to.y , next.y);if(mx != nx || my != ny){self.x = self.to.x;self.y = self.to.y;}if(self.roads.length > 0){self.setTo();}}self.setMoveDirection(mx,my);self.parent.parent.parent.controller.mapMove();};Character.prototype.onframe = function(event){var self = event.target;self.move();};Character.prototype.setMoveDirection = function(x,y){var self = this;var direction = self.directionList[x+","+y];self.setActionDirection(CharacterAction.MOVE,direction);};
當人物發生移動的時候,要設定移動速度,當人物只在x軸方向上或者只在y軸方向上移動的時候,把速度設定為moveStep,同時在兩個方向上移動時,也就是斜角方向上移動時,把速度設定為moveBevelStep。
然後,根據計算好的速度一個座標一個座標的進行移動,當移動完一個座標之後,判斷是否繼續移動或者停止,最後調用setMoveDirection函數設定相應的移動方向,最後調用地圖控制器的mapMove函數,讓人物保持在地圖的中央。
OK,經過上面的一番折騰,地圖中的遮擋和人物移動相關的處理就差不多了,測試連接如下:
http://lufylegend.com/demo/test/lsharp/rpg-lshape-02/index.html
效果如下:
最後,給出本次的代碼下載:
http://lufylegend.com/demo/test/lsharp/rpg-lshape-02/rpg-lshape-02.zip
關於RPG的開發到目前為止,還沒有放到L#的指令碼中,下次我會把上面RPG開發指令碼化,然後接著接著介紹一次性在地圖中添加多個人物,人物的排序,或者還可以再加上點人物對話的相關處理,請期待下次更新。