如题,要完成这个功用,首要我们需求先了解下的CocosCreator中的接触事情传递的机制。
接触事情冒泡
接触事情支持节点树的事情冒泡,以下图为例:
在图中的场景里,假定 A 节点拥有一个子节点 B,B 拥有一个子节点 C。开发者对 A、B、C 都监听了接触事情(以下的举例都默许节点监听了接触事情)。
当鼠标或手指在 C 节点区域内按下时,事情将首要在 C 节点触发,C 节点监听器接收到事情。接着 C 节点会将事情向其父节点传递这个事情,B 节点的监听器将会接收到事情。同理 B 节点会将事情传递给 A 父节点。这便是最根本的事情冒泡进程。需求着重的是,在接触事情冒泡的进程中不会有接触检测,这意味着即便触点不在 A B 节点区域内,A B 节点也会经过接触事情冒泡的机制接收到这个事情。
接触事情的冒泡进程与普通事情的冒泡进程并没有区别。所以,调用event.stopPropagation()
能够主动中止冒泡进程。
同级节点间的触点归属问题
假定上图中 B、C 为同级节点,C 节点部分掩盖在 B 节点之上。这时候假如 C 节点接收到接触事情后,就宣布了触点归属于 C 节点,这意味着同级节点的 B 就不会再接收到接触事情了,即便触点一起也在 B 节点内。同级节点间,触点归属于处于顶层的节点。
此时假如 C 节点还存在父节点,则还能够经过事情冒泡的机制传递接触事情给父节点。
这一点便是我们所需求注意的,因为牌组中的牌都处于同一层,因而不能直接监听每一张牌的接触事情,这样接触点归属一张牌之后,其他牌就无法收到事情传递,因而不能监听每张牌的接触事情,需求一个接触层,用来承受接触事情,经过判别接触点是否落在卡牌上。
原理
如上图,因为每张牌都要按照一个固定位移差摆放着,因而检查接触规模时,只要最终一张卡牌的接触规模是整张牌规模,其他牌的接触规模只要检查显现的部分。
不论接触点开始方位时在接触结尾的左边还是右边,只要判别卡牌的显现部分处于开始点和结尾之间便是被选中的状况
完成
1,创立一个卡牌视图类,用来控制卡牌被选中,挂起和复位作用,代码如下:
export default class CardView {
...
/** 原始方位 */
private _orginPos: cc.Vec3 = cc.v3();
/** 是否被选中 */
private _isSelect: boolean = false;
public get isSelect (): boolean {
return this._isSelect;
}
public set isSelect (isSelect: boolean) {
this._isSelect = isSelect;
this.selectStateImg.active = isSelect;
}
/** 是否挂起 */
private _isUp: boolean = false;
public isUp (): boolean {
return this._isUp;
}
/** 牌之间的间隔 */
private _interval: number = 0;
public set interval (interval: number) {
this._interval = interval;
}
public get interval (): number {
return this._interval;
}
/** 被选中后,挂起的牌复位,原位的牌挂起 */
public checkUp () {
if (this._isUp) {
this.unselectAction();
} else {
this.selectAction();
}
}
/** 被选中后挂起动画 */
public selectAction () {
if (this._isUp) {
return;
}
cc.tween(this.node)
.to(0.1, { position: cc.v3(this.node.x, this._orginPos.y + 20, 0) }, { easing: 'sineIn'})
.call(() => {
this._isUp = true;
})
.start();
}
/** 复位动画 */
public unselectAction () {
if (!this._isUp) {
return;
}
cc.tween(this.node)
.to(0.1, { position: cc.v3(this.node.x, this._orginPos.y, 0) }, { easing: 'sineIn'})
.call(() => {
this._isUp = false;
})
.start();
}
}
2, 创立接触层, 用来监听接触事情
xport default class CardPanelView extends EventComponent {
@inject("playerPanel1", cc.Node)
playerPanel1: cc.Node = null;
/** 根视图 */
private _gameView: UIView = null;
public set gameView (view: UIView) {
this._gameView = view;
}
/** 创立的牌组 */
private _cardViews: cc.Node[] = [];
/** 接触点开始方位 */
private _startPos: cc.Vec2 = null;
/** 接触到牌外之前的坐标 */
private _moveEndPos: cc.Vec2 = null;
/** 接触开始方位是否接触到牌 */
private _isTouchedInCard: boolean = false;
onLoad(): void {
super.onLoad();
}
/** 监听接触事情 */
addEvents(): void {
this.onN(this.node, cc.Node.EventType.TOUCH_START, this._touchStart);
this.onN(this.node, cc.Node.EventType.TOUCH_MOVE, this._touchMove);
this.onN(this.node, cc.Node.EventType.TOUCH_END, this._touchEnd);
}
init() {
this.initCards();
}
/** 初始化牌组 */
initCards () {
const cards = [0x1A, 0x1A, 0x1B, 0x1B, 0x1E, 0x1E, 0x1E, 0x1F, 0x1F, 0x01, 0x02];
createPrefab({
url: DDZ_PREFAB_URL[0],
view: this._gameView,
complete: (node) => {
for (let i = 0; i < cards.length; i++) {
const node1 = cc.instantiate(node);
this.node.addChild(node1);
node1.x = setCardPositionX(cards.length-1, i, node1.width, 60);
node1.y = this.playerPanel1.y;
const cardView1 = node1.addComponent(CardView);
cardView1.gameView = this._gameView;
cardView1.interval = 60;
cardView1.value = cards[i];
this._cardViews.push(node1);
}
}
});
}
private _touchStart (ev : cc.Event.EventTouch) {
this._startPos = ev.getLocation();
this._moveEndPos = this._startPos;
this._isTouchedInCard = this._isTouchedCard(this._startPos);
}
private _touchMove (ev : cc.Event.EventTouch) {
/** 开始接触点在牌组规模外,不进行接触检测 */
if (!this._isTouchedInCard) return;
/** 接触点移动到牌组规模外,就不继续进行新的碰撞检测 */
const currentPos = ev.getLocation();
const isCurTouchedCard = this._isTouchedCard(currentPos);
if (!isCurTouchedCard) {
return;
}
this._moveEndPos = currentPos;
/** 碰撞检测,检测到牌显现部分在开始和结尾规模之间,则为选中状况,否则为选中 */
this._cardViews.forEach((card, index, arr) => {
const cardRect = card.getBoundingBoxToWorld();
const cardView = card.getComponent(CardView);
cardRect.width = index === arr.length - 1 ? cardRect.width : cardView.interval;
if (this._isCardInSelectionRange(cardRect, this._startPos, this._moveEndPos)) {
cardView.isSelect = true;
} else {
cardView.isSelect = false;
}
});
}
private _touchEnd (ev : cc.Event.EventTouch) {
const currentPos = ev.getLocation();
// 选中牌意外的区域,一切牌复位
const _isEndTouchedInCard = this._isTouchedCard(currentPos);
if (!this._isTouchedInCard && !_isEndTouchedInCard) {
this._cardViews.forEach((card, index, arr) => {
const cardView = card.getComponent(CardView);
if (cardView.isSelect)
cardView.isSelect = false;
if (cardView.isUp) {
cardView.unselectAction();
}
});
return;
}
if (!this._isTouchedInCard) return;
// 选中牌区域,挂起的牌复位,原始方位的牌挂起
this._cardViews.forEach((card, index, arr) => {
const cardRect = card.getBoundingBoxToWorld();
const cardView = card.getComponent(CardView);
cardRect.width = index === arr.length - 1 ? cardRect.width : cardView.interval;
if (this._isCardInSelectionRange(cardRect, this._startPos, this._moveEndPos)) {
cardView.checkUp();
}
if (cardView.isSelect)
cardView.isSelect = false;
});
}
/** 判别牌显现区域是否在两个点规模内 */
private _isCardInSelectionRange(cardRect: cc.Rect, startPos: cc.Vec2, currentPos: cc.Vec2): boolean {
const minX = Math.min(startPos.x, currentPos.x);
const maxX = Math.max(startPos.x, currentPos.x);
const minY = Math.min(startPos.y, currentPos.y);
const maxY = Math.max(startPos.y, currentPos.y);
if (cardRect.x + cardRect.width < minX || cardRect.x > maxX || cardRect.y + cardRect.height < minY || cardRect.y > maxY) {
return false;
}
return true;
}
/** 判别是否在牌组内 */
private _isTouchedCard (targetPos: cc.Vec2) {
let isTouchedCard = false;
this._cardViews.forEach((card, index, arr) => {
const cardRect = card.getBoundingBoxToWorld();
const cardView = card.getComponent(CardView);
cardRect.width = index === arr.length - 1 ? cardRect.width : cardView.interval;
if (this._isCardInSelectionRange(cardRect, targetPos, targetPos)) {
isTouchedCard = true;
}
});
return isTouchedCard;
}
}
代码就不多解说了,看注释。 这样就根本能够完成多选卡牌的功用,作用如下: