AS3聊天單行輸入框圖文混排完美實現

來源:互聯網
上載者:User

標籤:as3   聊天   表情   圖文混排   輸入框   

幾年前剛畢業,第一個遊戲模組做的就是聊天。到現在,幾個遊戲寫過幾次聊天模組,
之前在4399做的《幻龍騎士》(又名《神騎士》),還有上周六剛上線的《瘋狂的子彈》,
用的是同一套代碼,聊天輸入框沒有圖文混排,而是用符號代替,輸出面板才有圖文混排。

輸出面板的圖文混排由於內容沒有鍵鼠操作,實現很簡單,不在本文討論之列;當然本
文的代碼中抽出一小部分就可以實現了。以上兩款遊戲沒有加密,有興趣的可以去弄來看看,
《子彈》裡面的鍵鼠、技能、射擊、彈道搞得好累人啊 ?(?ε?? )

特別是彈道計算,是高中畢業後到現在用過的最複雜的數學和物理知識。直線彈道、直
線貫穿彈道、雷射彈道、散彈彈道、散彈貫穿彈道、能量彈道、矩形彈道、扇形彈道、圓形
彈道、扇形彈道、虛擬彈道、平行彈道,以及其中幾種彈道的組合彈道。用到各種幾何方程,
還有平面幾何座標系的平移和旋轉,以及角度、弧度、向量計算等等;學校裡學的方程、公
式只是基礎,直接用的話效率很低,遊戲裡可以做一些簡化計算以提高效率。。。。。。

貌似寫跑題了,我是要寫什麼來著?哦,是輸入框的圖文混排哈。

現在在多玩YY做一款暫名為《神魔三國》的遊戲,預計九月上線。恰巧又接手聊天模組,
需要做輸入框的圖文混排,花了兩天時間摸索,於是就有了本文。

補充:遊戲裡私聊面板多行輸入框的圖文混排(類似QQ聊天面板)請看這裡:
AS3聊天多行輸入框圖文混排完美實現


package{import flash.display.MovieClip;import flash.display.Shape;import flash.display.Sprite;import flash.events.Event;import flash.events.KeyboardEvent;import flash.filters.GlowFilter;import flash.geom.Rectangle;import flash.text.TextField;import flash.text.TextFieldType;import flash.text.TextFormat;import flash.text.TextFormatAlign;import flash.ui.Keyboard;import flash.utils.getDefinitionByName;/** * 遊戲交談輸入框圖文混排完美實現 * 沒用到任何第三方類,看import就知道 * 已添加一百多行注釋,實際代碼大概三百行 * 主要測試點: * 1.選中一部分圖文,再按刪除,或插入表情,或Ctrl+X,或Ctrl+V等 * 2.文字框滿行後,按住左右鍵或滑鼠拖選左右移動,觸發橫向滾動的情況 * 用以上兩點測試,很多第三方控制項,或頁遊中都會出現問題。 * 另外,本實現不會做成像RichTextField那樣的通用的類庫, * 越是通用的東西,效率越低,建議根據實際對代碼做相應調整,Good Luck! * @since 2014.6.26 * @email [email protected] */public class InputText extends Sprite{/*表情預留位置,看起來像空格,但不是空格;   輸入框一般不能屏蔽空格輸入,但可以屏蔽此符號的輸入,因此用來做預留位置很合適*/private const PLACEHOLDER:String = String.fromCharCode(12288);public var textField:TextField; //文本輸入框private var mcLayer:Sprite; //用於放置表情的容器,在文字框上面一層private var dataList:Vector.<Express>; //儲存表情資訊的VOprivate var begin:int = 0; //輸入框中選中文本的開始索引private var end:int = 0; //輸入框中選中文本的結束索引,若無選擇文本,則 begin==end,並且等於caretIndex private var scrollH:int = 0;//文字框滿行後向左滾動的距離private var keyCode:uint = 0; //上一次所按的鍵盤碼private var defaultFormat:TextFormat;//文本預設格式private var placeFormat:TextFormat;//表情預留位置的格式/** * 遊戲交談輸入框圖文混排 * @param w 輸入框寬度 * @param h 輸入框高度 */public function InputText(w:Number = 170, h:Number = 20){defaultFormat = new TextFormat();defaultFormat.color = 0xFFFFFF;defaultFormat.size = 12;defaultFormat.letterSpacing = 0;//此處不能省略defaultFormat.align = TextFormatAlign.LEFT;defaultFormat.font = "SimSun";//宋體//調整預留位置的寬度,使之比表情的寬度大一點點//不要用預留位置的大小(size)去調整寬度,除非你的表情和字型差不多大小,多麼痛的領悟placeFormat = new TextFormat();placeFormat.letterSpacing = 16;textField = new TextField();textField.width = w;textField.height = h;textField.type = TextFieldType.INPUT;textField.defaultTextFormat = defaultFormat;/*   設定為單行文字框,不建議像《仙俠道》那樣用多行文字框,事情會變得更複雜,相信我   《仙俠道》沒有加密,去把代碼弄來看看就知道了,用了多行還要處理上下鍵滾行的噁心問題   而一般遊戲中都會用上下鍵來處理聊天緩衝功能,即上下鍵翻看前幾次已發送的聊天內容,以避免重複輸入 */textField.multiline = false;textField.wordWrap = false;textField.mouseWheelEnabled = false;textField.restrict = "^" + PLACEHOLDER; //屏蔽預留位置,讓玩家不能輸入此符號textField.filters = [new GlowFilter(0x0, 1, 3, 3, 3)];textField.maxChars = 100;textField.addEventListener(Event.SCROLL, onTextScroll);textField.addEventListener(Event.CHANGE, afterChange);textField.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);this.addChild(textField);//遮罩很有必要,滿行向左滾動後表情可能只顯示一半var mask:Shape = new Shape();mask.graphics.beginFill(0);mask.graphics.drawRect(0, 0, w, h);mask.graphics.endFill();this.addChild(mask);mcLayer = new Sprite();mcLayer.mask = mask;this.addChild(mcLayer);dataList = new Vector.<Express>();}/** * 文字框滾動事件,主要是處理橫向滾動 */private function onTextScroll(e:Event):void{//只處理橫向滾動,差異化處理很有必要,不加此判斷可能導致遞迴堆疊溢位if(textField.scrollH != scrollH){scrollH = textField.scrollH;render();}}/** * 鍵盤事件,不要在此處理Backspace、Delete等事件,相信我 */private function onKeyDown(e:KeyboardEvent):void{begin = textField.selectionBeginIndex;end = textField.selectionEndIndex;keyCode = e.keyCode;//發送聊天資料if(keyCode == Keyboard.ENTER){if(textField.length > 0){var chatContent:String = this.srcContent;//TODO: 這裡是向服務端發送資料的邏輯}}}/** * 常值內容改變事件, 此處是精華所在,理解了就會發現其實很簡單 */private function afterChange(e:Event = null):void{var $begin:int = begin;if(begin == end){if(keyCode == Keyboard.BACKSPACE){delExpress(begin - 1);$begin = (begin > 0) ? begin - 1 : 0;}else if(keyCode == Keyboard.DELETE){delExpress(begin);}}else//選中了一部分文本,刪除其中可能包含的表情{for(var i:int = begin; i < end; i++){delExpress(i);}}//刪除表情後,更新表情索引updateExpressIndex($begin);render();keyCode = 0;}/** * 圖文混排渲染,此處是痛點所在,理解了就會發現其實沒那麼簡單 */private function render():void{if(textField.length == 0){clear();return;}/*設定表情預留位置的寬度,上文已解釋過,不贅述*/textField.setTextFormat(defaultFormat, 0, textField.length);var textStr:String = textField.text;for(var i:int = 0; i < textStr.length; i++){var char:String = textStr.charAt(i);if(char == PLACEHOLDER){textField.setTextFormat(placeFormat, i, i + 1);}}/*先清理所有的表情,此處有最佳化空間,但建議不要折騰,除非純粹研究*/while(mcLayer.numChildren > 0){mcLayer.removeChildAt(0);}/*下面這一段是處理文本滿行後,按左右鍵或滑鼠拖選左右移動,觸發橫向滾動的情況*/var showBegin:int = -1;//輸入框中能看到的第一個字元索引var showEnd:int = -1;//輸入框中能看到的最後一個字元索引const sRight:int = textField.scrollH + textField.width; //輸入框中能看到的第一個字元在「整個」文本行中的橫座標/*遍曆每個字元的座標以找出showBegin和showEnd的值*/for(i = 0; i < textField.length; i++){//此處值得一提的是,不管文本橫向怎麼滾動,所有字元的座標都是正數,你知道為什麼嗎?var rect:Rectangle = textField.getCharBoundaries(i);if(showBegin == -1 && rect.right > textField.scrollH){showBegin = i;}if(showEnd == -1 && rect.left > sRight){showEnd = i - 1;break;}}//未滿行的情況,所有字元都顯示if(showEnd == -1){showEnd = textField.length - 1;}/*把文字框中能看到的表情預留位置都加上實際表情*/for each(var data:Express in dataList){if(data.index >= showBegin && data.index <= showEnd){rect = textField.getCharBoundaries(data.index);//根據表情和文字大小,微調座標,使表情在文本中置中data.mc.x = rect.x + 2 - textField.scrollH;data.mc.y = rect.y - 6;mcLayer.addChild(data.mc);}}}/** * 在文字框當前游標處插入表情 * @param sign Emoji,據此符號能夠建立出表情顯示對象 */public function insertExpression(sign:String):void{if(textField.length >= textField.maxChars){return;}//此處需要重新擷取選中文本的開始和結束索引,文字框失去焦點後的怪異問題begin = textField.selectionBeginIndex;end = textField.selectionEndIndex;//刪除選中文本中可能包含的表情for(var i:int = begin; i < end; i++){delExpress(i);}//把選中的文本替換為表情預留位置,沒有選中則是插入textField.replaceText(begin, end, PLACEHOLDER);//建立表情資料並儲存,保持有序var $i:int = -1;for(i = 0; i < dataList.length; i++){var data:Express = dataList[i];if(data.index >= begin){$i = i;break;}}var mc:MovieClip = getMovieClip(sign);if($i == -1){dataList.push(new Express(begin, sign, mc));}else{dataList.splice($i, 0, new Express(begin, sign, mc));}//插入表情後,更新表情索引updateExpressIndex(begin);render();textField.setSelection(begin + 1, begin + 1);}/** * 更新表情資料索引,索引為預留位置的索引 * @param $begin 只更新第$begin個字元後面的資料,前面的不變 */private function updateExpressIndex($begin:int):void{var $i:int = -1;for(var i:int = 0; i < dataList.length; i++){if(dataList[i].index >= $begin){$i = i;break;}}if($i != -1){var textStr:String = textField.text;for(i = $begin; i < textStr.length; i++){if(textStr.charAt(i) == PLACEHOLDER){dataList[$i++].index = i;}}}}/** * 取指定索引處的表情資料 * @param index 表情索引,即預留位置索引 * @return 表情資料 */private function getExpress(index:int):Express{for each(var data:Express in dataList){if(data.index == index){return data;}}return null;}/** * 刪除指定索引處的表情資料,並移除表情顯示對象 * @param index 表情索引 */private function delExpress(index:int):void{for(var i:int = 0; i < dataList.length; i++){var data:Express = dataList[i];if(data.index == index){dataList.splice(i, 1);if(mcLayer.contains(data.mc)){mcLayer.removeChild(data.mc);}return;}}}/** * 用插入的Emoji建立表情動畫 * @param sign Emoji * @return 表情動畫 */private function getMovieClip(sign:String):MovieClip{/*   此處符號可根據實際遊戲做些處理,   比如符號為 /:01,重新構造為 chat_expression_01 等   只要構造後的字串為你在fla裡匯出的表情類即可 */var $_class:Class = getDefinitionByName(sign) as Class;var $_item:MovieClip = new $_class();$_item.mouseChildren = false;$_item.mouseEnabled = false;return $_item;}/** * 擷取圖文混排未經處理資料,用於發向服務端,並廣播回來 */public function get srcContent():String{var charArr:Array = [];var textStr:String = textField.text;for(var i:int = 0; i < textStr.length; i++){var char:String = textStr.charAt(i);if(char == PLACEHOLDER){var data:Express = getExpress(i);charArr.push(data.sign);}else{charArr.push(char);}}return charArr.join("");}/** * 設定圖文混排未經處理資料,主要用於上下鍵翻看聊天記錄時解析表情用 * 輸出面板的圖文混排由於沒有鍵鼠操作,更簡單,不在本文討論之列; * 不過,相信你看到這裡也早知道該怎麼做了 */public function set srcContent(content:String):void{clear();//這裡的chat_expression_xx是我的遊戲裡的表情匯出類,根據你的遊戲自由調整var reg:RegExp = new RegExp("chat_expression_[0-9]{2}", "ig");var signArr:Array = content.match(reg);content = content.replace(reg, PLACEHOLDER);if(content.length > textField.maxChars){content = content.substr(0, textField.maxChars);}for(var i:int = 0; i < content.length; i++){var char:String = content.charAt(i);if(char == PLACEHOLDER){var sign:String = signArr.shift() as String;var mc:MovieClip = getMovieClip(sign);dataList.push(new Express(i, sign, mc));}}textField.text = content;render();}/** * 清空文字框即相關資料 */public function clear():void{textField.htmlText = "";begin = end = scrollH = keyCode = 0;dataList = new Vector.<Express>();if(mcLayer != null){while(mcLayer.numChildren > 0){mcLayer.removeChildAt(0);}}}}}/*===============================================*/import flash.display.MovieClip;class Express // 內部類,不用建立類檔案{public var index:int;public var sign:String;public var mc:MovieClip;/** * 表情資料 * @param index 表情索引,即表情預留位置在文本中的索引 * @param sign Emoji,用於建立表情動畫顯示對象 * @param mc 表情動畫,已經用sign建立好的顯示對象 */public function Express(index:int, sign:String, mc:MovieClip){this.index = index;this.sign = sign;this.mc = mc;}}


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.