作游戏聊天系统,要注意这些坑 关于FLASH中图文混排聊天框的小结

笔者最近刚作了个聊天系统,开发过程当中踩了很多的坑,在此总结下经验教训,以便回顾参考,也但愿他人看到后能够少走弯路。这里不全是贴代码,主要提供聊天系统的实现思路,以及须要注意的点。html

 

聊天框图文混排正则表达式

  在关于FLASH中图文混排聊天框的小结一文中已经总结了几种图文混排的实现方式。对于不须要拉伸缩放的表情聊天框,能够直接用AS3Textfield类本身实现两层结构的文本类,这种是最简单,笔者也是采用了这种作法。api

  聊天输入框若是没有特殊要求,不须要支持显示表情movieclip(后面简称mc),则通常采用AS3TextInput组件足矣,即只能输入纯文本。(在我接触的不少款网游中,聊天输入框都是纯文本的)工具

  既然输入框只支持纯文本,那怎么插入表情呢?这就须要实现图片与文字的互相转化了。实现原理并不难,简单来讲,就是当玩家从表情面板选中表情时,自动将其转换成表情代码(格式自定),插入输入框中。在玩家发送消息后,进行文本解析,利用正则表达式将聊天消息里的表情代码解析替换成占位符(其实就是空格),而后在相应位置上将表情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被添加到文本的外边。以下图所示,红框内为Textfieldwidth,因为使用半角空格做为占位符,有个表情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 += "&lt;";
                        break;   
                    case '>':   
                        filtered += "&gt;";
                        break;   
                    case '"':   
                        filtered += "&quot;";
                        break;   
                    case '&':   
                        filtered += "&amp;";
                        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值时,也不会自动换行。这样当玩家输入大量空格时,聊天框没有及时换行,致使聊天框被拉得过长,从而影响了场景显示。

  这是Textfieldapi实现得很差之处,所以最好还本身判断宽高度是否超出了限制

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;

  这样的代码才能有效限制聊天框背景的宽高,避免某个玩家文字过长的冒泡,遮挡掉游戏场景的大部分显示。另外,还能够经过限制同屏场景聊天冒泡的最大数量,给聊天冒泡设置半透明等方法,来缓解冒泡遮挡场景的体验问题。

相关文章
相关标签/搜索