UI的组成元素主要有两个,一个外框,一个圆,创建一个父物体,用来接受点击和拖拽事件,设置框和圆为父物体的子物体。父物体的碰撞体可以做大一点,点击到区域内的,调整父物体的位置到点击的区域,圆的相对坐标设为0。平常不用虚拟摇杆的时候可以设置框和圆的透明度为0.5那样,点击和出发拖拽再tween到1。拖拽OnDraw(Vector2 v)-NGUI,IDragHandler.OnDrag(PointerEventData d)触发计算设置圆的位置,注意限制在框的半径中,可以用Distance判断。
计算摇杆的方向,然后映射到主角的移动方向上
//计算摇杆控制的方向 Vector3 direction = point.position - transform.position; direction = new Vector3(direction.x, 0f, direction.y); direction.Normalize(); //映射到目标模型上,调整目标方向 target.LookAt(target.position + direction); //计算相机的旋转偏差 Quaternion rot = Quaternion.Euler(0, camera.transform.eulerAngles.y, 0); target.LookAt(rot * target.forward);
在3D的世界中,观察世界的相机不一定就是正方向的去观察世界的,也许是45的,所以当将摇杆方向映射到主角时,也需要考虑相机的角度。
比如说:摇杆方向(圆心在正上),映射方向为(0,0,1),主角向前走,相机无旋转在主角z轴后面对着它,刚好圆心向上,主角在相机的视野中也是向上(前)。当相机y轴以45°观察时,就没有对到主角的z轴,主角还是向前走,但是在视野中确不是向上了(不和摇杆同一方向),而是往左上走了,所以需要将方向同相机一样旋转45度纠正回来,达到视野的移动方向和摇杆的一致。
public IEnumerator CaptureScreenShow()
{
yield return new WaitForEndOfFrame();
string fileName = DateTime.Now.Ticks.ToString();
string tagerFolder = DateTime.Now.ToString("yyyy-MM-dd");
Texture2D t = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, true);
t.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0, false);
t.Apply();
byte[] byt = t.EncodeToPNG();
string SaveImagePath = Application.persistentDataPath.Substring(0, Application.persistentDataPath.IndexOf("Android")) + "/Pictures/Screenshots";
string pathName = Path.Combine( SaveImagePath , tagerFolder );
if (!Directory.Exists(pathName))
{
Directory.CreateDirectory(pathName);
}
string path = pathName + "/" + fileName + ".png";
File.WriteAllBytes(path, byt);
yield return null;
//判断是否截图成功
if (File.Exists(path))
{
#if UNITY_ANDROID //刷新相册
ScanFile(new string[] { pathName });
#endif
}
else {
GlobalHallUIMgr.Instance.ShowSystemTipsUI(StringTable.GetString("givescore_cap_pic_err"), 1f, false);
}
}
void ScanFile(string[] path)
{
using (AndroidJavaClass PlayerActivity = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
{
AndroidJavaObject playerActivity = PlayerActivity.GetStatic<AndroidJavaObject>("currentActivity");
using (AndroidJavaObject Conn = new AndroidJavaObject("android.media.MediaScannerConnection", playerActivity, null))
{
Conn.CallStatic("scanFile", playerActivity, path, null, null);
}
}
}截图成功后,如果只是保存到了某个目录上,手机是不会马上立刻的发现那张图片的,比如说,你要微信发送某张图片,如果手机都不知道有那张图片,自然就发送不到了,只能去指定目录分享了。
所以截图成功保存后,调用一下Android扫描文件API。
原理就是将使用同一套骨骼的模型部分,单独独立起来,制作成可以加载利用的资源。然后加载这些单独的部分,组合成模型。如果要替换某个部分,就将合成的部分资源换成其他就好了。
每个模型部分的骨骼数据要和原来的骨骼数据一致,这样就可以通过一个只有骨骼数据的GameObject,然后加上SkinnedMeshRenderer,用各个部分蒙皮成为一个千变万化的人物了。
因为使用的是骨骼动画,各部分的顶点和骨骼的绑定是预定好的,所有不管部分的变化,动画还是可以正常播放的。
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(SkinnedMeshRenderer))]
public class AvatarPipi : MonoBehaviour {
//事先准备好的部件,美术做好的,可以做成prefab,需要时再通过名字来加载
public List<GameObject> eyes = new List<GameObject>();
public List<GameObject> faces = new List<GameObject>();
public List<GameObject> hairs = new List<GameObject>();
public List<GameObject> pants = new List<GameObject>();
public List<GameObject> shoes = new List<GameObject>();
public List<GameObject> tops = new List<GameObject>();
private List<CombineInstance> mList = new List<CombineInstance>();//子网格信息
private List<Transform> bones = new List<Transform>();//骨骼
private List<Material> mats = new List<Material>();//材质
private List<Transform> allBones = new List<Transform>();
void Start () {
//获取所有的骨骼
allBones.AddRange(transform.GetComponentsInChildren<Transform>());
GeneraterAvatar();
}
void GeneraterAvatar()
{
ChangeMeshType(eyes);
ChangeMeshType(faces);
ChangeMeshType(hairs);
ChangeMeshType(pants);
ChangeMeshType(shoes);
ChangeMeshType(tops);
SkinnedMeshRenderer my = this.GetComponent<SkinnedMeshRenderer>();
//整合所有信息到一个SkinnedMeshRenderer来渲染
Mesh m = new Mesh();
m.CombineMeshes(mList.ToArray(),false,false);//合并子网格
my.sharedMesh = m;
my.materials = mats.ToArray();
my.bones = bones.ToArray();
}
void ChangeMeshType(List<GameObject> type)
{
if (type.Count < 1) return;
GameObject go = type[Random.Range(0, type.Count)];
SkinnedMeshRenderer skin = go.GetComponent<SkinnedMeshRenderer>();
if (skin == null) return;
//获取部件的网格、材质、骨骼,聚合起来到列表中
CombineInstance combine = new CombineInstance();
combine.mesh = skin.sharedMesh;
mList.Add(combine);
mats.AddRange(skin.materials);
GetBones(skin.bones);
}
void GetBones(Transform[] termB)
{
//遍历得到部位组件对应的骨骼
foreach (var bb in termB)
{
foreach (var b in allBones)
{
if (b.name.Equals(bb.name))
{
bones.Add(b);
}
}
}
}
void Update () {
if (Input.GetKeyDown(KeyCode.Space))
{
ResetAvatar();
GeneraterAvatar();
}
}
void ResetAvatar()
{
mList.Clear();
bones.Clear();
mats.Clear();
}
}代码就不分析了,注释都有的。记一下合并网格函数的用法:
Mesh.CombineMeshes(CombineInstance[] combine, bool mergeSubMeshes = true, bool useMatrices = true, bool hasLightmapData = false)
第一个参数就是要合并的网格们,网格的定义在combine.mesh;
第二个参数确定是否和合并网格到一个sub mesh中,不是的话各网格会独立开来,使用不同的材质。如果使用的是合并的材质,那么设置为true。
第三个参数确定是否使用CombineInstance.transform
private static StringBuilder ret = new StringBuilder(13);
public static string FormateGoldString(string gold)
{
ret.Remove(0, ret.Length);
int length = gold.Length;
string result = string.Empty;
if (length > 2)
{
ret.Append(gold.Substring(0, length - 2));
string str2 = gold.Substring(length - 2, 2);
if (str2 != "00")
{
ret.Append(".");
if (str2[1] == '0')
{
ret.Append(str2[0]);
}
else
{
ret.Append(str2);
}
}
}
else
{
if (length == 1)
{
if (gold == "0")
{
ret.Append(0);
}
else
{
ret.Append("0.0");
ret.Append(gold);
}
}
else
{
ret.Append("0.");
if (gold[1] == '0')
{
ret.Append(gold[0]);
}
else
{
ret.Append(gold);
}
}
}
return ret.ToString();
}很简单的就是分析各种可能出现的情况,将小数部分提取出来,加个点连接起来。
UnityWebRequest web = UnityWebRequest.Get(url); yield return web.Send(); string message = web.downloadHandler.text;//获取到返回的文字信息
以前记得每种类型的资源都是有那么一个静态Handle去get获取下载的东西的,突然间想获取文字就忘记了
原来是直接使用UnityWebRequest实例里面的downloadHandler.text获取string,这个里面还有data是获取byte[]类型的
Texture2D是在UnityEngine命名空间中的,用来设置NGUI的UITexture的mainTexture属性显示图片,网络下载图片格式等。
一、保存为Png文件
web = UnityWebRequest.GetTexture(codeUrl);
yield return web.Send();
Texture2D t = DownloadHandlerTexture.GetContent(web);
web.Dispose();
string BaseCodePath = System.IO.Path.Combine(Application.persistentDataPath, "qrcode");
if (t != null)
{
byte[] bts = t.EncodeToPNG();//主要在
string PathName = BaseCodePath;
if (!Directory.Exists(PathName))
{
Directory.CreateDirectory(PathName);
}
File.WriteAllBytes(PathName + "/" + userId.ToString() + ".png", bts);
}二、加载图片为Texture2D
string BaseCodePath = System.IO.Path.Combine(Application.persistentDataPath, "qrcode");
string filePath = BaseCodePath + "/" + userId.ToString() + ".png";
if (File.Exists(filePath))
{
byte[] bts = File.ReadAllBytes(filePath);
Texture2D t = new Texture2D(500, 500);//宽和高
t.LoadImage(bts);//主要在这里
Code.mainTexture = t;
}
public static string FormateGoldShow(UInt32 Gold)
{
double result = Math.Round((double)(1.0f *Gold / 100), 2);
return (result.ToString());
}不知道为什么(double的强转导致的),这种方法在电脑上是可以用的,但是到了手机上,精度就开始飘了,各种不准。
所以,我就用字符串解析的方法来格式化整数变小数用来显示了。(字符串解析方式(两位小数))
using System.Collections.Generic;
using UnityEngine;
using System.Xml;
public class ConfigManager : Manager {
private Dictionary<string, object> configs;
public override void Init()
{
//获取到Resources/Config下的所有配置文件 并且解析
configs = new Dictionary<string, object>();
TextAsset[] textAsset = Resources.LoadAll<TextAsset>("Config");
for(int i =0;i<textAsset.Length;i++)
{
AnalysisConfig(textAsset[i]);
Resources.UnloadAsset(textAsset[i]);
}
GameManager.Instance.DebugManager.Logic.Log("配置管理器初始化完成!");
}
//加载解析xml配置
private bool AnalysisConfig(TextAsset file)
{
XmlDocument xml = new XmlDocument();
xml.LoadXml(file.text);
XmlNode parent = xml.FirstChild;
string FileKey = file.name;
if (configs.ContainsKey(FileKey)){
GameManager.Instance.DebugManager.Error.Log("配置文件名不能重复!");
return false;
}
Dictionary<string, object> FileNode = new Dictionary<string, object>();
FileNode = ParseNode(parent) as Dictionary<string,object>;
configs.Add(FileKey, FileNode);
return true;
}
private object ParseNode(XmlNode node)
{
object newNode = new object();
XmlNodeList list = node.ChildNodes;
if (list.Count > 0 && list[0].Name != "#text")
{
//数组
Dictionary<string, object> dic = new Dictionary<string, object>();
foreach (XmlNode cNode in list)
{
object ccNode = new object();
ccNode = ParseNode(cNode);
if (dic.ContainsKey(cNode.Name)) {
GameManager.Instance.DebugManager.Error.Log(string.Format("同一个父节点下的子节点的标签名不能相同:{0}", cNode.ParentNode.Name + cNode.Name));
return null;
}
dic.Add(cNode.Name, ccNode);
}
newNode = dic;
}
else {
//单元素数据 退出点
newNode = node.InnerText;
}
return newNode;
}
/// <summary>
/// 格式:配置文件名.根节点下的子节点.根节点下的子节点的子节点
/// 注意不需要写根节点的标签名
public T GetConfigByKey<T>(string key)
{
string[] keys = key.Split('.');
if (!configs.ContainsKey(keys[0]))
{
GameManager.Instance.DebugManager.Error.Log(string.Format("缺少{0}的配置文件", keys[0]));
return default(T);
}
Dictionary<string, object> dic = configs[keys[0]] as Dictionary<string, object>;
object TargetNode = dic;
for (int i = 1; i < keys.Length; i++)
{
dic = TargetNode as Dictionary<string, object>;//往前推进
if (!dic.ContainsKey(keys[i])) {
GameManager.Instance.DebugManager.Error.Log(string.Format("缺少{0}节点", keys[i]));
return default(T);
}
TargetNode = dic[keys[i]];
}
return (T)TargetNode;
}
}启动的时候,将指定Resources/Config目录的配置文件一并解析到内存中,通过key的方式获取到配置信息
需要添加配置,只需要在Resources/Config目录下创建配置文件就行了,自己再根据格式获取到配置,用于一些不用经常修改的、轻量的配置
如果是一些需要经常修改,重量的,使用网络AssetBundle加载。
思路:来自于php laravel的配置
以前说过,可以控制Debug.unityLogger.logEnabled为false来全局控制输出,但是单单使用Debug.Log不好区分模块,也不好单独调试某一模块,一输出就全部输出了,很乱。
下一个就是自己封装Debug,用不同的方法表示不同模块,使用不同的开关控制模块输出,富文本输出不同的颜色区分,但是双击输出会定位到封装。
所以是封装成dll导入,Unity就不会定位到dll里面,而是上一层。但是,修改添加模块困难,所以这里封装成一个可扩展性的Debug管理器。
using UnityEngine;
namespace DebugLog
{
public class MyDebug
{
public string Name;
public string Color;
public bool IsCanDebug = true;
public MyDebug(string name, string color)
{
Name = name;
Color = color;
}
public void Log(string mes)
{
if (IsCanDebug == false) return;
string log = string.Format("<color={0}>{1}:</color>{2}", Color, Name, mes);
Debug.Log(log);
}
}
}using System.Collections.Generic;
namespace DebugLog
{
public class DebugManager
{
public MyDebug Default = new MyDebug("Default", "yellow");
public MyDebug UI = new MyDebug("UI","blue");
public MyDebug Net = new MyDebug("Net", "green");
public MyDebug Error = new MyDebug("Error", "red");
public MyDebug Logic = new MyDebug("Logic", "#FF6633");
private Dictionary<string, MyDebug> dic = new Dictionary<string, MyDebug>();
public DebugManager()
{
dic.Add(Default.Name, Default);
dic.Add(UI.Name, UI);
dic.Add(Net.Name, Net);
dic.Add(Error.Name, Error);
dic.Add(Logic.Name, Logic);
}
public void AddMyDebug(string name, string color)
{
if (!dic.ContainsKey(name))
{
MyDebug debug = new MyDebug(name, color);
dic.Add(name,debug);
}
}
public MyDebug GetMyDebug(string name)
{
MyDebug debug = Default;
if (dic.ContainsKey(name))
{
debug = dic[name];
}
return debug;
}
public void OpenAllDebug()
{
foreach (var i in dic)
{
i.Value.IsCanDebug = true;
}
}
public void CloseAllDebug()
{
foreach (var i in dic)
{
i.Value.IsCanDebug = false;
}
}
public void DebugOnly(string name)
{
if (!dic.ContainsKey(name)) return;
CloseAllDebug();
dic[name].IsCanDebug = true;
}
}
}尽可能的把有的模块写成成员变量,方便使用时有提示,而不是使用Add后再用一个字符串的名字去获取这个Debug
实在是需要扩展,就可以直接Add再Get,也不需要重新修改导入dll了
方法lerp是一个根据t来计算插值的,如果已知起始到目标都为固定,如果t每次都几乎一样,那么效果就是一下子快然后慢下来。
所以,就要控制t的大小是由小到大的,提供一个speed速度的概念,从0往上加到最高速度,因为刚开始的差值大,很小的t也会造成计算出来的插值很大,所以前面要控制t的成长变慢,后面再快。
using UnityEngine;
public class ZhuanPanMono : MonoBehaviour {
public static bool ShowResult = false;
public static uint Result = 0;
private float speed = 0f;
public static float z = 0;
private float heightSpeed = 3f;
private float scaleSpeed = 0.18f;
private Transform go;
// Use this for initialization
void Start () {
go = transform.GetChild(3);
}
// Update is called once per frame
void Update () {
if (!ShowResult) return;
uint TargetRotation = Result * 45 + 10 * 360;
z = Mathf.Lerp(z, TargetRotation, Time.deltaTime * speed);
go.rotation = Quaternion.Euler(go.rotation.x,go.rotation.y,z);
if (speed < heightSpeed) {
//通过缩放来控制前期速度的增长过快
speed += Time.deltaTime * scaleSpeed;
}
if (scaleSpeed < 1 && speed > 1f) {
//后期释放加速度的限制缩放
scaleSpeed += Time.deltaTime;
}
if (Mathf.Abs(z - TargetRotation) < 0.1f) {
//转动结束
ShowResult = false;
}
}
}