版本记录
版本号 | 时间 |
---|---|
V1.0 | 2019.01.26 星期六 |
前言
Unity是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。Unity类似于Director,Blender game engine, Virtools 或 Torque Game Builder等利用交互的图型化开发环境为首要方式的软件。其编辑器运行在Windows 和Mac OS X下,可发布游戏至Windows、Mac、Wii、iPhone、WebGL(需要HTML5)、Windows phone 8和Android平台。也可以利用Unity web player插件发布网页游戏,支持Mac和Windows的网页浏览。它的网页播放器也被Mac 所支持。网页游戏 坦克英雄和手机游戏王者荣耀都是基于它的开发。
下面我们就一起开启Unity之旅。感兴趣的看下面几篇文章。
1. Unity开启篇(一) —— Unity界面及创建第一个简单的游戏 (一)
2. Unity开启篇(二) —— Unity界面及创建第一个简单的游戏 (二)
3. Unity开启篇(三) —— 一款简单射击游戏示例 (一)
4. Unity开启篇(四) —— 一款简单射击游戏示例 (二)
5. Unity开启篇(五) —— 一款简单射击游戏示例 (三)
6. Unity开启篇(六) —— Unity动画简介 (一)
7. Unity开启篇(七) —— Unity动画简介 (二)
8. Unity开启篇(八) —— Unity声音简介(一)
9. Unity开启篇(九) —— Unity声音简介(二)
10. Unity开启篇(十) —— Unity粒子系统简介(一)
11. Unity开启篇(十一) —— Unity粒子系统简介(二)
开始
在本教程中,通过创建一个经典的竞技场射击游戏来学习Unity中脚本的基础知识。
Unity的大部分功能都在于其丰富的脚本语言C#
。 您可以使用它来处理用户输入,操纵场景中的对象,检测碰撞,生成新的游戏对象以及在场景周围投射定向光线以帮助您的游戏逻辑。 这可能听起来令人生畏,但Unity公开了具有完备文档的API,使这些任务变得轻而易举 - 即使对于新手开发人员也是如此!
在本教程中,您将创建一个自上而下的射击游戏,它使用Unity脚本来处理敌人的产生,玩家控制,射击射弹以及游戏玩法的其他重要方面。
注意:本教程假设您已具备使用C#或类似编程语言的经验,并了解Unity的界面和工作流程。
本教程是为Unity 5.3
或更高版本编写的。 您可以在此处here下载最新版本的Unity。
虽然Unity也支持UnityScript
和Boo
,但C#
是大多数开发人员倾向于使用的编程语言,并且有充分的理由。 C#被全球数百万开发人员用于应用程序,Web和游戏开发,并且有大量的信息和教程可以帮助您。
打开已有的工程,具体页面如下所示:
在Scene view
视图中查看。 这是一个小型区域,将成为游戏的战场,相机和灯光。 如果您的布局与屏幕截图中的布局不同,请选择右上角的下拉菜单并将其更改为2 by 3
。
没有英雄角色算什么游戏? 您的第一个任务是创建一个GameObject
来表示场景中的玩家。
Creating the Player
在Hierarchy
中,单击Create
按钮,然后从“3D”部分中选择Sphere
。 将球体定位在(X:0,Y:0.5,Z:0)
并将其命名为Player
:
Unity
使用entity-component
系统来构建其GameObjects
。 这意味着所有GameObject
都是组件的容器,可以附加这些组件以赋予其行为和属性。 以下是Unity内置组件的几个示例:
-
Tranform:每个
GameObject
都附带此组件。 它保存GameObject
的位置,旋转和缩放。 - Box Collider:立方体形状的对撞机,可用于探测碰撞。
- Mesh Filter:用于显示3D模型的网格数据。
Player GameObject
需要响应与场景中其他对象的碰撞。
要实现此目的,请在Hierarchy window
窗口中选择Player
,然后单击“检查器”窗口中的Add `Component按钮。 在弹出的菜单中选择Physics> Rigidbody,这将为播放器添加一个Rigidbody组件,以便它可以使用Unity的物理引擎。
像这样调整Rigidody
的值:设置Drag
到1,Angular Drag
到0并选中Freeze Position
旁边的Y复选框。
这将确保Player
无法上下移动,并且在旋转时不会增加阻尼。
Creating the Player Movement Script
现在Player
已准备就绪,是时候创建将从键盘输入并移动Player
的脚本。
在Project window
中,单击Create
按钮并选择Folder
。 将新文件夹命名为Scripts
,并在其中创建一个名为Player
的子文件夹。
在Player
文件夹中,单击Create
按钮并选择C#Script
。 将新脚本命名为PlayerMovement
。 序列如下所示:
注意:使用这样的文件夹可以轻松地按角色组织所有内容并减少混乱。 你将为
Player
制作几个脚本,所以给它自己的文件夹是有意义的。
双击PlayerMovement.cs
脚本。 这将打开您首选的代码编辑器并加载脚本。 Unity附带预装在所有平台上的MonoDevelop
,Windows
用户可以选择安装Visual Studio
,并在运行安装程序时使用它。
本教程假设您使用的是MonoDevelop
,但Visual Studio
用户应该能够顺利进行操作而不会出现任何问题。
一旦您选择的编辑器打开,您将看到以下界面:
这是Unity
为新脚本生成的默认类。它源自基类MonoBehaviour
,它确保此脚本将在游戏循环中运行,并具有对某些事件作出反应的附加功能。如果你来自iOS世界,这个对象相当于UIViewController
。当脚本运行时,Unity会按预定顺序调用多个方法。以下是一些最常见的:
-
Start()
:在脚本第一次更新之前,将调用此方法一次。 -
Update()
:当游戏运行且脚本启用时,此方法将在每一帧被触发。 -
OnDestroy()
:此方法在附加到此脚本的GameObject
之前被调用。 -
OnCollisionEnter()
:当连接此脚本的碰撞器或刚体触及另一个碰撞器或刚体时,此方法被调用。
有关事件的完整列表,请查看Unity’s documentation on MonoBehaviours的文档。
在Start()
方法上方添加以下两行:
public float acceleration;
public float maxSpeed;
它应该是这样的:
这些是公共变量声明(variable declarations)
,这意味着这些变量将在Inspector
中可见,并且可以调整而无需在脚本和编辑器之间来回切换。
acceleration
描述了Player's
速度随着时间的推移而增加的程度。 maxSpeed
是“速度限制”。
在其下方,声明以下变量:
private Rigidbody rigidBody;
private KeyCode[] inputKeys;
private Vector3[] directionsForKeys;
私有变量无法通过Inspector
设置,开发人员有责任在适当的时候初始化它们。
rigidBody
将保存对附加到Player GameObject
的Rigidbody
组件的引用。
inputKeys
是一个用于检测输入的密钥代码数组。
directionsForKeys
包含一个Vector3
变量数组,它将保存方向数据。
用以下内容替换Start()
方法:
void Start () {
inputKeys = new KeyCode[] { KeyCode.W, KeyCode.A, KeyCode.S, KeyCode.D };
directionsForKeys = new Vector3[] { Vector3.forward, Vector3.left, Vector3.back, Vector3.right };
rigidBody = GetComponent<Rigidbody>();
}
这段代码链接每个密钥的相应方向,例如 按W
可将物体向前移动。 最后一行获取对atttached Rigidbody
组件的引用,并将其保存在rigidBody
变量中供以后使用。
要实际移动Player
,您必须处理键盘输入。
将Update()
重命名为FixedUpdate()
并添加以下代码:
// 1
void FixedUpdate () {
for (int i = 0; i < inputKeys.Length; i++){
var key = inputKeys[i];
// 2
if(Input.GetKey(key)) {
// 3
Vector3 movement = directionsForKeys[i] * acceleration * Time.deltaTime;
}
}
}
这里有几件重要的事情:
- 1)
FixedUpdate()
与帧速率无关,在使用Rigidbodies
时应该使用。此方法将以恒定间隔触发,而不是尽可能快地运行。 - 2) 此循环检查是否按下了任何输入键。
- 3) 获取按下的键的方向,将其乘以加速度和完成最后一帧所花费的秒数。这将生成一个方向向量(X,Y和Z轴上的速度),用于移动
Player
对象。
如果您不熟悉游戏编程,您可能会问自己为什么必须乘以Time.deltaTime
。当游戏运行时,帧速率(或每秒帧数)将根据硬件和它所承受的压力而变化,这可能导致在强大的机器上发生很快,而在较弱的机器上发生太慢可能导致不期望的行为。一般规则是当你每个(固定)帧执行一个动作时,你需要乘以Time.deltaTime
。
在FixedUpdate()
下面添加以下方法:
void movePlayer(Vector3 movement) {
if(rigidBody.velocity.magnitude * acceleration > maxSpeed) {
rigidBody.AddForce(movement * -1);
} else {
rigidBody.AddForce(movement);
}
}
上述方法对ridigbody
施加力,使其移动。 如果当前速度超过maxSpeed
,则力量向相反方向移动以减慢玩家的速度,并有效地限制最大速度。
在FixedUpdate()
中,在if语句的右括号之前,添加以下行:
movePlayer(movement);
很好! 保存此脚本并返回Unity编辑器。 在Project window
中,将PlayerMovement
脚本拖动到“层次结构”内的Player
上。
向GameObject
添加脚本会创建组件的实例,这意味着将为您附加的GameObject
执行所有代码。
使用Inspector
将Acceleration
设置为625
,将Max Speed
设置为4375
:
运行场景并使用WASD
键移动Player
:
只有几行代码,这是一个非常好的结果!
然而,有一个明显的问题 - 玩家可以快速移出视线。
Creating the Camera Script
在Scripts
文件夹中,创建一个名为CameraRig
的新脚本,并将其附加到Main Camera
。 需要一些帮助来弄清楚步骤? 您可以查看下面的提示。
选择
Scripts
文件夹后,单击项目浏览器(Project Browser)
中的Create
按钮,然后选择C#Script
。 将新脚本命名为CameraRig
。 最后,将其拖放到Main Camera
对象上,如下所示:
现在,在新创建的CameraRig
类中,在Start()
方法的正上方创建以下变量:
public float moveSpeed;
public GameObject target;
private Transform rigTransform;
您可能已经猜到了,moveSpeed
是摄像机跟随目标的速度 - 可以是场景中的任何游戏对象。
在Start()
内,添加以下行:
rigTransform = this.transform.parent;
此代码获取对场景层次结构中父Camera
对象的变换的引用。 场景中的每个对象都有一个变换Transform
,它描述了对象的位置,旋转和缩放。
在同一个脚本中,添加以下方法:
void FixedUpdate () {
if(target == null){
return;
}
rigTransform.position = Vector3.Lerp(rigTransform.position, target.transform.position,
Time.deltaTime * moveSpeed);
}
CameraRig
移动代码比PlayerMovement
中的移动代码稍微简单一些。 这是因为你不需要Rigidbody
;只需在rigTransform
和目标的位置之间进行插值即可。
Vector3.Lerp()
在空间中取两个点,在[0,1]
范围内取浮点,它描述了沿两个端点的点。 左端点为0
,右端点为1
。将0.5
传递给Lerp()
将在两个端点之间准确返回一个点。
这会使rigTransform
更接近目标位置并稍微缓和一下。 简而言之 - 相机跟随player
。
回到Unity
。 确保在层次结构中仍然选择了Main Camera
。 在Inspector
中,将Move Speed
设置为8,将Target
设置为Player
:
运行游戏并在场景中移动;无论走到哪里,摄像机都应该顺利地跟踪目标变换。
Creating an Enemy
没有敌人的射击游戏很容易被击败,但有点无聊。 通过单击顶部菜单中的GameObject \ 3D Object \ Cube
创建敌人立方体。 将您的Cube重命名为Enemy
并添加Rigidbody
组件。
在Inspector
中,首先将Cube
的Transform
设置为(0,0.5,4)
。 在Rigidbody
组件的Constraints
部分中,选中Freeze Position
类别中的Y复选框。
优秀 - 现在让你的敌人以威胁的方式四处走动。 在Scripts
文件夹中创建一个名为Enemy
的脚本。 您现在应该成为专家,但如果没有,请查看教程前面的说明以供参考。
接下来,在类中添加以下公共变量:
public float moveSpeed;
public int health;
public int damage;
public Transform targetTransform;
您可能无需太多困难就能弄清楚这些变量代表什么。 您之前使用moveSpeed
创建了摄像机装备,它在此处具有相同的效果。 health
和damage
有助于确定敌人何时死亡以及他们的死亡会对Player
造成多大伤害。 最后,targetTransform
引用了Player
的变换。
说到玩家,你需要创建一个类来表示敌人想要摧毁的所有玩家。
在项目浏览器中,选择Player
文件夹并创建一个名为Player
的新脚本;此脚本将对碰撞做出反应并跟踪玩家的健康状况。 双击脚本进行编辑。
添加以下公共变量以存储Player's
的生命值:
public int health = 3;
这为健康状况提供了默认值,但也可以在检查器中进行修改。
要处理碰撞,请添加以下方法:
void collidedWithEnemy(Enemy enemy) {
// Enemy attack code
if(health <= 0) {
// Todo
}
}
void OnCollisionEnter (Collision col) {
Enemy enemy = col.collider.gameObject.GetComponent<Enemy>();
collidedWithEnemy(enemy);
}
当两个带有碰撞器的刚体接触时,OnCollisionEnter()
会触发。 Collision
参数包含有关接触点和碰撞速度等信息。 在这种情况下,您只对碰撞对象的Enemy
组件感兴趣,因此您可以调用collidedWithEnemy()
并执行攻击逻辑 - 您接下来将添加该逻辑。
切换回Enemy.cs
并添加以下方法:
void FixedUpdate () {
if(targetTransform != null) {
this.transform.position = Vector3.MoveTowards(this.transform.position, targetTransform.transform.position, Time.deltaTime * moveSpeed);
}
}
public void TakeDamage(int damage) {
health -= damage;
if(health <= 0) {
Destroy(this.gameObject);
}
}
public void Attack(Player player) {
player.health -= this.damage;
Destroy(this.gameObject);
}
你已经熟悉FixedUpdate()
了,不同的是你使用的是MoveTowards()
而不是Lerp()
。 这是因为敌人应该始终以相同的速度移动,而不是在接近目标时轻松进入。 当敌人被射弹击中时,会调用TakeDamage()
;当敌人达到0健康时,它会自我毁灭。Attack()
类似 - 它对玩家施加伤害然后敌人摧毁自己。
切换回Player.cs
并在collidedWithEnemy()
中,用以下内容替换Enemy attack code
:
enemy.Attack(this);
玩家将受到伤害,敌人将在此过程中自我毁灭。
切换回Unity
。 将Enemy
脚本附加到Enemy
对象并在Inspector
中,在Enemy上设置以下值:
- 1)
Move Speed: 5
- 2)
Health: 2
- 3)
Damage: 1
- 4)
Target Transform: Player
到现在为止,您应该能够自己完成所有这些工作。 自己尝试一下,然后将结果与下面的GIF进行比较:
在游戏中,与玩家对抗的敌人构成有效的敌人攻击。 使用Unity的物理检测碰撞几乎是一项微不足道的任务。
最后,将Player
脚本附加到层次结构中的Player
。
运行游戏,并密切关注控制台:
当敌人到达玩家时,它会成功执行攻击并将玩家的健康变量减少到2。但是在控制台中抛出NullReferenceException
,指向Player
脚本:
啊哈 - 玩家不仅可以与敌人发生碰撞,还可以与游戏世界的其他部分发生碰撞,例如竞技场。 这些游戏对象没有Enemy
脚本,因此GetComponent()
返回null
。
打开Player.cs
。 在OnCollisionEnter()
中,在if
语句中包装collidedWithEnemy()
:
if(enemy) {
collidedWithEnemy(enemy);
}
这样就没有null
了。
后记
本篇主要讲述了Unity脚本简介,感兴趣的给个赞或者关注~~~