CocosCreator中,让UI跟随场景中物件

需求

让UI始终跟随3D场景中的物件。(如角色的头顶的名称、血条等)


UI跟随物件移动.gif

实现

使用camera的covertToUINode方法,获得UI应被设置到的位置,再setPostion就行。

    public updatePos()
    {
        this._cam.convertToUINode(this._refNode.position, this._uiNode.parent, this._pos);
        this._uiNode.setPosition(this._pos);
    }

下面的代码提供了功能:根据物件的位置设置对应UI的位置,让UI始终跟随对应物件。可添加和删除对应关系。
在编辑模式下可先设置好若干组对应关系。


image.png

WorldPosToUiCtr.ts


import { _decorator, Component, Node, Camera, Vec2, Vec3 } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('WorldPosToUiElement')
class WorldPosToUiElement
{
    @property({ type: Node, visible: true, displayName: '3d世界中的参考点' })
    private _refNode: Node;
    public get world():Node
    {
        return this._refNode;
    }
    @property({ type: Node, visible: true, displayName: '要设置位置的UI节点' })
    private _uiNode: Node;
    public get ui():Node
    {
        return this._uiNode;
    }
    private _cam:Camera;
    private _pos:Vec3;
    public set cam(v:Camera)
    {
        if (!this._cam)
        {
            this._cam = v;
        }
    }

    public constructor (worldRef:Node, ui:Node, c:Camera)
    {
        this._refNode = worldRef;
        this._uiNode = ui;
        this.cam = c;

        this._pos = new Vec3(0,0,0);
    }

    public destroy()
    {
        this._refNode = null;
        this._uiNode = null;
        this.cam = null;
    }

    public updatePos()
    {
        if (this._refNode.active)
        {
            this._cam.convertToUINode(this._refNode.worldPosition, this._uiNode.parent, this._pos);
            this._uiNode.setPosition(this._pos);
        }
        // 根据世界物件的active设置对于UI的active
        if (this._uiNode.active != this._refNode.activeInHierarchy)
        {
            this._uiNode.active = this._refNode.activeInHierarchy;
        }
    }

}

@ccclass('WorldPosToUiCtr')
export class WorldPosToUiCtr extends Component 
{
    @property({ type: Camera, visible: true, displayName: '相机' })
    private _cam: Camera;
    @property({ type: [WorldPosToUiElement], visible: true, displayName: '各元素' })
    private _elements: Array = new Array();

    start()
    {
        this.init();
    }

    onDestroy()
    { 
        this.myDestroy();
    }

    update(deltaTime: number)
    {
        if(this._elements)
        {
            for(const e of this._elements)
            {
                e.updatePos();
            }
        }
    }
    
    private init(): void
    {
        if(this._elements)
        {
            for(const e of this._elements)
            {
                e.cam = this._cam;
            }
        }
    }
    
    private myDestroy(): void
    { 
        if (this._elements)
        {
            for(const e of this._elements)
            {
                e.destroy();
            }

            this._elements = null;
        }
    }

    public addElement(world:Node, ui:Node):void
    {
        if (!this._elements)
        {
            this._elements = new Array();
        }
        if (!this.containElement(world, ui))
        {
            this._elements.push(new WorldPosToUiElement(world, ui, this._cam));
            // console.info('添加元素,添加后数量:'+this._elements.length);
        }
    }
    public removeElement(world:Node, ui:Node):void
    {
        if (this._elements)
        {
            let e:WorldPosToUiElement = this.getElement(world, ui);
            if (e)
            {
                this._elements.splice(this._elements.indexOf(e), 1);
                // console.info('移除元素,移除后数量:'+this._elements.length);
            }
        }
    }

    private containElement(world:Node, ui:Node):boolean
    {
        let ret:boolean = false;
        if (this._elements)
        {
            for(const e of this._elements)
            {
                if (e.world == world && e.ui == ui)
                {
                    ret = true;
                    break;
                }
            }
        }
        return ret;
    }

    private getElement(world:Node, ui:Node):WorldPosToUiElement
    {
        let ret:WorldPosToUiElement = null;
        if (this._elements)
        {
            for(const e of this._elements)
            {
                if (e.world == world && e.ui == ui)
                {
                    ret = e;
                    break;
                }
            }
        }
        return ret;
    }
}

用于测试的一些代码

物体随机移动
RandomMoveCtr.ts

import { _decorator, Component, Node, CCFloat, Vec3, randomRange } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('RandomMoveCtr')
export class RandomMoveCtr extends Component 
{
    @property({ type: CCFloat, visible: true, displayName: 'x范围' })
    private _xRange:number = 5;
    @property({ type: CCFloat, visible: true, displayName: 'y范围' })
    private _yRange:number = 8;

    private _center:Vec3;
    private _targetPos:Vec3;

    @property({ type: CCFloat, visible: true, displayName: '移动速度' })
    private _moveSpeed:number = 10;

    start()
    {
        this.init();
    }

    onDestroy()
    { 
        this.myDestroy();
    }

    update(deltaTime: number)
    {
        if(this._targetPos.equals(this.node.position, 0.1))
        {
           this.createTarget();
        }
        else
        {
            const dir = (new Vec3(this._targetPos.x, this._targetPos.y, this._targetPos.z)).subtract(this.node.position).normalize();
            const offset = dir.multiplyScalar(this._moveSpeed * deltaTime);
            this.node.setPosition(this.node.position.add(offset)); 
        }
    }
    private init(): void
    {
        this._center = new Vec3(this.node.position.x, this.node.position.y, this.node.position.z);
        this.createTarget();
    }
    
    private myDestroy(): void
    { 

    }

    private createTarget():void
    {
        this._targetPos = new Vec3(this._center.x + randomRange(this._xRange*-1, this._xRange), this._center.y + randomRange(this._yRange*-1, this._yRange), this._center.z);
    }
}

运行时通过按键添加和移除 物件-UI对应关系
ElementTest.ts

import { _decorator, Component, Node, EventKeyboard, input, Input, KeyCode, instantiate } from 'cc';
import { WorldPosToUiCtr } from '../WorldPosToUiCtr';
const { ccclass, property } = _decorator;

@ccclass('ElementTest')
export class ElementTest extends Component 
{
    @property({ type: Node, visible: true, displayName: '3d世界中的参考点' })
    private _refNode: Node;
    @property({ type: Node, visible: true, displayName: '要设置位置的UI节点' })
    private _uiNode: Node;
    @property({ type: WorldPosToUiCtr, visible: true, displayName: 'Ctr' })
    private _ctr: WorldPosToUiCtr;
    start()
    {
        this.init();
    }

    onDestroy()
    { 
        this.myDestroy();
    }

    update(deltaTime: number)
    {
        
    }
    
    private init(): void
    {
        input.on(Input.EventType.KEY_DOWN, this.onKeyDown, this);
    }
    
    private myDestroy(): void
    { 
        input.off(Input.EventType.KEY_DOWN, this.onKeyDown, this);
    }

    private onKeyDown(event: EventKeyboard): void
    {
        if (event.keyCode == KeyCode.KEY_A)
        {
            // 添加
            
            let world = instantiate(this._refNode);
            world.setParent(this._refNode.parent);
            let ui = instantiate(this._uiNode);
            ui.setParent(this._uiNode.parent);
            this._ctr.addElement(world, ui);
           // this._ctr.addElement(this._refNode, this._uiNode);
        }
        else if (event.keyCode == KeyCode.KEY_D)
        {
            // 移除
            this._ctr.removeElement(this._refNode, this._uiNode);
        }

    }
}

额外知识1:ts获取当前的月和日

        const date:Date = new Date();
        const m = date.getMonth()+1;
        const d = date.getDate();

额外知识2:CocosCreator中,为按钮添加点击事件

// 添加关注
this._btn.node.on(Button.EventType.CLICK, this.onClickBtn, this);
// 移除关注
this._btn.node.off(Button.EventType.CLICK, this.onClickBtn, this);

// 点击按钮后,做什么
private onClickBtn():void
{
  this.close();
}

你可能感兴趣的:(CocosCreator中,让UI跟随场景中物件)