筆者最近剛作了個聊天系統,開發過程當中踩了很多的坑,在此總結下經驗教訓,以便回顧參考,也但願他人看到後能夠少走彎路。這裏不全是貼代碼,主要提供聊天系統的實現思路,以及須要注意的點。html
聊天框圖文混排正則表達式
在【關於FLASH中圖文混排聊天框的小結】一文中已經總結了幾種圖文混排的實現方式。對於不須要拉伸縮放的表情聊天框,能夠直接用AS3的Textfield類本身實現兩層結構的文本類,這種是最簡單,筆者也是採用了這種作法。api
聊天輸入框若是沒有特殊要求,不須要支持顯示錶情movieclip(後面簡稱mc),則通常採用AS3的TextInput組件足矣,即只能輸入純文本。(在我接觸的不少款網遊中,聊天輸入框都是純文本的)工具
既然輸入框只支持純文本,那怎麼插入表情呢?這就須要實現圖片與文字的互相轉化了。實現原理並不難,簡單來講,就是當玩家從表情面板選中表情時,自動將其轉換成表情代碼(格式自定),插入輸入框中。在玩家發送消息後,進行文本解析,利用正則表達式將聊天消息裏的表情代碼解析替換成佔位符(其實就是空格),而後在相應位置上將表情mc顯示出來。post
原理不難,難在實現細節。這裏總結需注意的細節。網站
一、表情格式。表情格式不要選擇過於複雜,過多字數的格式,越簡單越好。筆者在初次實現時選擇的格式是」[img]兩位數字[/img]」,這樣插入一個表情,實際等同於輸入了13個字符,若是聊天限制字數以微博140個字做爲標準,那隻能插入10個表情,顯然不合理。此外,簡單的表情格式方便高頻玩家直接手動輸入表情代碼,體驗更好。this
二、在玩家打字的過程當中,有可能中途點擊表情面板去插入表情,此時舞臺焦點就再也不爲輸入框(即輸入框光標再也不閃動,玩家插入表情後會發現不能繼續打字),爲了無損玩家聊天體驗,須要在插入表情代碼後,重設舞臺焦點爲輸入框:url
public function setFocus():void { //將舞臺焦點設置爲聊天輸入框 Context.stage.focus = textField; //將聊天光標設置到文本末尾 textField.setSelection(textField.length, textField.length); }
三、半角空格≠全角空格,關於佔位符的選擇。在前文提過,咱們須要利用正則表達式將表情替換成佔位符,才能給表情movieclip預留足夠位置顯示。筆者建議佔位符必定要使用全角空格(中文輸入法中Shift+Space可切換半角/全角),由於行末恰好是全角空格時,文本會自動換行,半角空格則不會。若是使用半角空格做爲佔位符,就會出現一種狀況,位於行末的表情代碼恰好被替換成幾個半角空格,即便該行的其餘文本,加上幾個空格的寬度已經超出了textField所設置的width值,該行文本仍然不會換行。這樣就致使表情mc被添加到文本的外邊。以下圖所示,紅框內爲Textfield的width,因爲使用半角空格做爲佔位符,有個表情mc華麗麗地跳脫出了文本框。所以,請使用全角空格作佔位符!spa
四、正則表達式解析表情代碼,這個能夠說是整個圖文混排文本最關鍵的代碼了,其實也只寥寥幾十行代碼:code
/** * 設置文本 外部添加內容請使用此方法 */ public function set htmlText(value:String):void { _htmlTxt = value; _textField.htmlText = value; checkImg(); addChildAt(_textField, 0); } public static const REG_IMG:RegExp = /#\d{2}/ig; /** * 查找圖片標籤
*/ private function checkImg():void { var content:String = _textField.text; var result:Array = []; var count:int; var objImg:Object; while(true) { //表情 objImg = REG_IMG.exec(content); if(objImg != null) { //#00 和下面的替換相差1個字符 objImg.index -= count * 1; result[result.length] = objImg; count++; }else break; } if(result.length > 0) { _htmlTxt = _htmlTxt.replace(REG_IMG, " ");//注:<font> </font> 用的全角空格 才能自動換行 _textField.htmlText = _htmlTxt; var obj:Object; for(var i:int=0; i< result.length; i++) { obj = result[i]; if(obj != null) { CaculatContent(obj.index,obj[0]); } } } } /** * 計算表情標籤 */ private function CaculatContent(startIndex:int,value:String):void { var mcName:String = value.slice(1, value.length); var displayObj:Sprite = Reflection.createMovieClipInstance("Movie"+int(mcName)) as Sprite; if(displayObj == null) return; var rect:Rectangle = _textField.getCharBoundaries(startIndex); if(rect != null) { displayObj.x = rect.x; displayObj.y = rect.y; addChild(displayObj); } }
字符過濾
對消息敏感內容的過濾通常交由後臺負責,前臺負責過濾處理文本中的特殊字符,如html標籤字符,轉義字符等。
遊戲聊天框裏的一段文本可能有不一樣樣式,不一樣顏色,通常人名還要手型顯示,支持點擊。所以通常使用htmlText方法設置文字,而不是text方法。htmlText是一個比text更爲複雜的方法,它接受html標籤。請看下面這段代碼:
var str:String = "第一行<br>第二行\n第三行\r第四行:\t<u><a href='http://www.baidu.com'>這是個網站連接</a></u><img src='https://www.baidu.com/img/bdlogo.png'></img>";
var text:TextField = new TextField(); text.wordWrap = true; text.multiline = true; text.width = 200; text.height = 200; this.addChild(text); text.htmlText = str;
運行結果以下:
能夠看到html標籤以及轉義字符,都實際起到了做用。若是不作過濾處理,就有可能被外掛製做者加以利用,在聊天包中的消息字段插入這些字符,用於刷屏以及散佈非法連接,圖片。
所以須要過濾掉轉義字符,html標籤,將它們變成單純的顯示文本。下面貼代碼:
package { /** * 正則表達式過濾字符工具 * @author ShuchangLiu */ public class HtmlRegexpUtil { public function HtmlRegexpUtil() { } /** * 進行字符過濾 * @param input * @return */ public static function filter(input:String):String { input = replaceTag(input); input = replaceSlash(input); return input; } /** * 過濾轉義字符 * @param input * @return */ public static function replaceSlash(input:String):String { input = input.replace(/\n/g, "\\n"); input = input.replace(/\r/g, "\\r"); input = input.replace(/\t/g, "\\t"); return input; } /** * * 基本功能:替換標記以正常顯示 * <p> * * @param input * @return String */ public static function replaceTag(input:String):String { if (!hasSpecialChars(input)) { return input; } var filtered:String = ""; var c:String; for (var i:int = 0; i <= input.length - 1; i++) { c = input.charAt(i); switch (c) { case '<': filtered += "<"; break; case '>': filtered += ">"; break; case '"': filtered += """; break; case '&': filtered += "&"; break; default: filtered += c; } } return (filtered.toString()); } /** * * 基本功能:判斷標記是否存在 * <p> * * @param input * @return boolean */ public static function hasSpecialChars(input:String):Boolean { var flag:Boolean = false; if ((input != null) && (input.length > 0)) { var c:String; for (var i:int = 0; i <= input.length - 1; i++) { c = input.charAt(i); switch (c) { case '>': flag = true; break; case '<': flag = true; break; case '"': flag = true; break; case '&': flag = true; break; } } } return flag; } } }
再看上面的例子,對文本進行過濾處理後的效果。
如今這些html標籤,轉義字符就失去相應的做用,只是單純的文字了。在實際開發中,除了對這兩點進行處理,還能夠再進一步過濾掉網址。
當心掉入Textfield的坑!textWidth > width ?textHeight > height ?
相信大部分aser都很篤定,width >=textWidth, height >=textHeight是絕對成立的,難道文本實際寬度/高度還能超過咱們設置的寬度/高度嗎? 很遺憾,是的!在絕大部分狀況下,Textfield實例的textWidth <=width,但不是100%成立,特定狀況下,textWidth > width 。究其緣由是其api實現並不完美,而致使這種狀況的罪魁禍首,又是前文說起的半角空格!看下面的代碼:
var str:String = "1 2"; var text:TextField = new TextField(); text.wordWrap = true; text.multiline = true; text.width = 50; this.addChild(text); text.htmlText = str; trace(text.width); //result:50 trace(text.textWidth); //result:192
筆者開發時由於不瞭解此點,掉進了坑。在MMORPG遊戲中,若是在場景(附近)頻道說話,除了在聊天框顯示消息,一般場景人物還會彈出聊天冒泡。聊天冒泡皮膚則根據文字寬高動態調整寬度、高度。所以,咱們能夠經過下面代碼簡單調整皮膚寬度。
_skin = new BubbleRoundRectWithArrowSkin(); addChildAt(_skin.display, 0); _message = new TextField(); _message.multiline = true; _message.wordWrap = true; _message.width = TEXT_MAX_WIDTH; addChild(_message); var bubbleWidth:int; bubbleWidth = _message.textWidth+ _skin.borderWidth; bubbleHeight = _message.textHeight+ _skin.borderHeight; _skin.setSize(bubbleWidth, bubbleHeight);
注意,上面代碼並不是用width方法,而是用textWidth方法去得到實際的文本寬度。不管_message 文本內容是什麼,在沒有設置autoSize的狀況下,_message.width的值是固定不變的,都爲TEXT_MAX_WIDTH。但這裏可能會出現問題,即實際文本寬度textWidth > width,致使聊天框背景會遠遠寬於文本。以下圖所示:
使用TextField類顯示文本,當文本行尾爲半角空格時(即英文輸入的空格),哪怕文本寬度已超出width值時,也不會自動換行。這樣當玩家輸入大量空格時,聊天框沒有及時換行,致使聊天框被拉得過長,從而影響了場景顯示。
這是Textfield的api實現得很差之處,所以最好還是本身判斷寬高度是否超出了限制。
if(_message.textWidth> TEXT_MAX_WIDTH) bubbleWidth = TEXT_MAX_WIDTH + _skin.borderWidth; else bubbleWidth = _message.textWidth+ _skin.borderWidth; if(_message.textHeight> TEXT_MAX_HEIGHT) bubbleHeight = TEXT_MAX_HEIGHT + _skin.borderHeight; else bubbleHeight = _message.textHeight+ _skin.borderHeight;
這樣的代碼纔能有效限制聊天框背景的寬高,避免某個玩家文字過長的冒泡,遮擋掉遊戲場景的大部分顯示。另外,還能夠經過限制同屏場景聊天冒泡的最大數量,給聊天冒泡設置半透明等方法,來緩解冒泡遮擋場景的體驗問題。