最近在2D游戏开发的时候,发现自己需要在角色头顶添加一个Text,并且这个Text位置是保持在世界坐标中的。然而我发现Unity2D居然不能很自然的做这一件事。
原因出在Unity2D的对象分类上,其中创建像Sprite, SpriteShape等常用的精灵,属于世界坐标系的对象,而创建Text,Image等UI组件,又属于UI坐标系。
世界坐标系和UI坐标系是两套独立的坐标系,互不相干。这样的好处是,当我们在世界坐标系中使用镜头移动等处理方法时,UI部分的展示丝毫不受影响,所以无论角色怎么跑动,卷轴怎么移动,顶栏分数等UI信息丝毫不受影响。
但是这样也存在一个问题,万一我需要角色和分数等信息有位置上的关联呢?
打个比方,比如我希望在打败敌人后敌人头顶显示得分(有时候也需要是伤害值)。这时候两套坐标系就存在关联了。
解决这个问题的一个办法是对相应的坐标进行转换处理,这时候就需要用到一个API:WorldToScreenPoint 。下面就总结下使用方法。
首先,WorldToScreenPoint 是一个Camera类的成员方法,为了随时方便的获取到Camera对象,我编写了个帮助类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameObjectFinder {
public static Camera FindMainCamera() {
GameObject obj = GameObject.FindWithTag("MainCamera");
if (obj != null) {
return obj.GetComponent<Camera>();
}
return null;
}
}
通过Camera的Tag来找到Camera对象。
然后,我在Canvas下挂载一个脚本,叫 UIManagerController 。
using System.Net.Mime;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UIManagerController : MonoBehaviour
{
public Text score;
public GameObject showUpScorePrefab;
void Start() {
score.text = "" + ScoreManager.Singleton.Score;
}
public void ShowUpScore(Vector2 worldPosition, int scoreNumber) {
Vector2 uiPosition = GameObjectFinder.FindMainCamera().WorldToScreenPoint(worldPosition);
GameObject scoreObject = Instantiate(showUpScorePrefab, transform);
string text = scoreNumber.ToString();
ShowUpScoreController showUpController = scoreObject.GetComponent<ShowUpScoreController>();
if (showUpController != null) {
showUpController.ShowUp(text, worldPosition);
}
}
}
这其中的 ShowUpScoreController 是创建一个Text的 Prefab 所挂载的脚本。
ShowUpScoreController 代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ShowUpScoreController : MonoBehaviour {
private const float moveUpSpeed = 50;
private float speed = 0;
private Vector2 showUpWorldPosition;
public void ShowUp(string text, Vector2 worldPosition) {
Text textObject = gameObject.GetComponent<Text>();
textObject.text = text;
speed = moveUpSpeed;
showUpWorldPosition = worldPosition;
Invoke("DestroySelf", 0.4f);
}
void Update() {
float offset = speed * Time.deltaTime;
showUpWorldPosition += new Vector2(0, offset);
// 不断的刷新位置
transform.position = GameObjectFinder.FindMainCamera().WorldToScreenPoint(showUpWorldPosition);
}
private void DestroySelf() {
Destroy(gameObject);
}
}
这个代码的逻辑是,当敌人被打败后,头顶会浮现得分的分数,然后这个分数会往上移动小段距离,然后该分数消失。