【学习笔记】Unity3D官方游戏教程:Survival Shooter tutorial

2017-06-25 by Liuqingwen | Tags: Unity3D | Hits

一、前言

刚开始学习 Unity3D 游戏开发,没什么资料,看了官方的视频教程,感觉还不错。不过,对于新手来说,莫过于实战能力的提高了。学习完还应该动手写写代码,熟悉一些旧的知识,掌握一些新东西。所以对于我这种刚入门的游戏开发者,还是非常有必要把自己的学习过程记录下来,加深自己对 Unity3D 游戏开发的理解和认识,提高实战能力。

对了,对于新手入门,官方的视频教程还是非常值得一看的!推荐到 YouTube 上把官方的视频教程下载下来,因为新手一遍可能不能完全看懂,多看几次,多写代码,多做总结,项目源代码也可以下载下来研究研究,能真正提高实战能力才是王道。 smile

二、学习笔记

话又说回来,我只是初学者,我自己是在官方下载了项目素材后一步一步按部就班地实现游戏的基本功能的,然后在此基础上自己再实现点小功能什么的,大家可以到官方网站教程地址下载相关源文件,以下是我简单的一些学习笔记记录:

1. GameObject上多个脚本开发

我想,对于 GameObject 上使用单个脚本还是多个脚本这是新手很容易进入的误区,刚开始我总是认为一个 GameObject 只能添加或者只需要添加一个自定义的 Script 脚本,这是很不符合游戏开发流程的。想想,如果一大堆逻辑写在一个脚本里,肯定不利于解耦、容易出错、也很难维护。

实际开发过程中一个 GameObject 可能会有多个脚本协作运行。每个脚本都作为一个独立的 Component 组件,这是单一职责原则,利于解耦和调试。比如一个游戏玩家 Player 上有控制移动的 PlayerMovement 脚本,也有控制射击的 PlayerShooting 脚本,还有生命值 PlayerHealth 脚本组件等,独立而又能相互调用,降低开发难度:

1
2
3
4
5
6
7
8
GameObject player = GameObject.Find("Player");

PlayerHealth health = player.GetComponent<PlayerHealth>();
PlayerShooting shooting = player.GetComponent<PlayerShooting>();

if(health.isDead) {
shooting.enabled = false;
}

2. Component组件都是可以动态设置的

Unity3D 可以直接用编辑窗口就能做出很基础的功能,但是这并不意味之所有组件只能在编辑状态下设置,我们完全可以在脚本中动态获取相关组件,并设置其相关属性值。就像改变游戏对象的坐标值,操作刚体的各种属性,开启禁用游戏物体的碰撞体属性等等。

我们写的附加在游戏物体上的脚本同样是 Component ,都可以通过代码获取相对于的组件: T component = GameObject.GetComponent<T>() ,获取后可以动态禁用组件也可以动态更改属组件的各个属性值,很方便是吧?! grin

3. GameObject在销毁后其相关属性也同时销毁

我在游戏开发最后添加了一个自定义的小小功能:给玩家治疗生命的游戏物体。设置很简单:在检测到玩家碰撞到治疗物体后,玩家生命值恢复,治疗物体消失,同时播放治疗效果的音频。功能虽小,但是加强了游戏的可玩性,不过我发现,运行我写的代码后虽然有治疗效果,但是并没有触发音频效果。

Survival Shooter Heal GameObject

原来,在我调用了 Destroy(GameObject) 之后,附在 GameObject 上的 Audio Source 音频组件也就销毁了,治疗效果的音频播放自然也就停止了。针对这个问题,我想了一个折中的方案:让治疗物体在一小段时间后再销毁,这期间治疗物体关闭碰撞体属性,位置不断上升,同时就能播放完整的治疗音效了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void OnTriggerEnter (Collider other) {
//检测碰撞体是否是游戏玩家
if ( other.CompareTag ("Player") ) {
var player = other.gameObject as GameObject;
var health = player.GetComponent<Health> ();
if ( ! health.isDead ) {
health.HealHealth (this.healPoint);
//禁用停止相关组件
this.GetComponent<BoxCollider> ().enabled = false;
this.GetComponent<ParticleSystem> ().Stop ();
//播放治疗效果音频
this.healAudio.Play ();
}
//一段时间后销毁治疗物体
Destroy (this.gameObject, this.destroyTime);
}
}

4. Time.deltaTime的使用

在开发过程中,我们经常需要设置游戏玩家的位置或者移动速度。我在看视频教程的时候,总是不明白为什么需要在速度后面再乘以 Time.deltaTime ,直接用速度不就可以了吗?后来我知道原因了,大概是这样的:我们不是能利用设置 Time.timeScale = 0 来暂停游戏吗?道理是一样的,乘以 Time.deltaTime 能够达到全局控制速度的作用。所以,对于这种按照帧率来渲染的动画,速度乘以帧率是有道理的,特别是在 FixedUpdata() 函数里,模拟更加真实。

1
2
3
4
5
6
Vector3 movement;
private void Move(float x, float z) {
this.movement.Set(x, 0f, z);
this.movement = this.movement.normalized * this.speed * Time.deltaTime;
this.player.MovePosition (this.transform.position + this.movement);
}

5. 使用物理射线检测碰撞并用LineRender画线

射线检测碰撞是 Unity 中很重要的一个物理概念。射线也困扰了我很久,在学习了这个游戏教程之后,我总算明白了它的基本原理:射线就是一条从一个点到另一个点的不可见直线,它能检测到碰撞层中所碰撞到的物体,并算出相应的碰撞点。

在这个游戏开发中,射击后用射线来检测碰撞物体和碰撞点,接着就可以用 LineRenderer 从枪口画出一条到碰撞点的可见直线了,最后做一些让射击逼真的动画色彩效果,比如光照、音效等,这样就实现基本的射击动画了,可以看代码,并不难:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//定义可以被射击碰撞到的层
int shootLayer = LayerMask.GetMask ("Shootable");

//射击线的起始位置(index)和最大长度
int startIndex = 0;
int endIndex = 1;
float maxLength = 100f;

//定义用于碰撞测试的射线(原点属性和方向属性)
Ray ray;
ray.origin = transform.position;
ray.direction = transform.forward;

//用LineRenderer画出射击线(需要起点位置和终点位置)
LineRenderer shootLine = GetComponent<LineRenderer>();
shootLine.SetPosition (startIndex, transform.position);

//物理射线碰撞测试(shootHit是输出碰撞点,如果有的话)
if ( Physics.Raycast (ray, out shootHit, maxLength, shootLayer) ) {
//如果射线能检测到前方有物体碰撞,那么判断是否是射击到怪物
var hitObject = shootHit.collider.gameObject;
if ( hitObject.CompareTag ("Enemy") || hitObject.CompareTag("BOSS") ) {
var health = hitObject.GetComponent<Health> ();
if ( ! health.isDead ) {
//自定义的怪物生命值接受伤害的函数
health.TakeDamageAt (this.damagePerShoot, shootHit.point);
}
}
//子弹射击线的终点定在碰撞物体的位置,完成绘画
gunLine.SetPosition (endIndex, shootHit.point);
}
else
{
//如果射线检测到前方最大距离内没有物体碰撞,直接画最长的一根线就可以
shootLine.SetPosition (endIndex, transform.forward * maxLength);
}

//添加一些射击效果,比如音效,射击碰撞粒子效果等
//other code here...

6. 其他小知识

当然,我也学到了一些其他的新的东西或者需要注意的地方:

  1. 相互引用的物体之间要注意游戏物体是否已经被销毁,否则容易抛出 NullPointerException 错误
  2. 动画控制 Animator Override Controller 是对 Animator Controller 的复用
  3. AwakeStart 函数的区别: Awake 立刻运行,即使物体被禁用,但是 Start 必须在 Enabled 非禁用的前提下调用
  4. 基本的自动寻路应用,可以随时停止: GetComponent<NavMeshAgent>().enabled = false
  5. 疑问:获取 Player 对象有两种主要的方式: Find 和手动添加引用 public GameObject Player ,区别还不是很清楚?(以后学习过程中关注这点,如有朋友留言告知,非常感谢!)

三、总结

以上就是我在《 Survival Shooter tutorial 》游戏教程中学到的一些入门的基础知识点。对于新手来说,要真正的具备独立开发游戏的能力还有很多很多要学的,这还是在我使用了官方素材的前提下,只需要写写代码实现就可以了,单独素材也是需要花时间制作或者搜寻的,真的,要学的东西还有很多啊!默默加油吧! joy

资料:
Survival Shooter tutorial: https://unity3d.com/learn/tutorials/projects/survival-shooter-tutorial
Unity3D(www.youtube.com): https://www.youtube.com/user/Unity3D


Comments: