AS3聊天单行输入框图文混排完美实现

时间:2022-09-10 15:09:46

几年前刚毕业。第一个游戏模块做的就是聊天。到如今。几个游戏写过几次聊天模块。

之前在4399做的《幻龙骑士》(又名《神骑士》),还有上周六刚上线的《疯狂的子弹》,

用的是同一套代码,聊天输入框没有图文混排,而是用符号取代,输出面板才有图文混排。



输出面板的图文混排因为内容没有键鼠操作。实现非常easy,不在本文讨论之列;当然本

文的代码中抽出一小部分就能够实现了。以上两款游戏没有加密,有兴趣的能够去弄来看看。

《子弹》里面的键鼠、技能、射击、弹道搞得好累人啊 ლ(╹ε╹ლ )



特别是弹道计算,是高中毕业后到如今用过的最复杂的数学和物理知识。直线弹道、直

线贯穿弹道、激光弹道、散弹弹道、散弹贯穿弹道、能量弹道、矩形弹道、扇形弹道、圆形

弹道、扇形弹道、虚拟弹道、平行弹道。以及当中几种弹道的组合弹道。用到各种几何方程,

还有平面几何坐标系的平移和旋转,以及角度、弧度、向量计算等等;学校里学的方程、公

式仅仅是基础,直接用的话效率非常低,游戏里能够做一些简化计算以提高效率。

。。。

貌似写跑题了,我是要写什么来着?哦,是输入框的图文混排哈。

如今在多玩YY做一款暂名为《XX三国》的游戏,估计九月上线。恰巧又接手聊天模块,

须要做输入框的图文混排,花了两天时间摸索。于是就有了本文。



补充:游戏里私聊面板多行输入框的图文混排(类似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 cceevv@163.com
*/
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>; //存储表情信息的VO private 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: 这里是向服务端发送数据的逻辑
}
}
} /**
* 文本内容改变事件, 此处是精华所在,理解了就会发现事实上非常easy
*/
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 表情符号,据此符号可以创建出表情显示对象
*/
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;
}
}
} /**
* 用插入的表情符号创建表情动画
* @param sign 表情符号
* @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 表情符号,用于创建表情动画显示对象
* @param mc 表情动画。已经用sign创建好的显示对象
*/
public function Express(index:int, sign:String, mc:MovieClip)
{
this.index = index;
this.sign = sign;
this.mc = mc;
}
}

AS3聊天单行输入框图文混排完美实现的更多相关文章

  1. 【转】关于FLASH中图文混排聊天框的小结

    原文链接 图文混排也是FLASH里一个很古老的话题了,我们不像美国佬那样游戏里面聊天框就是聊天框,全是文字干干净净,也不像日本人发明了并且频繁地使用颜文字.不管是做论坛.做游戏,必定要实现的一点就是带 ...

  2. ListView异步加载图片,完美实现图文混排

    昨天参加一个面试,面试官让当场写一个类似于新闻列表的页面,文本数据和图片都从网络上获取,想起我还没写过ListView异步加载图片并实现图文混排效果的文章,so,今天就来写一下,介绍一下经验. Lis ...

  3. 仿QQ聊天图文混排流程图【适用于XMPP】

      图文混排流程图.graffle4.8 KB   下面附上图片素材: 表情.zip692.5 KB     下面是字符串与图片的详细对应关系:                 "[呲牙]& ...

  4. 用NSAttributedString实现简单的图文混排

    iOS7以后,因为TextKit的强大,可以用NSAttributedString很方便的实现图文混排(主要是利用了NSTextAttachment). 关于Textkit的牛逼之处,可以参考objc ...

  5. Unity UGUI图文混排源码&lpar;二&rpar;

    Unity UGUI图文混排源码(一):http://blog.csdn.net/qq992817263/article/details/51112304 Unity UGUI图文混排源码(二):ht ...

  6. Unity UGUI实现图文混排

    目前在unity实现图文混排的好像都是通过自定义字体然后在文本获取字符的位置,用图片替换掉图片标签,这样对于支持英文来说,并没有什么影响.然后对于中文来说就是一个相当麻烦的事了,毕竟图文混排多用于游戏 ...

  7. &lbrack;UGUI&rsqb;图文混排&lpar;一&rpar;:标签制定和解析

    参考链接: https://github.com/SylarLi/RichText/tree/master/Assets/Scripts 正则表达式: https://blog.csdn.net/ly ...

  8. Laya的图文混排

    参考: Laya图文混排 Laya的图文混排教程 编辑模式F9,增加laya.html.js库 在层级窗口右键,添加一个HtmlDivElement组件 大致的原理: 1. 例如输入框的字符串是 &q ...

  9. 图文混排--CoreText的简单运用

    常见的在一些微博微信中可以看见一段文字中有不同的字体,字体有不同的颜色,并且可能会有一些笑脸之类的表情,这些可以通过图文混排做到. 图文混排可以通过WebView和CoreText做到,其他还有别的方 ...

随机推荐

  1. 了解SpringMVC框架及基本工作流程

    传统原生的JSP+Servlet在开发上过程上虽然简单明了,JSP页面传递数据到Servlet,Servlet整理数据(逻辑开发)或者从数据库提取数据接着再转发到JSP页面上,但是其似乎只能止步于此, ...

  2. 关于archlinux下的ralink5370网卡

    驱动此网卡要使用 rt2800usb,rt2800lib 这两个模块 顺便说一下对模块进行操作的命令: rmmod 模块名 //为移除模块 insmod 模块所在路径 //为添加模块 查看网卡是否能被 ...

  3. css之opacity

    设置div元素的不透明级别 语法: value :从0.0(完全透明)到1.0(完全不透明) inherit:应该从父元素继承opacity属性 z-index 属性设置元素的堆叠顺序,仅能在定位元素 ...

  4. Visual Studio原生开发的10个调试技巧(转)

    本文由 伯乐在线 - JingerJoe 翻译自 Marius Bancila.转载请参见文章末尾处的要求.   [感谢@_La_Isla_Bonita 的热心翻译.如果其他朋友也有不错的原创或译文, ...

  5. C&num; - Sql数据类型的对应关系

    <Language From="SQL" To="C#"> <Type From="bigint" To="lo ...

  6. SQlSERVER生成唯一编号

    基数表-用来存储编号前缀和类型 建表如下 CREATE TABLE [dbo].[SerialNo]( [sCode] [varchar](50) NOT NULL, [sName] [varchar ...

  7. 一道关于call和this的JS面试题

    一个有情怀的程序员...... 2017年始,希望成为一个更好的自己,想自己所想,爱自己所爱 ----------------------------------------------------- ...

  8. 南阳OJ-2-括号配对问题---栈的应用

    题目链接: http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=2 题目大意: 有一行括号序列,请你检查这行括号是否配对. 思路: 直接用栈来模拟 ...

  9. JVM回收方法区内存

    很多人认为方法区(或者HotSpot虚拟机中的永久代)是没有垃圾收集的,Java虚拟机规范中确实说过可以不要求虚拟机在方法区实现垃圾收集,而且在方法区进行垃圾收集的“性价比”一般比较低:在堆中,尤其是 ...

  10. python tkinter Text

    """小白随笔,大佬勿喷""" '''tkinter —— text''' '''可选参数有: background(bg) 文本框背景色: ...