Unity GGJ48小时心得
上边是我自己在笔记软件上的记录,下边是抄过来的,不过格式可能乱了。大概可以看下内容,如果想仔细看的话还是到上边看格式正常的版本呗0.0一堆issues
[*]FixedUpdate与游戏定时器 FixedUpdate除了用来处理物理逻辑之外并不适合处理其他模块的逻辑,尤其是当大家的潜意识里都笃定它的更新频率是固定的时候。因为这很危险,比如一个需要状态同步的游戏要求按照固定的频率向目标同步状态,如果贸然采用FixedUpdate方法,就会出现上图那样可能在短时间内多次调用的问题。所以最好只在物理逻辑的处理中使用FixedUpdate,而不要滥用。 可变增量时间:每次调用Update之间,经过的时间不一致,所以大部分人使用Time.deltaTime()来计算两次Update之间的时差,磨平可变增量时间的错误。 固定增量时间:因为物理模拟很多微分积分,欧拉法使用都涉及到固定增量时间,因此FixedUpdate应运而生。当然,此时两次间时差并不一定真的是两次调用间的差距,而是因为Unity项目设定时候确定的值。
[*]协程(协同程序)的概括(StartCoroutine 和yield return和StopCoroutine ) public Coroutine StartCoroutine(IEnumerator routine); 协同进程执行中任何地方都可以使用yield语句,yield返回值控制何时恢复程序向下执行,协同程序在对象自有帧执行过程中堪称优秀。协同程序在性能上没有更多的开销。StartCoroutine函数是立刻返回的,但是yield可以延迟结果。直到协同程序执行完毕。 协程函数: 能暂停执行,暂停后立即返回直到中断指令完成后继续执行的函数;类似于一个子线程独立处理问题,性能开销小。 特点: 同一时刻,一个脚本实例下,可以有多个暂停的协成,但只有一个运行着的; 函数体全部完成后,协程结束; 协程可以很好控制跨越一定帧数后执行的行为; 协程在性能上相比一般函数无更多开销 使用方法: 1、通过MonoBehaviour提供的StartCoroutine方法来实现启动协同程序。 StartCoroutine(IEnumerator routine); 比如:StartCoroutine(Example());缺点是无法单独停止此协程,只能等待执行完毕或者StopAllCoroutine()结束所有的。 2、StartCoroutine (methodName:string, value : object = null); 优点,可以传递方法名和一个参数,可以直接通过StopCoroutine(string methodName);停止此协程;缺点:性能开销大,只能传递一个参数。 停止协同程序: 1、StopCoroutine(string methodName); 2、StopAllCoroutine(); 3、设置gameobject的active为false时可以终止协同程序,但是再次设置为true后协程不会再启动。 协同程序执行顺序: 开始协同程序 → 执行协同程序 → 中断协同程序(中断指令)→ 返回上层继续执行→ 中断指令结束后继续执行协同程序剩下的内容 协同程序注意事项: 1、不能在Update或者FixUpdate方法中使用协同程序,否则会报错。 2、关于中断指令:中断指令/YieldInstruction,一个协程收到中断指令后暂停执行,返回上层执行同时等待这个指令达成后继续执行。
[*]定时器与延时函数 单触发定时器:Invoke(string method,int Secondtimes)过Secondtimes 秒后触发method 函数。 重复触发InvokeRepeating(string method,int Secondtimetowake,int Secondtimetonext)每Secondtimetonext触发下一次。使用延时函数,将想要使用延时的函数定义为IEnumerator类型,然后使用StartCoroutine(IEnumerator routine)调用该函数,注意调用的是声明为IEnumerator类型的函数而不是yield的函数。
[*]C#中的IEnumerator 和 yield https://blog.csdn.net/u012138730/article/details/81148086 迭代器模式:允许你访问一个数据项序列,而无需关心序列内部的组织结构。 IEnumerator叫“迭代器”;IEnumerable叫“可迭代的”。
[*]不带<>的IEnumerator 和 IEnumerable(非泛型): IEnumberator,枚举器(迭代器),字面意思,用来一个一个枚举。 其关键成员有:当前对象current,是否有下一个movenext IEnumerable,可枚举的, 字面意思,继承了我就变成了一个可枚举的对象。 其只有一个成员 GetEnumerator,返回枚举器类型。 二者都是接口类型
//枚举器接口 IEnumerator
public interface IEnumerator
{
object Current { get; }
// 如果是返回 false,就是结束迭代器块
bool MoveNext();
void Reset();
}
//可枚举的接口 IEnumerable, 返回枚举器接口
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
如果说某个类实现了IEnumerable可枚举的,那么我们说这个类就是可迭代对象。比如dict
// dict是实现了 IEnumerable 接口的可迭代对象。
// iterator 是枚举器
var iterator = dict.GetEnumerator();
while(iterator.MoveNext())
{
item = iterator.Current;
.....
}
// 当然也可以直接更简单的实现迭代循环
foreach(var item in dict)
[*]C#举例如何实现可迭代对象 实现一个IterationSample 类,让它成为是一个为可迭代对象 首先肯定要继承IEnumerable接口,然后再需要实现:1)一个获得枚举器的函数,GetEnumerator函数2) 一个枚举器类,IterationSampleIterator,继承IEnumberator
class IterationSample : IEnumerable
{
object[] values;
int startingPoint;
public IterationSample(object[] values, int startingPoint)
{
this.values = values;
this.startingPoint = startingPoint;
}
public IEnumerator GetEnumerator()
{
return new IterationSampleIterator(this);
}
// 迭代器实现
class IterationSampleIterator : IEnumerator
{
// 重要数据成员,来获取当前值,下一个值,是否结束。在构造函数中赋值。
IterationSample parent;
// 标记当前所迭代的位置。
int position;
internal IterationSampleIterator(IterationSample parent)
{
this.parent = parent;
position = -1;
}
public bool MoveNext()
{
if (position != parent.values.Length)
{
position++;
}
return position < parent.values.Length;
}
public object Current
{
get
{
if (position == -1 ||
position == parent.values.Length)
{
throw new InvalidOperationException();
}
int index = (position + parent.startingPoint);
index = index % parent.values.Length;
return parent.values;
}
}
public void Reset()
{
position = -1;
}
}
}
这样就能像下面这样进行迭代:
// sample 是 IterationSample 对象
var iterator = sample.GetEnumerator();
while(iterator.MoveNext())
{
item = iterator.Current;
.....
}
或者用
foreach(var item in sample)
{
.....
}
上述实现迭代器的代码容易理解,但是实现代码繁琐。
[*]C#举例实现:使用yield关键字简化 有重复代码,那么可以让编译器给我们实现: 同样实现上述例子中的功能,C#中可以使用如下进行简化: 1)GetEnumerator函数重新实现,包含yield return 语句。 2)IterationSampleIterator 枚举器类,没有实现, 删除不需要了。
class IterationSample : IEnumerable
{
object[] values;
int startingPoint;
public IterationSample(object[] values, int startingPoint)
{
this.values = values;
this.startingPoint = startingPoint;
}
public IEnumerator GetEnumerator()
{
for (int index = 0; index <values.Length; index++)
{
yield return values[(index + startingPoint) % values.Length];
}
}
}
yield关键字是一个语法糖,背后其实生成了一个新的类,肯定是一个枚举器。而枚举器的具体实现MoveNext Current。 就看GetEnumerator中的函数体了。 一个例子了解编译器自动生成的迭代器的 工作原理:
class IteratorWorkflow
{
static readonly string Padding = new string(&#39; &#39;, 30);
// 也可以返回 IEnumerator<int>,相应的Main里面就不需要先获取 iterable 了。
static IEnumerable<int> CreateEnumerable()
{
Console.WriteLine(&#34;{0}Start of CreateEnumerable()&#34;, Padding);
for (int i = 0; i < 3; i++)
{
Console.WriteLine(&#34;{0}About to yield {1}&#34;, Padding, i);
// !!!遇到yield return 代码就停止执行,再下一次调用MoveNext的时候 继续执行。
yield return i;
Console.WriteLine(&#34;{0}After yield&#34;, Padding);
}
Console.WriteLine(&#34;{0}Yielding final value&#34;, Padding);
yield return -1;
Console.WriteLine(&#34;{0}End of GetEnumerator()&#34;, Padding);
}
static void Main()
{
// 此处不会真正调用CreateEnumerable()函数
IEnumerable<int> iterable = CreateEnumerable();
IEnumerator<int> iterator = iterable.GetEnumerator();
Console.WriteLine(&#34;Starting to iterate&#34;);
// 也可以用foreach来替代。这里展示了foreach的类似过程。
while (true)
{
Console.WriteLine(&#34;Calling MoveNext()...&#34;);
//此处开始真正调用CreateEnumerable()函数
bool result = iterator.MoveNext();
Console.WriteLine(&#34;... MoveNext result={0}&#34;, result);
if (!result)
{
break;
}
Console.WriteLine(&#34;Fetching Current...&#34;);
Console.WriteLine(&#34;... Current result={0}&#34;, iterator.Current);
}
}
}
关于yield的一些注意事项:
[*]可以在方法,属性 和 索引器中使用 yield 来实现迭代器。 使用yield,函数返回类型必须是IEnumberable<T>、IEnumberable、IEnumberator<T>和IEnumberator中的一个。 使用yield,函数参数不能是 ref或 out
[*]try和 catch语句里面不能出现yield return
[*]不能在匿名方法中用迭代器代码块,也就是不能使用yield。
[*]yield break 相当于普通方法中的return
[*]渲染层级以及背景拼接 渲染层级:决定谁在上面谁在下面 通过Sorting Layer和Order in Layer设置渲染层级:
通过点击+添加,并为新得层级添上名字。这里可以拖动某一层级调整顺序,约在下面的层级越后渲染,也就是越显示在前面,Default为默认最小层级。这里添加了两个层级,分别为BackGround和Map用来给背景和地图瓦片。
背景拼接: 背景不够容纳地图,这个时候需要进行一下背景的拼接,只需要复制background,然后拖拽拼接。 如何保证无缝完美的拼接? 需要通过原本图像的像素来计算一下坐标移动的距离。
横向的像素为240,然后我们原本设置的Pixels Per Units为16,那么240/16=15,表铭背景一张图片的横向宽度为15个unity的单位。那么横向背景拼接,复制的backgroud背景只需要改变position X的15个单位。
[*]触发检测和碰撞检测 碰撞:
void OnCollisionEnter(Collision col)
{
Debug.Log(&#34;开始碰撞&#34; + col.collider.gameObject.name);
}
void OnCollisionStay(Collision col)
{
Debug.Log(&#34;持续碰撞中&#34; + col.collider.gameObject.name);
}
void OnCollisionExit(Collision col)
{
Debug.Log(&#34;碰撞结束&#34; + col.collider.gameObject.name);
}
发生碰撞的条件: 主动方必须有Rigidbody,发生碰撞的两个游戏对象必须有Collider,被动方对于RigidBody可有可无,参数是表示被动方。
触发:
void OnTriggerEnter(Collider other)
{
Debug.Log(&#34;触发器开始出发:&#34; + other.gameObject.name);
}
void OnTriggerStay(Collider other)
{
Debug.Log(&#34;触发器检测中:&#34; + other.gameObject.name);
}
void OnTriggerExit(Collider other)
{
Debug.Log(&#34;触发器结束:&#34; + other.gameObject.name);
}
发生触发的条件: 发生碰撞的物体两者其中之一有Rigidbody即可,发生碰撞的两个游戏对象必须有Collider,其中一方勾选IsTrigger即可,参数是表示被动方
[*]Instantiate 使用预制体生成物体
Instantiate函数实例化是将original对象的所有子物体和子组件完全复制,成为一个新的对象。这个新的对象拥有与源对象完全一样的东西,包括坐标值等。instantiateWorldSpace这个值为TRUE,表示实例化对象相对于世界坐标系的位置不变,相对于父对象的坐标值变了;为false,表示实例化对象相对于父对象的坐标值不变,但在世界坐标系中的位置变了。即看当前物体到底要生成到何处,对应于世界坐标不变还是对应父物体不变。
[*]获取当前物体的所有子物体 1、使用GetComponentsInChildren()Transform[] father = GetComponentsInChildren<Transform>();不过此法会获取所有子物体及孙物体如下方法可以仅获得子物体C# Transform father; for (int i = 0; i < father.childCount; i++) { var child = father.GetChild(i).gameObject; Debug.Log((http://child.name)); } 2、GameObject.FindGameObjectsWithTag()来找到物体此法只用于已有tag的物体GameObject[] name3 = GameObject.FindGameObjectsWithTag(&#34;erzi&#34;);若只找一个则是GameObject[] name2 = GameObject.FindGameObjectWithTag(&#34;erzi&#34;); 3、查找当前场景下所有带脚本的游戏物体MonoBehaviour[] scripts = ``Object.FindObjectsOfType<MonoBehaviour>();然后scripts.gameObject就可以获得游戏物体咯。
[*]position和localposition position是根据世界原点为中心 localPosition是根据父节点为中心,如果没有父节点,localpositon和position是没有区别的 选中一个物体左上角Global和Local切换看物体世界坐标轴和本地坐标轴下的坐标
[*]物体间距离计算 static function ``*Distance*`` (a: Vector3, b: Vector3) : float 等同于(a-b).magnitude
[*]物体移动的方法 第一个方法: 可以通过this.transform.Translate( (directionOfTravel.x * speed * Time.deltaTime), (directionOfTravel.y * speed * Time.deltaTime),(directionOfTravel.z * speed * Time.deltaTime), Space.World); 也可以如直接计算此时的position直接赋值 第二个方法: 挂载Rigidbody后,在FixedUpdate中获得要去的地方的位置,计算此时速度方向,然后赋值给rb.velocity ,2D中速度为二维向量。
[*]C#的基类、派生类、重写与重载
class <派生类> : <基类>
{
...
}
C# 不支持多重继承。但是,可以使用接口来实现多重继承。
class Shape
{
...
}
public interface PaintCost
{
int getCost(int area);
}
class Rectangle : Shape, PaintCost
{
...
}
继承时构造方法的调用顺序为:子类静态、父类静态、父类构造、子类构造 子类重写父类方法:
[*]使用Virtual关键字Override从写
public virtual void Clear()
{
UpdateView();
}
// 不能修改public 为其它权限
public override void Clear()
{
base.Clear();
}
[*]使用new覆盖
publicvoid Clear()
{
UpdateView();
}
// 可以覆盖,但是父类调用时候不会调用到子类实现的新方法
private new void Clear()
{
base.Clear();
}
继承是派生类(子类)去实现(重写<override>、重构<new>)基类(父类)的方法或属性。从而获取在派生类中要实现的功能。 重写<override>只是对方法里面的功能实现重新做的了编写,并没有对方法的参数进行添加、改变、删除 ;重载则是对方法参数的个数、位置、参数类型进行了改变。 重写的父类方法时,必须指明被重写的方法是虚方法(virtual关键字)。在子类重写父类方法时必须有重写关键字(override)这样才能重写父类的方法
[*]音乐控件的操作 AudioSource获取音乐源,AudioClips数组存放可能用到的所有音效。记得要把物体上的音乐源拖拽到脚本上才能使用。audio.play()是播放音乐,audio.clip赋值当前要播放的音乐。audio.loop是循环播放的标志位,audio.stop()是停止,都可以在程序中使用来播放音效。 audioSource.isPlaying表示是否在播放音乐;audioSource.pitch表示音乐的播放速度 https://blog.csdn.net/qq_41967240/article/details/102619099
[*]四元数与旋转相关 四元数本质上是一种高阶复数,是一个四维空间,它的虚部包含了三个虚数单位,i、j、k,即一个四元数可以表示为x = a + bi + cj + dk。 三种旋转方式: 矩阵旋转 优点: 旋转轴可以是任意向量; 缺点: 旋转其实只需要知道一个向量+一个角度,一共4个值的信息,但矩阵法却使用了16个元素; 而且在做乘法操作时也会增加计算量,造成了空间和时间上的一些浪费; 欧拉旋转 优点: 很容易理解,形象直观; 表示更方便,只需要3个值(分别对应x、y、z轴的旋转角度);但按我的理解,它还是转换到了3个3*3的矩阵做变换,效率不如四元数; 缺点: 之前提到过这种方法是要按照一个固定的坐标轴的顺序旋转的,因此不同的顺序会造成不同的结果; 会造成万向节锁(Gimbal Lock)的现象。这种现象的发生就是由于上述固定坐标轴旋转顺序造成的。理论上,欧拉旋转可以靠这种顺序让一个物体指到任何一个想要的方向,但如果在旋转中不幸让某些坐标轴重合了就会发生万向节锁,这时就会丢失一个方向上的旋转能力,也就是说在这种状态下我们无论怎么旋转(当然还是要原先的顺序)都不可能得到某些想要的旋转效果,除非我们打破原先的旋转顺序或者同时旋转3个坐标轴。这里有个视频可以直观的理解下; 由于万向节锁的存在,欧拉旋转无法实现球面平滑插值; 四元数旋转 优点: 可以避免万向节锁现象; 只需要一个4维的四元数就可以执行绕任意过原点的向量的旋转,方便快捷,在某些实现下比旋转矩阵效率更高; 可以提供平滑插值; 缺点: 比欧拉旋转稍微复杂了一点点,因为多了一个维度; 理解更困难,不直观; 使用一个四元数q=((x,y,z)sinθ2, cosθ2) 来执行一个旋转。具体来说,如果我们想要把空间的一个点P绕着单位向量轴u = (x, y, z)表示的旋转轴旋转θ角度,我们首先把点P扩展到四元数空间,即四元数p = (P, 0)。那么,旋转后新的点对应的四元数(当然这个计算而得的四元数的实部为0,虚部系数就是新的坐标)为:
$$
p^{&#39;}= q p q^{-1}
$$
其中,q=(cosθ2, (x,y,z)sinθ2) ,由于u是单位向量,因此 N(q)=1,即q1=q。右边表达式包含了四元数乘法。 表示:q = ((x, y, z),w) = (v, w),其中v是向量,w是实数,这样的式子来表示一个四元数
$$
四元数乘法:q1q2=(\vec{v1}×\vec{v2}+w1\vec{v2}+w2\vec{v1},w1w2\vec{v1}\vec{v2})
$$
$$
共轭四元数: q^* = (- \vec{v2},w)
$$
$$
四元数的模:$N(q) = √(x^2 + y^2 + z^2 +w^2)$,即四元数到原点的距离
$$
$$
四元数的逆:$q^{1}=\frac{q^*}{N(q)}$
$$
最简单的例子:把点P(1, 0, 1)绕旋转轴u = (0, 1, 0)旋转90°,求旋转后的顶点坐标。首先将P扩充到四元数,即p = (P, 0)。而q = (usin45°, cos45°)。求p′=qpq1的值。建议大家一定要在纸上计算一边,这样才能加深印象,连笔都懒得动的人还是不要往下看了。最后的结果p` = ((1, 0, -1), 0),即旋转后的顶点位置是(1, 0, -1)。 注意: 用于旋转的四元数,每个分量的范围都在(-1,1); 每一次旋转实际上需要两个四元数的参与,即q和q; 所有用于旋转的四元数都是单位四元数,即它们的模是1;
Unity中可以使用Quaternion.Euler和Quaternion.eulerAngles和其他类型转换: 轴角到四元数:单位长度的旋转轴(x, y, z)和一个角度θ,对应的四元数为:q=((x,y,z)sinθ/2, cosθ/2) 欧拉角到四元数:欧拉旋转(X, Y, Z),其对应四元数为:x = sin(Y/2)sin(Z/2)cos(X/2)+cos(Y/2)cos(Z/2)sin(X/2) y = sin(Y/2)cos(Z/2)cos(X/2)+cos(Y/2)sin(Z/2)sin(X/2) z = cos(Y/2)sin(Z/2)cos(X/2)-sin(Y/2)cos(Z/2)sin(X/2) w = cos(Y/2)cos(Z/2)cos(X/2)-sin(Y/2)sin(Z/2)sin(X/2) q = ((x, y, z), w) Unity中使用: Quaternion.AngleAxis(float angle, Vector3 axis),它可以返回一个绕轴线axis旋转angle角度的四元数变换 使用Quaternion.eulerAngles来得到欧拉角度,计算完成后更新Quaternion.eulerAngles;或者使用Quaternion.Euler(yourAngles)来创建一个新的四元数。 Quaternion.LookRotation<br />Quaternion.Angle<br />Quaternion.Euler<br />Quaternion.Slerp<br />Quaternion.FromToRotation<br />Quaternion.identity
https://blog.csdn.net/qq_15020543/article/details/82834885 https://www.jianshu.com/p/9c3f11eeda1f https://www.cnblogs.com/xiaowangba/p/6314663.html
[*]发送消息和接收消息(脚本函数调用) 为了方便Unity物体之间的通信,Unity推出了SendMessge方法 推送机制可以非常方便我们的脚本开发,它实现的是一种伪监听者模式,利用的是反射机制。
[*]SendMessage 函数原型:public void SendMessage(string methodName, object value = null, SendMessageOptions options = SendMessageOptions.RequireReceiver); 调用一个对象的methodName函数,这个函数可以是公有的也可以是私有的,后面跟一个可选参数(此参数作为传入参数)
[*]SendMessageUpwards 原型:public void SendMessageUpwards(string methodName, object value = null, SendMessageOptions options = SendMessageOptions.RequireReceiver); 作用和SendMessage类似,只不过它不仅会向当前对象推送一个消息,也会向这个对象的父对象推送这个消息(会遍历所有父对象)
[*]BroadcastMessage 原型:public void BroadcastMessage(string methodName, object parameter = null, SendMessageOptions options = SendMessageOptions.RequireReceiver); 作用和SendMessageUpwards的作用正好相反,它不是推送消息给父对象,而是推送消息给所有的子对象(遍历子对象)
当然,在碰撞检测获取到其他物体的collider时候可以通过other.GameObject获取其物体,然后通过GetComponent<脚本名>.FunctionName();来实现对其的方法调用,不过仅支持public的方法。 再其次,被调用脚本函数为static类型,调用时直接用脚本名.函数名()即可。
[*]获取输入事件 有两种获取输入的系统,一种为新的InputSystem,另一种为原本的InputManager。 旧有的输入系统大概都是:
// 按住a键
Input.GetKey(&#34;a&#34;)
//这两种一样,KeyCode枚举类型,down是按下瞬间,up为抬起瞬间
Input.GetKeyDown(&#34;a&#34;)
Input.GetKeyDown(KeyCode.A)
Input.GetKeyUp(&#34;a&#34;)
//鼠标输入,左键0 右键1 中键2
Input.GetMouseButton(0)
Input.GetMouseButtonDown(0)
Input.GetMouseButtonUp(0)
和一些GetAxis类的函数,输入管理器可以在项目管理中设置轴名称,降低程序耦合,另一方面可以让游戏玩家自定义游戏输入按键。
而新的InputSystem则是需要在创建一个InputActions文件,勾选generate C# Class,然后打开InputActions文件,对按键以及对于的调用函数进行设置, Action Maps(最左相当于一个文件夹) ;Action中的InputUp是我用来监听所有能代表Up输入; InputUp子目录中的可以通过最右中的Listen来监听输入;
最后使用要把Player Input挂载在玩家的游戏物体上。 编写玩家逻辑代码: 两套实现方法: 第一套不适用于本地多人游戏,因为两个函数关联,每次按下必执行另一个函数,会广播到所有挂载此脚本的物体上
controls.PlayerMove.upAction.performed += ctx => PlayerMove(getDir(GameController.blockType.Up));
controls.PlayerMove.downAction.performed += ctx => PlayerMove(getDir(GameController.blockType.Down));
controls.PlayerMove.leftAction.performed += ctx => PlayerMove(getDir(GameController.blockType.Left));
controls.PlayerMove.rightAction.performed += ctx => PlayerMove(getDir(GameController.blockType.Right));
第二套方法适合本地多人游戏,
private void OnUpAction(InputValue value)
{
PlayerMove(getDir(GameController.blockType.Up));
}
private void OnDownAction(InputValue value)
{
PlayerMove(getDir(GameController.blockType.Down));
}
private void OnLeftAction(InputValue value)
{
PlayerMove(getDir(GameController.blockType.Left));
}
private void OnRightAction(InputValue value)
{
PlayerMove(getDir(GameController.blockType.Right));
}
https://blog.csdn.net/weixin_46045344/article/details/115445372 https://www.bilibili.com/video/BV1cf4y1F7zY?from=search&seid=12301447195505609892&spm_id_from=333.337.0.0
[*]正交与透视 正交摄像机没有透视效果,一般可以用于 2D 游戏开发或者是 3D 游戏的 UI 开发。在正交相机中唯一与显示范围相关的属性只有一个,那就是 Size,单位为 Unity 单位。这个属性的值代表了摄像机在纵向上一半的显示范围。举例来说,如果把 Size 设置为5,那就意味着这个摄像机在纵向可以显示10个 Uinty 单位。 像素和 Unity 单位之间的转换关系是怎么的呢?准确的说,他们两者之间的关系是不固定的,这取决与在导入素材时&#34;Pixels To Units&#34;这一属性的值
[*]四种坐标系详细介绍 World Space(世界坐标):我们在场景中添加物体(如:Cube),他们都是以世界坐标显示在场景中的。transform.position可以获得该位置坐标。Screen Space(屏幕坐标):以像素来定义的,以屏幕的左下角为(0,0)点,右上角为(Screen.width,Screen.height),Z的位置是以相机的世界单位来衡量的。注:鼠标位置坐标属于屏幕坐标,Input.mousePosition可以获得该位置坐标,手指触摸屏幕也为屏幕坐标,Input.GetTouch(0).position可以获得单个手指触摸屏幕坐标。 ViewPort Space(视口坐标):视口坐标是标准的和相对于相机的。相机的左下角为(0,0)点,右上角为(1,1)点,Z的位置是以相机的世界单位来衡量的。(用的不多,反正我暂时没有用到~呵呵~) 绘制GUI界面的坐标系:这个坐标系与屏幕坐标系相似,不同的是该坐标系以屏幕的左上角为(0,0)点,右下角为(Screen.width,Screen.height)。 世界坐标→屏幕坐标:camera.WorldToScreenPoint(transform.position);这样可以将世界坐标转换为屏幕坐标。其中camera为场景中的camera对象。 屏幕坐标→视口坐标:camera.ScreenToViewportPoint(Input.GetTouch(0).position);这样可以将屏幕坐标转换为视口坐标。其中camera为场景中的camera对象。 视口坐标→屏幕坐标:camera.ViewportToScreenPoint(); 视口坐标→世界坐标:camera.ViewportToWorldPoint(); 通过这些方法可以将坐标进行替换计算。 https://blog.csdn.net/e295166319/article/details/52734585
[*]Vector3类详解 向量的运算: 加减:各个分量分别相加减。标量:只有大小,没有方向数乘:向量与标量的乘数 向量运算: 点乘(余弦,结果为数字) 叉乘(正弦,结果为垂直于两向量的新向量) Vector3的常用方法: 静态成员变量 right(右):代表坐标轴(1,0,0) left (左):代表坐标轴(-1,0,0) up(上) :代表坐标轴(0,1,0) down (下): 代表坐标轴(0,-1,0) forward (前):代表坐标轴(0,0,1) back(后):代表坐标轴(0,0,-1) zero(零):代表坐标轴(0,0,0) one(一):代表坐标轴(1,1,1) 实例成员变量 magnitude:(返回向量的长度,向量的长度是(xx+yy+z*z)的平方根)(只读) normalized:返回一个长度为一的新向量,原向量长度不变 sqrMagnitude:返回向量的长度的平方(只读) 方法 Cross:向量叉乘 Dot:向量点乘 Lerp(v(from),v(to),t):两个向量之间的线性插值。按照数字t在from到to之间插值。 MoveTowards(v,v,speed);当前的地点移向目标。 RotateTowards(v,v,r,m);当前的向量转向目标。 Max(v,v),Min(v,v); 向量的最大值,最小值 Angle:返回两个向量之间的夹角 Distance:返回两个向量之间的距离 还有许多,在下边博客中
https://blog.csdn.net/Barrett_/article/details/111354110?ops_request_misc=%7B%22request%5Fid%22%3A%22164326202916780264039287%22%2C%22scm%22%3A%2220140713.130102334.pc%5Fblog.%22%7D&request_id=164326202916780264039287&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~times_rank-4-111354110.nonecase&utm_term=Vector3&spm=1018.2226.3001.4450 https://blog.csdn.net/Windgs_YF/article/details/87882774
一堆感觉可能会用到的参考
https://www.jianshu.com/p/3d90a5bfad83
https://www.xuanyusong.com/archives/561
https://www.cnblogs.com/driftingclouds/p/6442847.html
https://www.cnblogs.com/xiaoahui/p/10454278.html
https://blog.csdn.net/qq_38721111/article/details/86742317
https://blog.csdn.net/m0_43396324/article/details/121600078
https://www.cnblogs.com/mttnor/p/9647850.html
https://blog.csdn.net/nanggong/article/details/79832456
mask一下可能有用的要看的东西
https://www.gameres.com/847380.html
https://learn.unity.com/tutorial/she-zhi-unity-bian-ji-qi?uv=2020.3&projectId=5facf921edbc2a2003a58d3a
https://itch.io/jams
https://blog.csdn.net/hanjilun/article/details/106041574
https://blog.csdn.net/yindongfang1/article/details/102827602
https://blog.csdn.net/ymiku/article/details/45956969
http://www.sikiedu.com/course/94/tasks
Unity官方的一些教程
https://learn.u3d.cn/tutorial/MiniGameDev#
https://learn.unity.com/tutorial/get-ready-for-unity-essentials?language=en&pathwayId=5f7bcab4edbc2a0023e9c38f&missionId=5f77cc6bedbc2a4a1dbddc46&projectId=612f9602edbc2a1b588a3af3
https://learn.unity.com/project/ping-tai-you-xi-microgame?uv=2019.3
结尾
当然是我们两天内最后做完的垃圾小游戏啦
视频在这里:
https://www.bilibili.com/video/BV1DF411p78x/
活动项目在这里:
https://globalgamejam.org/2022/games/light-and-dark光与暗-1
源码会在这里:
https://gitee.com/gongweixi/LightAndDark
不过由于项目还会在完美世界的制作人培养计划中作为作业,所以暂时先不放出来啦。
四元数乘法:q1q2=(\vec{v1}×\vec{v2}+w1\vec{v2}+w2\vec{v1},w1w2\vec{v1}\vec{v2})
页:
[1]