// Unity学习笔记(零基础入坑),自用的同时也许也能帮助别人
// 初学者,没太多编程基础,所以难免可能会有写错的地方,请见谅
//详细教程视频都有,笔记只记重点
传送门: Unity学习笔记 - Roguelike Tutorial(上)
五、让游戏元素动起来
1. 创建了一个基类
为玩家和敌人提供运动功能,玩家和敌人的脚本会继承这个基类。
public abstract class MovingObject : MonoBehaviour {}
关于abstract:
The Abstract keyword enables us to create classes and class members that are incomplete and must be implemented in the derived class.
https://msdn.microsoft.com/en-gb/library/sf985hc5.aspx
2.变量
public float moveTime = 0.1f; //移动 1 point 的时间
public LayerMask blockingLayer; //检测碰撞的层
private BoxCollider2D boxCollider;
private Rigidbody2D rb2D;
private float inverseMoveTime;
3. 函数1 - Start函数
获取到会用到的变量
4. 函数2 - 平滑运动函数 SmoothMovement
使玩家和敌人呈现连续、平滑的运动。
输入目的地的Vector3即可实现当前物体向目的地的连续、平滑的直线运动
逻辑:和终点有距离——>向终点方向移动一“步”(inverseMoveTime * Time.deltaTime)并刷新距离——>停一帧——> 和终点有距离 ……
Magnitude是向量的长度,sqrMagnitude是长度的平方:
一个向量V的长度计算方法是Mathf.Sqrt(Vector3.Dot(v, v))。然而,sqrt计算是相当复杂的,需要更长的时间比正常执行算术操作。计算长度平方来代替直接计算长度速度更快,除掉开根号计算是基本相同的。如果你只是使用大小比较的距离,那么你同样可以比较平方长度大小来比较距离,得到相同的结果。
http://wiki.ceeger.com/script:unityengine:classes:vector3:vector3.sqrmagnitude
public static Vector3 MoveTowards(Vector3 current, Vector3 target, float maxDistanceDelta);
MoveToward的作用是将当前值current移向目标target。(对Vector3是沿两点间直线)
maxDistanceDelta就是每次移动的最大长度。
返回值是当current值加上maxDistanceDelta的值,如果这个值超过了target,返回的就是target的值。
文/fan2b(简书作者)
5. 函数3 - Move
输入移动方向,返回是否有障碍及障碍物体
没有障碍则触发“平滑运动函数” SmoothMovement (有障碍的问题会在后两个函数中处理)
“out”在这里被用来让函数返回多个值,即不止返回一个bool值,还会返回一个raycastHit2D类型的值(hit)。
The out keyword causes arguments to be parsed by reference. In this case we're using it to return more than one value from our Move function.
boxCollider.enabled = false;
We're going to disable the attached box collider2D to make sure that when we are casting our ray that we're not going to hit our own collider.
hit = Physics2D.Linecast(start, end, blockingLayer);
Physics2D.Linecast会返回一个RaycastHit2D,即射线检测到的第一个物体。
6. 函数4 - AttemptMove 获取障碍物
接收Move函数返回的障碍物,
获取到障碍物的component并作为参数传入函数5 OnCantMove,并运行OnCantMove。
泛类型 <T>
public T GenericMethod <T> (T param)
在函数被实际调用时,T会被具体的类型替代。
http://unity3d.com/cn/learn/tutorials/topics/scripting/generics
*之所以使用泛类型是因为,在继承类player、和继承类enemy中障碍的类型是不同的(墙、player)。
泛型类型约束——关键字 where
protected virtual void AttemptMove(intxDir,intyDir) where T:Component
{ ... }
限定泛类型的范围
四种限制类型:
Class, Struct, new (), interfaces
https://msdn.microsoft.com/zh-cn/library/bb384067.aspx
7. 函数5 - OnCantMove
abstract函数,在基类中未定义具体功能,在继承类中会被重写,以实现在不同继承类中的不同功能。
2016.08.10 这篇脚本 对一个零编程基础的“玩家”真心够复杂,而我的技巧就是适度地 不 求 甚 解!
六、墙的逻辑
C# Wall,撞一下会碎,撞多了会消失。
墙被撞的时候,调用函数DamageWall就可以了。
返回unity,要把这个脚本挂在8块墙上,每块墙的dmgSprite变量处要放入对应的被撞碎的墙的图片。
七、Player动画控制器设置
打开Player的Animator Controller。
1. 参数列表里,新建两个trigger,一个用来触发攻击,一个用来触发受伤。
2. 在PlayerIdle和PlayerAttack之间建立双向Transitions
PlayerIdle --> PlayerAttack: Has Exit Time不勾选,即触发后立即切换动画
PlayerIdle <-- PlayerAttack: Has Exit Time勾选,即当前动画结束后切换动画,Exit Time =1,即完全播完后切换
Transition Duration都改为0,因为2D的基于sprite的动画切换无法实现过渡,3D才需要。
3. 触发条件设定:PlayerIdle --> PlayerAttack的Condition选上建好的Trigger
4. PlayerIdle和PlayerDamage同理
动画转换测试:
点击左下参数列表里的Trigger即可触发相应动画:
2016.08.11
八、Player的脚本 —— 控制player的移动
先在GameManager中补充了一些要用到的东西:
新建脚本Player:
1. 函数 - Start
重写基类函数:
protected override void Function ()
{ 重写内容……
base.Function();
重写内容…… }
2. 函数 - CheckIfGameOver
3. 函数 - 重写 AttemptMove
4. 自启动函数 - Update
##两次栽在Input.GetAxis的这个“Horizontal”上了,是“Horizontal”不是“Horizonal”啊!
调用AttemptMove,同时设定泛类型T-->Wall,并传入获取到的输入参数。
*泛类型在函数被执行时,需将泛类型具体化。
AttemptMove运行时会调用基类中的Move函数和OnCantMove函数,而Move函数又会调用SmoothMovement函数。
Input.GetAxisRaw:
Since input is not smoothed, keyboard input will always be either -1, 0 or 1. This is useful if you want to do all smoothing of keyboard input processing yourself.
5. 函数 - 重写OnCantMove
6. 函数 - Restart
7. 自启动函数 - OnTriggerEnter2D
8. 自启动函数 - OnDisable
当前物体disable的时候,将分数值传给GameManager
9. 函数 - LoseFood
当前脚本中未被调用,留在enemy脚本中产生攻击时调用。
最后,Player脚本挂到player上,Blocking Layer选好。
2016.08.12 Fighting
九、Enemy的脚本 —— enemy的移动和攻击
1. 继承MovingObject,重写Start函数
获取自身动画和player的transform。
2. 重写AttemptMove函数
增加是否移动的判断,enemy动一帧停一帧,不会连续动。
3. 新增函数 MoveEnemy —— 智能判断趋向player的移动方向
这个函数的作用是判断往哪个方向移动,先选X or Y,再选 正 or 负。
MoveEnemy是这个脚本的核心,它调用AttemptMove,而AttemptMove则调用其他基类中的函数。但enemy脚本中并不包含Update,MoveEnemy会被GameManager调用。这大概是因为当前是否让enemy移动的判断要写在GameManager里,并且场景中会有多个enemy。
public void MoveEnemy()
This is going to be called by the GameManager when it issues the order to move to each of our enemies in our Enemies list.
Mathf.Abs()是取绝对值。
这里也凸显了float.Epsilon作为一个无限接近于0的数值的作用。
条件表达式:
基本格式 表达式1? 表达式2:表达式3
1为真则返回2的值,1为假则返回3的值。
http://blog.csdn.net/liyzh_inspur/article/details/3080769
4. 重写OnCantMove
最后要返回编辑器把脚本挂在两个enemy上,把playerDamage参数设定上,还有blockingLayer。
2016.08.13
十、Enemy动画控制和行动触发
1. enemy动画控制器
跟player动画控制器的操作基本相同
Enemy1Idle ---> Enemy1Attack :No Has Exit Time,Condition +Trigger "enemyAttack"
Enemy1Idle <--- Enemy1Attack :Has Exit Time, Exit Time = 1
Transition Duration都改为0
2. GameManager中调度enemy的移动
变量:
Awake新加List
每次开始新level,GameManager重新加载,Awake运行,清空上一关的敌人:
加敌人到列表的函数,在enemy脚本中调用:
协程,调用enemy脚本中的移动函数:
*为什么如果没有敌人,要多停一下,player再动?不知道。
Update 调用协程MoveEnemies,MoveEnemies调用enemy上的MoveEnemy完成移动:
2016.08.14
PS: 今天测试的时候发现,动画Trigger名字的大小写又搞错了,然后OutWall没有加Collider
2016.08.15