2017-03-25 16:57:14 bzzbzz7 阅读数 1232
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

一、利用重力加速度传感器,控制摄像头方向

重力加速度传感器精度为:0.1 范围(0—±1)

unity3d Inspector 窗口里面:position是global position, rotation是local rotation

gravity参考坐标系:
这里写图片描述

camara参考坐标系:
这里写图片描述

原理:手机倾斜,坐标系跟着倾斜,根据重力向量在世界坐标系中恒定不变,求得手机坐标系倾斜角度,把该角度赋予场景中的camara 使得相机跟随手机同步倾斜
求gravity关于原点的对称点,把该点角度赋予相机:
Quaternion qgravity = Quaternion.LookRotation(gyro.gravity*-1);
transform.localRotation = qgravity;

效果:不理想,只能同步倾斜,不能同步旋转。

2011-07-21 18:27:52 leecong1p 阅读数 388
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

      手机平台上开发斜45度地图系统的游戏,相信做惯了正面俯视的开发者刚接触总很不习惯。所谓斜45度游戏,也就是常说的2.5D游戏,用斜方向俯视的角度来增强立体感的一种技术。这种技术在PC平台上早就流行了,手机平台由于屏幕表现力的限制,大部分使用正面视角。但随着手机屏幕分辨率不断增大,斜45度视角的游戏出现得越来越多。

      斜45度地图系统分Staggered、Slide、Diamond等几种,除了起始位置的区别,与正视地图系统的主要区别在于使用菱形的图块。关于什么是45度地图系统以及其原理,我不想再多说,网上有很多的资料,下面主要讲一讲坐标系的转换。

 


图1

 

 

 

 

 


图2(该图来自云风的博客http://www.codingnow.com/
 

 

 

 

 

 

 

      如图1,虽然图块是菱形拼接,但图片还是矩形,绘制地图就是要矩形的图片映射到正确的屏幕坐标系上,使图片无缝地拼接起来。如图2,以Diamond地图系统为例,为了简化,掠过滚屏等问题,假设始终从屏幕左上角开始绘制0,0的数据,图中所标的数字是指图块数据下标(data[j][i]中的(j,i))。通过观察你会发现:图片的X位置为数据j,i之差乘以图片宽度的一半,Y位置为数据j,i之和乘以高度的一半,即公式1:

           px  = (CHIP_W >> 1) * (i - j);

           py = (CHIP_H >> 1) * (i + j);

           通过公式1,我们就能根据数据数组的下标值获得对应图块在屏幕坐标系中的位置。逆推,得到公式2:

        i = 0.5f * (y / (CHIP_H >> 1) + x / (CHIP_W >> 1));
        j =  0.5f * (y / (CHIP_H >> 1) - x / (CHIP_W >> 1));

        通过公式2,可以把图块位置映射到对应的数据坐标。

 

        下面开始绘制地图内容。传统的俯视角度游戏地图绘制,都是记录屏幕(或摄像机)所在的数据j,i位置,计算屏幕能容纳多少个图块,用一个嵌套循环完成地图的绘制。如下所示:

         for(int j=0;j<SCREEN_HEIGHT/CHIP_H;j++)

         {

                 for(int i=0;i<SCREEN_WIDTH/CHIP_W;i++)

                 {

                      ......//do draw

                 }

          }


 

图3

         但是如图3所示,斜45度地图是斜的,无法用数组的循环遍历来绘制。不过既然有了转换公式,我们可以把屏幕分割成CHIP_W/2*CHIP_H/2的若干个区域,通过“遍历”这些区域的坐标,可以用公式知道,要在这个坐标上画哪张图片。算法如下:

 

 //当paintY为CHIP_H / 2的奇数倍时,paintX需要偏移CHIP_W / 2 
 int offset = 0;
 for (int paintY = 0; paintY <= SCREEN_H + CHIP_H; paintY += CHIP_H / 2) 
{
     for (int paintX = 0; paintX <= SCREEN_W + CHIP_W; paintX += CHIP_W) 
    {
         int gx = getGx(paintX + offset, paintY) + startCol;
         int gy = getGy(paintX + offset, paintY) + startRow; 
         if (gy < 0 || gx < 0 || gy > 10 || gx > 10)
         { 
             continue; 
         } 
         drawTile(g, data[gy][gx], paintX + offset, paintY); 
     } 
     offset = offset == 0 ? CHIP_W / 2 : 0;
 }

 

 

//屏幕坐标转换成游戏坐标 
int getGx(int x, int y) 
{
     return (int) (0.5f * (y / (CHIP_H >> 1) + x / (CHIP_W >> 1))); 
}

 int getGy(int x, int y) 
{
     return (int) (0.5f * (y / (CHIP_H >> 1) - x / (CHIP_W >> 1))); 
}

 

  

      关于地图上的碰撞与拾取。当你需要判断精灵所处地图数据位置的时候用公式2,会发现判断误差很严重,仔细想一下不难解释:逆推过来的公式是根据图片之间的坐标系来定义的,而菱形切片之间透明的部分都是重叠的,所以当你判断重叠部分的碰撞时就无法预计判断是那一块数据了,所以用公式2进行地图数据的碰撞判断不可行。

      
 

图4

 

                             

 图5                                               图6

 

      介绍一下我的碰撞方法。如图4,要判断屏幕中任意一点X,Y于数据中的位置。首先按图块的图片尺寸将屏幕分割,计算X,Y位于图中绿色矩形框选的哪个图片中,初步得到j,i。知道了位于哪张图块图片中,如图5,再判断X,Y位于图5中四个角落哪一个区域。结合图6,如果都不位于这四个角落,那X,Y就属于j,i。位于左上角的红色区域,对应i-1,其它三个角落同理。这里的难点在于如何判断红色区域,建议用三角形与点碰撞的算法。这种碰撞拾取算法的优点是精确无误差,而且无论菱形图块比例是32:15 、2:1还是其他比例都可以检测。

       数据碰撞检测的主要代码:

    int[] checkInDataIJ(int x, int y)
    {
        final int I = 0;
        final int J = 1;
        int[] data = new int[] { 0, 0 };
        Log.e("", "click:" + x + "," + y);
        int xd = x / CHIP_W;
        int yd = y / CHIP_H;
        if (x < 0)
        {
            xd -= 1;
        }
        if (y < 0)
        {
            yd -= 1;
        }
        Log.e("", "xd:" + xd + " yd:" + yd);
        data[I] = yd + xd;
        data[J] = yd - xd;
        //计算触摸点位于矩形中,与菱形的位置 
        int cx = x % CHIP_W;
        if (cx < 0)
        {
            cx += CHIP_W;
        }
        int cy = y % CHIP_H;
        if (cy < 0)
        {
            cy += CHIP_H;
        } //是否位于左上角的三角形
        if (MyMath.isInsideTriangle(cx,
                cy,
                new int[] { 0, CHIP_W / 2, 0 },
                new int[] { 0, 0, CHIP_H / 2 }))
        {
            data[I] -= 1;
        }
        //是否位于右上角的三角形
        else if (MyMath.isInsideTriangle(cx, cy, new int[] { CHIP_W / 2,
                CHIP_W, CHIP_W }, new int[] { 0, 0, CHIP_H / 2 }))
        {
            data[J] -= 1;
        }
        //是否位于右下角的三角形 
        else if (MyMath.isInsideTriangle(cx, cy, new int[] { CHIP_W, CHIP_W,
                CHIP_W / 2 }, new int[] { CHIP_H / 2, CHIP_H, CHIP_H }))
        {
            data[I] += 1;
        }
        //是否位于左下角的三角形 
        else if (MyMath.isInsideTriangle(cx,
                cy,
                new int[] { 0, CHIP_W / 2, 0 },
                new int[] { CHIP_H / 2, CHIP_H, CHIP_H }))
        {
            data[J] += 1;
        }
        Log.e("debug", "get(:" + data[J] + "," + data[I] + ")");
        return data;
    }

 

    三角形检测算法:

  

    public static boolean isInsideTriangle(int cx, int cy, int[] x, int[] y)
    {
        float vx2 = cx - x[0];
        float vy2 = cy - y[0];
        float vx1 = x[1] - x[0];
        float vy1 = y[1] - y[0];
        float vx0 = x[2] - x[0];
        float vy0 = y[2] - y[0];
        float dot00 = vx0 * vx0 + vy0 * vy0;
        float dot01 = vx0 * vx1 + vy0 * vy1;
        float dot02 = vx0 * vx2 + vy0 * vy2;
        float dot11 = vx1 * vx1 + vy1 * vy1;
        float dot12 = vx1 * vx2 + vy1 * vy2;
        float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01);
        float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
        float v = (dot00 * dot12 - dot01 * dot02) * invDenom;
        return ((u > 0) && (v > 0) && (u + v < 1));
    }

  

2018-02-08 09:15:31 pz789as 阅读数 4307
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

这次在项目中,做动画时,想要多UI的图片做倾斜动画,比如进场有一定的斜度,然后又变回原来的样子。于是在网上搜索有关Image变形或倾斜的做法,后面找到根据文档发现可以通过继承Image来做一些修改,关键是在Image渲染之后,我们拿到顶点坐标,对坐标做一定的偏移,就可以达到目的了。

先来看看效果:


看着效果还不错,看代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

[DisallowMultipleComponent]
[RequireComponent(typeof(RectTransform))]
[RequireComponent(typeof(CanvasRenderer))]
[AddComponentMenu("UI/UISkewImage (UI)", 99)]
[ExecuteInEditMode]
public class UISkewImage : Image {
	[SerializeField]
	private Vector3 offsetLeftButtom = Vector3.zero;
	public Vector3 OffsetLeftButtom{
		get{return offsetLeftButtom;}
		set{
			offsetLeftButtom = value;
			SetAllDirty();
		}
	}
	[SerializeField]
	private Vector3 offsetRightButtom = Vector3.zero;
	public Vector3 OffsetRightButtom{
		get{return offsetRightButtom;}
		set{
			offsetRightButtom = value;
			SetAllDirty();
		}
	}
	[SerializeField]
	private Vector3 offsetLeftTop = Vector3.zero;
	public Vector3 OffsetLeftTop{
		get{return offsetLeftTop;}
		set{
			offsetLeftTop = value;
			SetAllDirty();
		}
	}
	[SerializeField]
	private Vector3 offsetRightTop = Vector3.zero;
	public Vector3 OffsetRightTop{
		get{return offsetRightTop;}
		set{
			offsetRightTop = value;
			SetAllDirty();
		}
	}
	Vector3 GetOffsetVector(int i){
		if (i == 0){
			return offsetLeftButtom;
		}else if (i == 1){
			return offsetLeftTop;
		}else if (i == 2){
			return offsetRightTop;
		}else {
			return offsetRightButtom;
		}
	}
	protected override void OnPopulateMesh(VertexHelper toFill){
		base.OnPopulateMesh(toFill);
		int count = toFill.currentVertCount;
		for(int i=0;i<count;i++){
			UIVertex vertex = new UIVertex();
			toFill.PopulateUIVertex(ref vertex, i);
			// Debug.Log(i.ToString() + ": " + oldPosition[i].ToString());
			vertex.position += GetOffsetVector(i);
			toFill.SetUIVertex(vertex, i);
		}
	}
}

上面四个Offset分别对应四个顶点,如果要想丰富它,我觉得还是用一个插件好了,哈哈~数学不好,不知道怎么算顶点和三角面。

在上面代码中,虽然参数加了,但是自己定义的属性,没法显示在Inspector上面,因为我们直接继承了Image类,Image有一个ImageEditor的编辑器类,里面并没有把我们的参数显示出来,所以我们还需要写一个类,继承ImageEditor,然后在这个类里面,把我们自己定义的类显示在面板上,看截图:


这样就可以在编辑器里面直接操作了,编辑器类在这里:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEditor;
using UnityEditor.UI;

[CustomEditor(typeof(UISkewImage), true)]
[CanEditMultipleObjects]
public class UISkewImageEditor: ImageEditor {
	public override void OnInspectorGUI(){
		base.OnInspectorGUI();

		UISkewImage uiSkewImage = (UISkewImage)target;
		EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
		if (GUILayout.Button("重置", GUILayout.Width(40))){
			uiSkewImage.OffsetLeftTop = Vector3.zero;
		}
		uiSkewImage.OffsetLeftTop = EditorGUILayout.Vector3Field("左上", uiSkewImage.OffsetLeftTop, GUILayout.ExpandWidth(true));
		EditorGUILayout.EndHorizontal();

		EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
		if (GUILayout.Button("重置", GUILayout.Width(40))){
			uiSkewImage.OffsetRightTop = Vector3.zero;
		}
		uiSkewImage.OffsetRightTop = EditorGUILayout.Vector3Field("右上", uiSkewImage.OffsetRightTop, GUILayout.ExpandWidth(true));
		EditorGUILayout.EndHorizontal();

		EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
		if (GUILayout.Button("重置", GUILayout.Width(40))){
			uiSkewImage.OffsetLeftButtom = Vector3.zero;
		}
		uiSkewImage.OffsetLeftButtom = EditorGUILayout.Vector3Field("左下", uiSkewImage.OffsetLeftButtom, GUILayout.ExpandWidth(true));
		EditorGUILayout.EndHorizontal();

		EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
		if (GUILayout.Button("重置", GUILayout.Width(40))){
			uiSkewImage.OffsetRightButtom = Vector3.zero;
		}
		uiSkewImage.OffsetRightButtom = EditorGUILayout.Vector3Field("右下", uiSkewImage.OffsetRightButtom, GUILayout.ExpandWidth(true));
		EditorGUILayout.EndHorizontal();

		if (GUI.changed){
			EditorUtility.SetDirty(uiSkewImage);
		}
	}
}

到这里,基本上是可以用了,但是为了能够在任何时候都可以直接创建它,就和你在某个UI物体下面右键创建Image一样,我又写了一个编辑器类,可以直接创建SkewImage,这样用起来就方便啦。代码如下:

using UnityEngine;
using UnityEngine.UI;
using UnityEditor;
using UnityEngine.EventSystems;

public class UICustomCreator {
	[MenuItem("GameObject/UI/UICustom/SkewImage", false, 0)]
	static void CreateUICustomSkewImageGuiObject(MenuCommand command){
		Canvas canvas = CreateCanvas();
		GameObject go = CreateUISkewImage(command, canvas);
		CreateEventSystem();
		Selection.activeGameObject = go;
	}
	// Create the UISkewImage Object
	static GameObject CreateUISkewImage(MenuCommand command, Canvas canvas){
		GameObject go = new GameObject("UISkewImage");
		RectTransform goRectTransform = go.AddComponent<RectTransform>();
		Undo.RegisterCreatedObjectUndo((Object)go, "Create " + go.name);
		// Check if object is being create with left or right click
		GameObject contextObject = command.context as GameObject;
		if (contextObject == null) {
			goRectTransform.sizeDelta = new Vector2(100f, 100f);
			GameObjectUtility.SetParentAndAlign(go, canvas.gameObject);
		}else {
			goRectTransform.sizeDelta = new Vector2(100f, 100f);
			GameObjectUtility.SetParentAndAlign(go, contextObject);
		}
		UISkewImage image = go.AddComponent<UISkewImage>();
		image.OffsetLeftTop = Vector3.zero;
		image.OffsetRightTop = Vector3.zero;
		image.OffsetLeftButtom = Vector3.zero;
		image.OffsetRightButtom = Vector3.zero;
		image.raycastTarget = true;
		return go;
	}
	// Check if there is a Canvas in the scene
	static Canvas CreateCanvas(){
		Canvas canvas = Object.FindObjectOfType<Canvas>();
		if (canvas == null)
		{
			// Create new Canvas since none exists in the scene.
			GameObject canvasObject = new GameObject("Canvas");
			canvas = canvasObject.AddComponent<Canvas>();
			canvas.renderMode = RenderMode.ScreenSpaceOverlay;
			canvas.gameObject.AddComponent<CanvasScaler>();
			// Add a Graphic Raycaster Component as well
			canvas.gameObject.AddComponent<GraphicRaycaster>();
			Undo.RegisterCreatedObjectUndo(canvasObject, "Create " + canvasObject.name);
		}
		return canvas;
	}
	// Check if an event system already exists in the scene
	static void CreateEventSystem(){
		if (!Object.FindObjectOfType<EventSystem>())
		{
			GameObject eventObject = new GameObject("EventSystem", typeof(EventSystem));
			eventObject.AddComponent<StandaloneInputModule>();
			#if UNITY_5_3_OR_NEWER
				// Nothing
			#else
				eventObject.AddComponent<TouchInputModule>();
			#endif
			Undo.RegisterCreatedObjectUndo(eventObject, "Create " + eventObject.name);
		}
	}
}

这样就可以咯,注意,Editor类一定要放在Assets/.../Editor/的子目录里面,中间省略是随意哪个目录,但是最终必须是Editor,否则是不会起作用的。

看效果:


看到了么,超级方便!

再来两个截图:



到这里就结束了吗?还没有,我后面做动画时,发现我改变自定义的值,却不能像其他已有参数一样,直接在Animation编辑器中显示这个参数,这就有点头疼了。因为这样没法在编辑器调整数值,还是一个个的手动在Animation面板中添加参数,然后还要在这里调整数值,实在是太麻烦了。于是去Unity官网提过的UI源码看他们源码是怎么做到的,这里有UGUI的源码,可以参考哦:Unity UGUI 源码

通过我对方尝试,终于成功了,只需要改改ImageEditor类就可以了,修改后的类如下:

[CustomEditor(typeof(UISkewImage), true)]
[CanEditMultipleObjects]
public class UISkewImageEditor: ImageEditor {
	SerializedProperty m_OffsetLeftTop;
	SerializedProperty m_OffsetRightTop;
	SerializedProperty m_OffsetLeftButtom;
	SerializedProperty m_OffsetRightButtom;
	GUIContent m_LTContent;
	GUIContent m_RTContent;
	GUIContent m_LBContent;
	GUIContent m_RBContent;
	protected override void OnEnable()
	{
		base.OnEnable();
		m_OffsetLeftTop = serializedObject.FindProperty("offsetLeftTop");
		m_OffsetRightTop = serializedObject.FindProperty("offsetRightTop");
		m_OffsetLeftButtom = serializedObject.FindProperty("offsetLeftButtom");
		m_OffsetRightButtom = serializedObject.FindProperty("offsetRightButtom");
		m_LTContent = new GUIContent("左上");
		m_RTContent = new GUIContent("右上");
		m_LBContent = new GUIContent("左下");
		m_RBContent = new GUIContent("右下");
	}
	public override void OnInspectorGUI(){
		base.OnInspectorGUI();
		serializedObject.Update();
		EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
		if (GUILayout.Button("重置", GUILayout.Width(40))){
			m_OffsetLeftTop.vector3Value = Vector3.zero;
		}
		EditorGUILayout.PropertyField(m_OffsetLeftTop, m_LTContent);
		EditorGUILayout.EndHorizontal();

		EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
		if (GUILayout.Button("重置", GUILayout.Width(40))){
			m_OffsetRightTop.vector3Value = Vector3.zero;
		}
		EditorGUILayout.PropertyField(m_OffsetRightTop, m_RTContent);
		EditorGUILayout.EndHorizontal();

		EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
		if (GUILayout.Button("重置", GUILayout.Width(40))){
			m_OffsetLeftButtom.vector3Value = Vector3.zero;
		}
		EditorGUILayout.PropertyField(m_OffsetLeftButtom, m_LBContent);
		EditorGUILayout.EndHorizontal();

		EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
		if (GUILayout.Button("重置", GUILayout.Width(40))){
			m_OffsetRightButtom.vector3Value = Vector3.zero;
		}
		EditorGUILayout.PropertyField(m_OffsetRightButtom, m_RBContent);
		EditorGUILayout.EndHorizontal();
		
		serializedObject.ApplyModifiedProperties();

		// UISkewImage uiSkewImage = (UISkewImage)target;
		// EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
		// if (GUILayout.Button("重置", GUILayout.Width(40))){
		// 	uiSkewImage.OffsetLeftTop = Vector3.zero;
		// }
		// uiSkewImage.OffsetLeftTop = EditorGUILayout.Vector3Field("左上", uiSkewImage.OffsetLeftTop, GUILayout.ExpandWidth(true));
		// EditorGUILayout.EndHorizontal();

		// EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
		// if (GUILayout.Button("重置", GUILayout.Width(40))){
		// 	uiSkewImage.OffsetRightTop = Vector3.zero;
		// }
		// uiSkewImage.OffsetRightTop = EditorGUILayout.Vector3Field("右上", uiSkewImage.OffsetRightTop, GUILayout.ExpandWidth(true));
		// EditorGUILayout.EndHorizontal();

		// EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
		// if (GUILayout.Button("重置", GUILayout.Width(40))){
		// 	uiSkewImage.OffsetLeftButtom = Vector3.zero;
		// }
		// uiSkewImage.OffsetLeftButtom = EditorGUILayout.Vector3Field("左下", uiSkewImage.OffsetLeftButtom, GUILayout.ExpandWidth(true));
		// EditorGUILayout.EndHorizontal();

		// EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
		// if (GUILayout.Button("重置", GUILayout.Width(40))){
		// 	uiSkewImage.OffsetRightButtom = Vector3.zero;
		// }
		// uiSkewImage.OffsetRightButtom = EditorGUILayout.Vector3Field("右下", uiSkewImage.OffsetRightButtom, GUILayout.ExpandWidth(true));
		// EditorGUILayout.EndHorizontal();

		// if (GUI.changed){
		// 	EditorUtility.SetDirty(uiSkewImage);
		// }
	}
}
主要是那个
serializedObject.Update();
serializedObject.ApplyModifiedProperties();
这样关键,会自动加入到动画系统里面去,看截图:


一开始是白色的,只要在Animation面板开启录制模式,我在这里修改任何一个参数,就会变红,就像Unity自身的参数一样!可以开心的调动画啦~哈哈

2018-11-26 17:49:41 yangxun983323204 阅读数 2727
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

2.5D可能并不是一个很精准的词,我这里所指的是DNF这种可以在3个轴向上运动的2D精灵类型的效果。

以DNF为例,精灵实际上没有Z轴坐标的,当按↑,人物向屏幕里(远离玩家)方向移动时(z轴),在渲染上,我们看到人物是在向上移动。而我们按c,人物跳起(y轴),我们仍然看到人物在向上移动。这就是DNF类游戏看起来有点3d效果的奥秘:它营造一种我们在斜向下45度(或者其它角度)观看地面的感觉,因此当人物坐标在Z轴移动时,在屏幕投影位置上我们看到人物向上或向下移动。

所以,这种效果可以通过3维坐标在渲染时的一个变换来完成。

设点A(x1,y1,z1),那么它渲染时的世界坐标会变换为:

x2 = x1,

y2 = y1*cos(a)+z1*cos(90-a)

z2 = 0

其中a代表视线与xz平面的夹角,不过一般Y轴不计算投影,因为要保持精灵的正常显示,不能让它因斜视而变矮。

因此,y2 = y1 + z1*cos(90-a)

最开始,我打算了unity中为一个物体建立两个gameobject,一个物体赋值3维坐标,因为它处于真实位置,因此可直接使用物理系统,另一个物体用上面的映射方法,计算出坐标并使用它来表示精灵的位置,SpriteRenderer附加在第二个物体上。我确实这样实现了,也运行正常。

不过后面我想到,可以用shader来更改显示位置,这样只用一个gameobject,用普通Sprite即可,比较符合直觉,也便于使用。下面先看下效果:

45度角:

yangxun983323204

这样好处在于,物体的transform和boxcollider仍然是在正常的3D空间中的,所以碰撞检测可以完全以我们熟悉的3D物理组件来完成,而不需要自己实现。

另外,对于要做小游戏的程序员来说,2D游戏对素材要求会小一些。。用此方式,表现力会比用单纯的2d精灵和2d物理好很多。

附上shader,用精灵的默认shader改的:

Shader "Sprites/YX2.5D"
{
	Properties
	{
		[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
		_Color ("Tint", Color) = (1,1,1,1)
		[MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
		[HideInInspector] _RendererColor ("RendererColor", Color) = (1,1,1,1)
		[HideInInspector] _Flip ("Flip", Vector) = (1,1,1,1)
		[PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {}
		[PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0
		[HideInInspector] ZFactor ("Z-Factor",float) = 0.707
		[HideInInspector] WOrigin ("Origin",Vector) = (0,0,0,0)
	}

	SubShader
	{
		Tags
		{ 
			"Queue"="Transparent" 
			"IgnoreProjector"="True" 
			"RenderType"="Transparent" 
			"PreviewType"="Plane"
			"CanUseSpriteAtlas"="True"
		}

		Cull Off
		Lighting Off
		ZWrite Off
		Blend One OneMinusSrcAlpha

		Pass
		{
		CGPROGRAM
			#pragma vertex SpriteVert
			#pragma fragment SpriteFrag
			#pragma target 2.0
			#pragma multi_compile_instancing
			#pragma multi_compile _ PIXELSNAP_ON
			#pragma multi_compile _ ETC1_EXTERNAL_ALPHA
			#include "UnityCG.cginc"
			
			#ifdef UNITY_INSTANCING_ENABLED

				UNITY_INSTANCING_CBUFFER_START(PerDrawSprite)
					// SpriteRenderer.Color while Non-Batched/Instanced.
					fixed4 unity_SpriteRendererColorArray[UNITY_INSTANCED_ARRAY_SIZE];
					// this could be smaller but that's how bit each entry is regardless of type
					float4 unity_SpriteFlipArray[UNITY_INSTANCED_ARRAY_SIZE];
				UNITY_INSTANCING_CBUFFER_END

				#define _RendererColor unity_SpriteRendererColorArray[unity_InstanceID]
				#define _Flip unity_SpriteFlipArray[unity_InstanceID]

			#endif // instancing

			CBUFFER_START(UnityPerDrawSprite)
			#ifndef UNITY_INSTANCING_ENABLED
				fixed4 _RendererColor;
				float4 _Flip;
			#endif
				float _EnableExternalAlpha;
			CBUFFER_END

			// Material Color.
			fixed4 _Color;
			float4 WOrigin;
			float ZFactor;
			float YFactor;

			struct appdata_t
			{
				float4 vertex   : POSITION;
				float4 color    : COLOR;
				float2 texcoord : TEXCOORD0;
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};

			struct v2f
			{
				float4 vertex   : SV_POSITION;
				fixed4 color    : COLOR;
				float2 texcoord : TEXCOORD0;
				UNITY_VERTEX_OUTPUT_STEREO
			};

			v2f SpriteVert(appdata_t IN)
			{
				v2f OUT;

				UNITY_SETUP_INSTANCE_ID (IN);
				UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);

			#ifdef UNITY_INSTANCING_ENABLED
				IN.vertex.xy *= _Flip.xy;
			#endif
				float4 wpos = mul(unity_ObjectToWorld, IN.vertex)-WOrigin;
				float4 wposMap = float4(wpos.x,wpos.y + wpos.z*ZFactor,0,wpos.w); 
				OUT.vertex = mul(UNITY_MATRIX_VP,wposMap);
				OUT.texcoord = IN.texcoord;
				OUT.color = IN.color * _Color * _RendererColor;

				#ifdef PIXELSNAP_ON
				OUT.vertex = UnityPixelSnap (OUT.vertex);
				#endif

				return OUT;
			}

			sampler2D _MainTex;
			sampler2D _AlphaTex;

			fixed4 SampleSpriteTexture (float2 uv)
			{
				fixed4 color = tex2D (_MainTex, uv);

			#if ETC1_EXTERNAL_ALPHA
				fixed4 alpha = tex2D (_AlphaTex, uv);
				color.a = lerp (color.a, alpha.r, _EnableExternalAlpha);
			#endif

				return color;
			}

			fixed4 SpriteFrag(v2f IN) : SV_Target
			{
				fixed4 c = SampleSpriteTexture (IN.texcoord) * IN.color;
				c.rgb *= c.a;
				return c;
			}
		ENDCG
		}
	}
}

使用方法为,创建一个材质,使用上面提供的shader,默认就是45度角的效果。然后SpriteRender使用该材质。

为了方便起见,还写了一个c#脚本,方便视角、原点等参数设置,但它不是必须的:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Sprite25D : MonoBehaviour {

    public Vector3 Origin;
    public float Angle = 45;
    private new SpriteRenderer renderer;
    

    private void OnEnable()
    {
        Apply();
    }

    public void Apply()
    {
        if (renderer == null)
        {
            renderer = GetComponent<SpriteRenderer>();
        }
        Material mat;
        if (Application.isPlaying)
            mat = renderer.material;
        else
            mat = renderer.sharedMaterial;
        mat.SetVector("WOrigin", Origin);
        mat.SetFloat("ZFactor", Mathf.Sin(Angle * Mathf.Deg2Rad));
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(Sprite25D))]
public class Sprite25DInspector : Editor
{

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        GUILayout.Label("以下操作会修改sharedMaterial");
        if (GUILayout.Button("设置当前位置为映射原点"))
        {
            var inst = (target as Sprite25D);
            var pos = inst.transform.position;
            inst.Origin = new Vector3(pos.x, pos.y, pos.z);
            inst.Apply();
        }
        if (GUILayout.Button("重置原点为zero"))
        {
            var inst = (target as Sprite25D);
            inst.Origin = Vector3.zero;
            inst.Apply();
        }
    }
}

就是这样了

2015-03-05 17:20:00 weixin_30800987 阅读数 10
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

昨天在做一个3D模型的表面编辑功能,模型碰撞盒是MeshCollider。将屏幕上画的编辑区域(封闭曲线)逐点与模型进行射线碰撞,碰撞发生的位置所在的三角形就是我要获取的待编辑三角形,首先需要获得碰撞位置的三角形索引值,我想到了一个方案:

1.用碰撞点(RaycastHit.point)和每个三角形的3个顶点做共面检测 。

2.如果检测共面,再用斜坐标系分解的方法判断碰撞点是否在该三角形内。

3.如果在三角形内,则记录下该三角形的索引值。

结果虽然勉强得到想要的结果,但是由于每次需要遍历的射线点都在30个以上,需要检测的模型三角形常常是4000+个,效率非常捉急。

于是翻了一下Unity Scripting API ,才发现有个RaycastHit.triangleIndex这个东西。

想想也是,引擎内部在做碰撞检测的时候早就用了更高效的办法将射线与每个三角片做了判断检测。如果自己再检测一遍,岂不是枉费用了这么好的引擎。

所以  吸取教训,好好看API文档吧!

 

 

附:

RaycastHit.triangleIndex

 
public int triangleIndex;

Description

The index of the triangle that was hit.

Triangle index is only valid if the collider that was hit is a MeshCollider.

 

转载于:https://www.cnblogs.com/kyokuhuang/p/4316361.html

没有更多推荐了,返回首页