做游戏聊天系统,要注意这些坑

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

 

聊天框图文混排

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

  聊天输入框如果没有特殊要求,不需要支持显示表情movieclip(后面简称mc),则一般采用AS3TextInput组件足矣,即只能输入纯文本。(在我接触的很多款网游中,聊天输入框都是纯文本的)

  既然输入框只支持纯文本,那怎么插入表情呢?这就需要实现图片与文字的互相转化了。实现原理并不难,简单来说,就是当玩家从表情面板选中表情时,自动将其转换成表情代码(格式自定),插入输入框中。在玩家发送消息后,进行文本解析,利用正则表达式将聊天消息里的表情代码解析替换成占位符(其实就是空格),然后在相应位置上将表情mc显示出来。

  原理不难,难在实现细节。这里总结需注意的细节。

  1、表情格式。表情格式不要选择过于复杂,过多字数的格式,越简单越好。笔者在初次实现时选择的格式是”[img]两位数字[/img]”,这样插入一个表情,实际等同于输入了13个字符,如果聊天限制字数以微博140个字作为标准,那只能插入10个表情,显然不合理。此外,简单的表情格式方便高频玩家直接手动输入表情代码,体验更好。

  2、在玩家打字的过程中,有可能中途点击表情面板去插入表情,此时舞台焦点就不再为输入框(即输入框光标不再闪动,玩家插入表情后会发现不能继续打字),为了无损玩家聊天体验,需要在插入表情代码后,重设舞台焦点为输入框:

public function setFocus():void
{
      //将舞台焦点设置为聊天输入框
      Context.stage.focus = textField;
      //将聊天光标设置到文本末尾            
      textField.setSelection(textField.length, textField.length);
}

  3、半角空格≠全角空格,关于占位符的选择。在前文提过,我们需要利用正则表达式将表情替换成占位符,才能给表情movieclip预留足够位置显示。笔者建议占位符一定要使用全角空格(中文输入法中Shift+Space可切换半角/全角),因为行末刚好是全角空格时,文本会自动换行,半角空格则不会。如果使用半角空格作为占位符,就会出现一种情况,位于行末的表情代码刚好被替换成几个半角空格,即使该行的其他文本,加上几个空格的宽度已经超出了textField所设置的width值,该行文本仍然不会换行。这样就导致表情mc被添加到文本的外边。如下图所示,红框内为Textfieldwidth,由于使用半角空格作为占位符,有个表情mc华丽丽地跳脱出了文本框。因此,请使用全角空格做占位符!

  

  4、正则表达式解析表情代码,这个可以说是整个图文混排文本最关键的代码了,其实也只寥寥几十行代码:

/**
* 设置文本 外部添加内容请使用此方法
*/        
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;

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

 

转载于:https://www.cnblogs.com/leoin2012/p/4852436.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值