在写学习笔记之前,我先说说为什么会做这么一个游戏DEMO吧,目前是在一家互联网广告公司任职3D设计组长一职,主要内容是CG这块,因为对AR和VR很感兴趣,并且暂时选择了Unity这款游戏引擎做为我的敲门砖,因为CG是我的优势,所以对Unity这款软件不陌生,上手很快,唯一比较陌生的是程序代码这块,但因为大学的时候学过C和游戏编程,所以倒不止于看到程序就放弃,只是工作一两年完全没接触程序了。因此我决定在上手了Unity软件后,准备啃下Unity中编程这块硬骨头。我选择了C#这门语言来学习,因为市面上大多数AR和VR教程都是用C#来写的,这样学习起来方便一些。
我先在在网上学习了一些Unity的C#基础知识,比如GameObject,Transform,枚举,方法等,对Unity中编程的基础知识有了一些了解,接着我觉得还得跟着一些教程做练习,理解消化,跟着官方教程是一个很不错的选择,接下来就进入我的Spaceshooter笔记。
一:教程的前5个课时讲述的是3D知识,比如相机灯光背景和特效等,表示毫无压力跳过
二:角色控制、发射子弹
代码如下:
using UnityEngine;
using System.Collections;
public class spaceshooter : MonoBehaviour {
public float speed;
public float rt;
public float xMin;
public float xMax;
public float zMin;
public float zMax;
publicGameObjectshot;
publicTransformshotspawn;
publicfloatfirerate;
privatefloatnextfire=0.0f;
voidUpdate(){
if(Input.GetButton("Fire1")&&Time.time>nextfire){
nextfire=Time.time+firerate;
Instantiate(shot,shotspawn.position,shotspawn.rotation);
}
}
void FixedUpdate() {
float mh = Input.GetAxis ("Horizontal");
float mv = Input.GetAxis ("Vertical");
Rigidbody rigidbody =GetComponent();
Vector3 movement = new Vector3 (mh, 0.0f, mv);
rigidbody.velocity = movement*speed;
transform.rotation= Quaternion.Euler (0.0f, 0.0f, rigidbody.velocity.x* rt);
transform.position = new Vector3 (
Mathf.Clamp (transform.position.x,xMin,xMax),
0.0F,
Mathf.Clamp(transform.position.z,zMin,zMax)
);
}
}
这段代码主要实现了以下功能
1:这段代码首先没有写在Unity默认的Update函数中,而是写在了FixedUpdate()函数中,在网上搜了一下Update和FixedUpdate的区别,Update()和FixedUpdate()在游戏中都会在更新的时候自动循环调用。但是Update是在每次渲染新的一帧的时候才会调用,也就是说,这个函数的更新频率和设备的性能有关以及被渲染的物体(可以认为是三角形的数量)。在性能好的机器上可能fps 30,差的可能小些。这会导致同一个游戏在不同的机器上效果不一致,有的快有的慢。因为Update的执行间隔不一样了。而FixedUpdate,是在固定的时间间隔执行,不受游戏帧率的影响。所以处理Rigidbody的时候最好用FixedUpdate。
PS:FixedUpdate的时间间隔可以在项目设置中更改,Edit->Project Setting->time找到Fixed timestep。就可以修改了。
2:关于Rigidbody调用的问题,官方案例是4.6版本,可以直接调用rigidbody,但5.0以后的版本中,Unity将C#规范了,要调用rigidbody必须要定义,比如Rigidbody r=GetComponent<Rigidebody>()来定义。
3:通过键盘上的"W" "A" "S" "D"或者方向键来控制飞船的前后左右的移动,左右是通过Input.GetAxis ("Horizontal")和Input.GetAxis ("Vertical")来实现,它们都是Vector3类型并且值范围都是在-1到1之间 ,此时定义一个访问修饰符为public且float类型的speed来控制飞船的速度;
4:用Math.Clamp方法来控制飞船移动边界,通过查阅官方文档知道,Math.Clamp返回的是一个float类型value的值,写法是Math.Clamp(float value,float min,float max)
5:设定飞船的旋转边界值,rotation是一个Quaternion类型的值,用Quaternion.Euler来表示物体的旋转,物体在x和y方向上的旋转为0 ,在z方向上的旋转跟物体的速度正负值挂钩,所以用rigidbody.velovity.x来表示,这样,按下方向键,向左或者向右时,飞船就能够发生“侧翻”效果.
6:Instantiat方法的使用,参照官方文档,Instancitate是用来实例化物体的,使用方法是Instantiate (Object Original,Vector3 position,Quaternion rotation),Object是要实例化的对象,Vector是要复制到的位置,Quaternion是设置物体的旋转。
7:Time.time方法用来获取游戏运行的时间,在本例中用来设置按下鼠标左键后间隔多少时间发射下一发子弹。
8:Input.GetButton("FIre1")代码块中,Fire1表示鼠标左键,Fire0表示鼠标中间,Fire2表示鼠标右键,而且Input.GetButton表示持续按下Fire1,同类型的还有Input.GetButtonDown和Input.GetButtonUp也表示按下按键,但是只能计算一次按下或者弹起。
二:边界
voidOnTriggerExit(Colliderother){
Destroy(other.gameObject);
}
调用了Trigger中的一个方法,即离开这个边界的物体都会被“杀死”,条件就是只要触碰到了边界物体的Collider就会调用这个方法,Destroy中的参数是other.gameobjet,表明只会杀死其他的物体,而不会把自身给销毁掉,跟后面要用到的碰撞方法有区别。
三 创建激光
void Start()
{
Rigidbodyrigidbody=GetComponent();
rigidbody.velocity=transform.forward*speed;
rigidbody.angularVelocity=Random.insideUnitSphere*tumble;
}
1:这个代码是用来表示行星的产生方向和起始随机的旋转速度,transform.forward用来表示z轴方向上的速度,为(0,0,1)
2:rigidbody.angularVelocity用来表示物体的旋转角速度,是一个Vector3类型的,而随机的起始速度则由Random类中的insideUnitSphere来表示,它是一个半径为1的球,方向是-1到1,并且这个方法只能放在Start函数中只能调用一次,自己在做的时候,放在了Update()函数中,导致这个产生的行星都像发了疯似的旋转,因为每一帧都会更新它的角速度,而这个角速度又是随机产生的。
四 爆炸
代码如下
publicGameObjectzidanbaozha;
publicGameObjectfeichuanbaozha;
publicGameObjectzidanbaozhashengyin;
publicGameObjectfeichuanbaozhashengyin;
voidOnTriggerEnter(Colliderother){
//自己的写法
if(other.tag=="Boundary")//这里的if是不让边界盒子跟行星发生碰撞
{
return;//这里的return是返回一个空的值,表示什么也不做,也可以不写这个return
}
if(other.tag=="Bolt")
{
Destroy(other.gameObject);
Destroy(gameObject);
Instantiate(zidanbaozha,transform.position,Quaternion.identity);
Instantiate(zidanbaozhashengyin,transform.position,Quaternion.identity);
}
if(other.tag=="Player")
{
Destroy(other.gameObject);
Destroy(gameObject);
Instantiate(feichuanbaozha,transform.position,Quaternion.identity);
Instantiate(feichuanbaozhashengyin,transform.position,Quaternion.identity);
}
if(other.tag=="xingxing")
{
Destroy(other.gameObject);
Destroy(gameObject);
Instantiate(zidanbaozha,transform.position,Quaternion.identity);
}
//官方教程的写法
/*if(other.tag=="Boundary")//这里的if是不让边界盒子跟行星发生碰撞
{
return;//这里的return是返回一个空的值,表示什么也不做,也可以不写这个return
}
Instantiate(zidanbaozha,transform.position,Quaternion.identity);
if(other.tag=="Player")
{
Instantiate(feichuanbaozha,transform.position,Quaternion.identity);
}
Destroy(other.gameObject);
Destroy(gameObject);*/
1:这里的代码是分类子弹的collider碰撞到不同的物体的时候,会有不同的爆炸特效,判断的一句是gameobject的Tag。我的写法相比官方的想法可能会更死板,不简练,但毕竟不是写代码的老手,这样写代码方便自己理解,以后代码量上去了我相信自己还是能够养成良好的代码习惯。
2:这里的Quaternion.identity表示物体没有旋转。
五 生成波
代码如下
publicGameObjecthazard;
publicVector3spawnwaves;
publicinthazardcount;
publicfloatwaittime;
publicfloatspawntime;
publicfloatjiangeshijian;
voidStart(){
StartCoroutine(SpawnWaves());
}
IEnumeratorSpawnWaves(){
yieldreturnnewWaitForSeconds(waittime);
while(true)
{
for(inti=0;i
{
floata=Random.Range(-4.76f,4.76f);
hazard.transform.position=newVector3(a,0.0f,14.0f);
hazard.transform.rotation=Quaternion.identity;
Instantiate(hazard,hazard.transform.position,hazard.transform.rotation);
yieldreturnnewWaitForSeconds(spawntime);
}
yieldreturnnewWaitForSeconds(jiangeshijian);
}
}
1:这里的代码是让“敌人”也就是行星能够一波一波地产生,并且在每次游戏开始之前,会过1s后才开始,让玩家做好游戏准备,并且“敌人”会一波一个产生,每一波之间也会有间隔时间。
2:这里用到了SpawnWaves()方法,其实我觉得有没有这个方法也是一样的,因为能够产生一波一波效果的主要是协同方法中的WaitForSeconds类来控制的,而且SpawnWaves()方法必须要在void Star()方法中手动调用才行。
3:能够实现一波一波效果的,最关键是用到了协同函数,来实现延迟的效果也就是StartCoroutine(method name)方法,查看官方文档知道,StartCoroutine(方法名)就能启用Coroutine方法,并且返回值的类型不再是void,而是IEnumerator,并且用关键字yield return new WaitForSeconds (float value)来标识WaitForSeconds类,这里的value是标识延迟的时间。(http://docs.unity3d.com/ScriptReference/WaitForSeconds.html)这里插入官方文档关于如何调用Coroutine,并且使用WaitForSeconds来实现延迟效果有很好的解释
六 结束游戏
在这一块中,主要困惑点是在重新加载当前游戏的api,因为官方教程用的是4.2的版本,用Application.loadlevel(Application.loadlevel) 来加载当前场景,当时在5.3版本中完全不能用,在5.3版本中使用SceneManager.LoadScene(scene name or index of the scene),并且在用这个类的时候,还必须引用Scene Management,在官网文档中其实也没解释清楚,在网上搜了好久才知道这个的用法,最终,在5.3中重新加载当前场景,我的代码是
usingUnityEngine;
usingSystem.Collections;
usingUnityEngine.SceneManagement;
publicclassGameController:MonoBehaviour{
void Update()
{
if (gameover)
{
if(Input.GetKeyDown(Keycode.R))
{
SceneManager.LoadScene(0);
}
}
}
}
这样就当按下R键的时候,就会重新加载当前的场景,重新开始游戏