这几天写游戏的新手引导模块,需要做一个镂空遮罩,就是盖着整个场景,只让用户点击镂空的一个圆形区域。这个事看起来很简单。。。其实就是很简单啊!可是因为我把问题想得太复杂了,浪费了好多时间,特此文章一篇。也希望遇到同样问题的人,不再跳进这个坑。
先是上网搜索了,发现都是用混合模式的方法:1.先创建一个Sprite画一个矩形盖着整个场景,blendMode设置为Layer,2.再创建一个Sprite画一个小圆,blendMode设置为Erase。3.将小圆添加到矩形里。 OK,镂空图案出来了。中间一个圆确实是透明的,可是测试的时候发现,这个方法根本是个坑。因为即使中间的圆形区域看起来是透明的,却还是可以点击的!根本没有穿透。整个场景被盖得严严实实的。
然后又继续上网搜索,看了几篇坑更深的博文,“详细分析”了镂空遮罩这个东西在Flash Player似乎是不可实现的,并且不记得在哪看见了“模拟鼠标事件”的字眼。好吧,既然这样。那我就模拟鼠标点击事件吧。继续用混合模式的方法,制作一个全部盖着的遮罩。然后监听,中间那个圆形的鼠标事件获取点击位置,再然后克隆下这个事件想办法从遮罩后面的某个显示对象上抛出去。那要怎么确定从哪个对象上抛事件呢?我查了下API,发现DisplayObject有个不太常用的函数:getObjectsUnderPoint()。测试了下,发现它能拿到指定坐标下的所有显示对象列表,并且从外向内,从低层到顶层地排序。也就列表是最后一个元素是该坐标下最顶部的最小的一个显示对象。
嗯,感觉有点靠谱了。我就直接用最后一个显示对象抛出克隆的鼠标事件。放到工程里测试了下,发现果然可以。但是,只是部分可以,有的地方怎么点都没反应。我又断点调试看了下。原来那个方法返回的只是显示对象列表,但并不是所有显示对象都能冒泡鼠标事件的。能冒泡鼠标事件的对象必须是InteractiveObject的子类。而InteractiveObject是DisplayObject的子类。问题就出在这。DisplayObject的子类里有个Shape,它不是Interactive的子类。所以在它上面抛出鼠标事件,一点反应都没有。好吧,那我再判断下,如果它不是InteractiveObject,就用它的父级抛出鼠标事件。
嗯,感觉又靠谱了。测试了下,怎么还是不行啊?再断点调试,发现列表最顶端的元素,它的父级的父级的父级,mouseChildren和mouseEnabled都是false,也就是说,这个元素,它本应该是被穿过的,它下一层的元素才有可能是可以抛出鼠标事件的对象。这下纠结了啊,下一个元素有可能是它的父级,有可能是它同级的,也有可能是同级的子项。并且不是它自身的mouseEnabled为true就算了。每一层的父级,若有一个mouseChildren为false,则子项的mouseEnabled全都相当于false。即使自身的mouseEnabled为false,它也有可能作为父级的点击区域而存在。。。这坑已经越来越深了。我纠结了一下午,都在理这其中的判定顺序。就在我即将突破的时候。同事来凑热闹了,了解了情况后说:果然纠结啊,我也想试试。然后他就回去用Flash pro画了一个mc出来,杯具就这样发生了。。。
他回去只写了几行测试代码。叫我过去看,结果居然就搞定了。原来用绘图函数,直接drawRect(),再drawEllipse(),第一个Rect就会被第二个Ellipse抠出个镂空区域来,画出来的镂空区域就是可以穿过点击的。没错,就是这么简单。。。下面上代码:
package
{
import flash.display.Sprite;
import flash.events.MouseEvent;
/**
* 镂空遮罩测试
* @author DOM
*/
public class HollowTest extends Sprite
{
public function HollowTest()
{
super();
var bg:Sprite = new Sprite;
bg.graphics.beginFill(0x009aff);
bg.graphics.drawRect(0,0,500,400);
bg.graphics.endFill();
bg.addEventListener(MouseEvent.CLICK,onBgClick);
addChild(bg);
var hollowMask:Sprite = new Sprite;
hollowMask.graphics.beginFill(0x009900);
hollowMask.graphics.drawRect(150,100,200,200);
hollowMask.graphics.drawEllipse(200,150,100,100);
hollowMask.graphics.endFill();
hollowMask.addEventListener(MouseEvent.CLICK,onMaskClick);
addChild(hollowMask);
}
private function onMaskClick(event:MouseEvent):void
{
trace("maskClick!");
}
private function onBgClick(event:MouseEvent):void
{
trace("bgClick!");
}
}
}
后来整理了下,封装了个镂空遮罩组件出来,可以直接设置它的宽高和镂空位置,它会自动重绘。还是上代码吧:
package module.newGuide.view
{
import flash.display.Sprite;
import flash.events.Event;
/**
* 镂空遮罩工具类
* @author DOM
*/
public class HollowMask extends Sprite
{
/**
* 构造函数
* @param width 镂空圆形的宽度
* @param height 镂空圆形的高度
*/
public function HollowMask(hollowWidth:Number=100,hollowHeight:Number=100)
{
super();
this._hollowWidth = hollowWidth;
this._hollowHeight = hollowHeight;
this.redrawRequest = true;
}
private var _maskColor:uint = 0x000000;
/**
* 遮罩的颜色
*/
public function get maskColor():uint
{
return _maskColor;
}
public function set maskColor(value:uint):void
{
if(_maskColor==value)
return;
_maskColor = value;
redrawRequest = true;
}
private var _maskAlpha:Number = 0.5;
/**
* 遮罩的透明度
*/
public function get maskAlpha():Number
{
return _maskAlpha;
}
public function set maskAlpha(value:Number):void
{
if(_maskAlpha==value)
return;
_maskAlpha = value;
redrawRequest = true;
}
/**
* 移动镂空区域到指定的坐标
* @param x 目标点x坐标
* @param y 目标点y坐标
* @param speed 移动速度,单位为像素,若小于等于0,则不执行缓动。
*/
public function moveHollowTo(x:Number,y:Number,speed:Number = 0):void
{
moveSpeed = speed;
if(speed<=0)
{
removeMoveEventListener();
_hollowX = x;
_hollowY = y;
drawBackground();
}
else
{
targetX = x;
targetY = y;
addMoveEventListener();
}
}
/**
* 缓动事件监听已添加标志
*/
private var moveEventAttached:Boolean = false;
/**
* 添加缓动事件监听
*/
private function addMoveEventListener():void
{
if(moveEventAttached)
return;
moveEventAttached = true;
this.addEventListener(Event.ENTER_FRAME,onMoveHollow);
}
/**
* 移除缓动事件监听
*/
private function removeMoveEventListener():void
{
if(!moveEventAttached)
return;
moveEventAttached = false;
removeEventListener(Event.ENTER_FRAME,onMoveHollow);
}
/**
* 移动速度,像素
*/
private var moveSpeed:Number = 0;
/**
* 目标点x坐标
*/
private var targetX:Number = 0;
/**
* 目标点y坐标
*/
private var targetY:Number = 0;
/**
* 缓动处理函数
*/
private function onMoveHollow(event:Event):void
{
var offsetX:Number = targetX - _hollowX;
var offsetY:Number = targetY - _hollowY;
var distance:Number = Math.sqrt(offsetX*offsetX+offsetY*offsetY);
if(distance==0||distance<=moveSpeed)
{
_hollowX = targetX;
_hollowY = targetY;
removeMoveEventListener();
}
else
{
_hollowX += offsetX*moveSpeed/distance;
_hollowY += offsetY*moveSpeed/distance;
}
drawBackground();
}
private var _hollowX:Number = 0;
/**
* 镂空区域的x坐标位置
*/
public function get hollowX():Number
{
return _hollowX;
}
public function set hollowX(value:Number):void
{
if(_hollowX == value)
return;
_hollowX = value;
redrawRequest = true;
}
private var _hollowY:Number = 0;
/**
* 镂空区域的y坐标位置
*/
public function get hollowY():Number
{
return _hollowY;
}
public function set hollowY(value:Number):void
{
if(_hollowY == value)
return;
_hollowY = value;
redrawRequest = true;
}
private var _hollowWidth:Number = 100;
/**
* 镂空圆形区域宽度
*/
public function get hollowWidth():Number
{
return _hollowWidth;
}
public function set hollowWidth(value:Number):void
{
if(_hollowWidth==value)
return;
_hollowWidth = value;
redrawRequest = true;
}
private var _hollowHeight:Number = 100;
/**
* 镂空圆形区域高度
*/
public function get hollowHeight():Number
{
return _hollowHeight;
}
public function set hollowHeight(value:Number):void
{
if(_hollowHeight==value)
return;
_hollowHeight = value;
redrawRequest = true;
}
private var _width:Number = 100;
override public function get width():Number
{
return _width;
}
override public function set width(value:Number):void
{
if(_width==value)
return;
_width = value;
redrawRequest = true;
}
private var _height:Number = 100;
override public function get height():Number
{
return _height;
}
override public function set height(value:Number):void
{
if(_height == value)
return;
_height = value;
redrawRequest = true;
}
private var _redrawRequest:Boolean = false;
/**
* 需要重绘背景的标志
*/
private function set redrawRequest(value:Boolean):void
{
if(_redrawRequest==value)
return;
_redrawRequest = value;
addEventListener(Event.ENTER_FRAME,onEnterFrame);
}
/**
* 延迟一帧来集中处理,减少不必要的重绘次数
*/
private function onEnterFrame(event:Event):void
{
removeEventListener(Event.ENTER_FRAME,onEnterFrame);
if(_redrawRequest)
{
_redrawRequest = false;
drawBackground();
}
}
/**
* 立即重绘
*/
public function redrawNow():void
{
if(_redrawRequest)
{
_redrawRequest = false;
drawBackground();
}
}
/**
* 绘制背景遮罩,子类可以覆盖此方法绘制指定的背景。
*/
protected function drawBackground():void
{
this.graphics.clear();
if(_width==0||_height==0)
{
return;
}
this.graphics.beginFill(_maskColor,_maskAlpha);
this.graphics.drawRect(0,0,_width,_height);
if(_hollowWidth!=0&&_hollowHeight!=0)
{
this.graphics.drawEllipse(_hollowX,_hollowY,_hollowWidth,_hollowHeight);
}
this.graphics.endFill();
this.graphics.lineStyle(2,0xFFCC00,1);
this.graphics.drawEllipse(_hollowX,_hollowY,_hollowWidth,_hollowHeight);
this.graphics.endFill();
}
}
}