[Unity3D入门]分享一个自制的入门级游戏项目"坦克狙击手"
[Unity3D入门]分享一个自制的入门级游戏项目"坦克狙击手"
我在学Unity3D,TankSniper(坦克狙击手)这个项目是用来练手的。游戏玩法来自这里(http://www.4399.com/flash/127672_3.htm),虽然抄袭了人家的创意,不过我只用来练习(目前还很不成熟,离人家的境界相差很大),坦克、导弹、建筑模型来自网络,应该不会有版权问题吧。
由于模型和代码总共10M以上了,需要源代码和发布的Windows版、网页版程序的同学麻烦支付100元并留下你的邮箱~
到目前为止,用到的Unity3D知识有:地形Terrain,子物体gameObject,预制体Prefab,粒子系统Shuriken,刚体rigidbody,碰撞体collider,场景scene。
本文将非常简略,因为我也不知道该详写什么略写什么。有任何问题的话请留言,我会详细回复,并且根据情况加入正文。
需要step by step指导的同学,可以参考(http://pixelnest.io/tutorials/2d-game-unity/)。我就是从这篇文章开始学习Unity3D的。有了这个基础,看本文就没有什么问题了。
如何创建大量坦克
目前TankSniper里有4个坦克模型。如你所见,游戏中需要出现大量的坦克。在Unity3D中,我们不用new SomeTank()这种方式创建坦克,而是用Unity3D自带的Instantiate(prefab)方法创建坦克。其中的prefab就是预先设计订制的坦克模板,所以叫预制体。
创建预制体很简单,你只需
-
在Hierarchy中创建一个Cube。
-
把导入的坦克模型拖拽到Hierarchy面板。
-
调整Cube和坦克模型的position、rotation、Scale,使Cube恰好包住坦克模型,然后把坦克模型拖拽到Cube下,成为Cube的子物体。
-
把Cube拖拽到Project面板的Asset文件夹下(或Asset的子文件夹下)。
这样,一个以Cube为名称的预制体就做好了。以后你就可以在C#脚本中通过写
Instantiate(Cube);
这样的句子来创建坦克了。
一个小问题是,为什么要把坦克模型当做Cube的子物体?理由有2:首先,这样可以任意调整坦克模型的transform属性,而预制体整体的transform仍旧可以是0,0,0,这样方便使用;然后,用Cube严密包裹坦克模型后,Cube可以作为碰撞检测的边界,长方体之间的碰撞计算量比复杂的坦克模型要小得多。这是一种常用的做法。
爆炸效果和导弹尾焰
爆炸和尾焰都是用粒子系统做的,通过调整粒子系统的参数就可以实现,而且我没有用任何纹理图片。视觉效果虽然一般,不过目前这不是我要学的重点,暂时知足常乐一下好了。
导弹攻击坦克
实际上就是在碰撞事件OnCollisionEnter中写代码:在导弹的OnCollisionEnter事件中添加爆炸的粒子系统并销毁导弹;在坦克的OnCollisionEnter事件中减掉一定数值的HP值,若HP<=0了,就用Unity3D自带的Destroy()方法销毁坦克。
1 void OnCollisionEnter(Collision collision) { //当碰撞体与刚体与其他碰撞体或刚体接触时调用 2 // foreach (ContactPoint contact in collision.contacts) { 3 // Debug.Log(string.Format("{0}", contact.ToString())); 4 // Debug.DrawRay(contact.point, contact.normal, Color.white); 5 // } 6 // if (collision.relativeVelocity.magnitude > 2) 7 // audio.Play(); 8 foreach (ContactPoint contact in collision.contacts) { 9 ExplosionEffectHelper.Instance.Explode(ExplosionEffectHelper.ExplosionEffect.MissileExplosion, contact.point); 10 SoundEffectHelper.Instance.MakeExplosionSound(); 11 Destroy(this.gameObject); 12 break; 13 } 14 }
1 void OnCollisionEnter(Collision collision) { //当碰撞体与刚体与其他碰撞体或刚体接触时调用 2 //foreach (ContactPoint contact in collision.contacts) { 3 // Debug.DrawRay(contact.point, contact.normal, Color.white); 4 //} 5 //if (collision.relativeVelocity.magnitude > 2) 6 // audio.Play(); 7 var missileScript = collision.gameObject.GetComponent<MissileScript>(); 8 if (missileScript != null) { 9 var power = missileScript.power; 10 this.Damage(power); 11 } 12 }
最开始我用的是OnTriggerEnter事件。不过OnTriggerEnter无法获取导弹和坦克碰撞的准确位置,也就无法在最准确的位置释放爆炸效果,所以换成了OnCollisionEnter。
关于isTrigger与触发OnTriggerEnter、OnCollisionEnter之间的关系,可参考(http://www.cnblogs.com/infly123/p/3920393.html),本文不再详细说明。
发射导弹
导弹也要做成预制体。
我的设定是:鼠标左键按下时,在摄像机正下方距地面一定高度处发射导弹,导弹速度方向要指向点击到的三维场景中的坐标。这就要求从屏幕坐标转换到世界坐标。我愁了两天,终于找到了办法。
1 // 每帧调用一次,用于更新游戏场景和状态(和物理状态有关的更新应放在FixedUpdate里) 2 void Update () { 3 if (Input.GetMouseButtonDown(0)) { shooting = true; } 4 if (Input.GetMouseButtonUp(0)) { shooting = false; } 5 6 if (shooting) { 7 elapsedInterval += Time.deltaTime; 8 if (elapsedInterval >= shootInterval) { 9 elapsedInterval = 0; 10 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);//从摄像机发出到点击坐标的射线 11 RaycastHit hitInfo; 12 if (Physics.Raycast(ray, out hitInfo)) { 13 /* 14 Debug.DrawLine(ray.origin, hitInfo.point);//划出射线,只有在scene视图中才能看到 15 GameObject gameObj = hitInfo.collider.gameObject; 16 Debug.Log(string.Format("Clicked object's name: {0}", gameObj.name)); 17 */ 18 Transform missile = Instantiate(missilePrefab) as Transform; 19 Vector3 position = Camera.main.transform.position; 20 missile.position = new Vector3(position.x, position.y * 2 / 3, position.z); 21 Vector3 dirPos = (hitInfo.point - missile.position); 22 dirPos.Normalize(); 23 missile.gameObject.rigidbody.velocity = dirPos * 150; 24 SoundEffectHelper.Instance.MakePlayerShotSound(); 25 } 26 } 27 } 28 }
这时你会发现,导弹虽然按照要求的方向走了,但是全部是像螃蟹一样横着飞过去的。这不科学。所以要把导弹的旋转方向调整到飞行方向。这个问题我又琢磨了一天,找到了办法。
1 static readonly Vector3 missileInitialRotation = new Vector3(-1, 0, 0); 2 // 每帧调用一次,用于更新游戏场景和状态(和物理状态有关的更新应放在FixedUpdate里) 3 void Update () { 4 if (Input.GetMouseButtonDown(0)) { shooting = true; } 5 if (Input.GetMouseButtonUp(0)) { shooting = false; } 6 7 if (shooting) { 8 elapsedInterval += Time.deltaTime; 9 if (elapsedInterval >= shootInterval) { 10 elapsedInterval = 0; 11 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);//从摄像机发出到点击坐标的射线 12 RaycastHit hitInfo; 13 if (Physics.Raycast(ray, out hitInfo)) { 14 /* 15 Debug.DrawLine(ray.origin, hitInfo.point);//划出射线,只有在scene视图中才能看到 16 GameObject gameObj = hitInfo.collider.gameObject; 17 Debug.Log(string.Format("Clicked object's name: {0}", gameObj.name)); 18 */ 19 Transform missile = Instantiate(missilePrefab) as Transform; 20 Vector3 position = Camera.main.transform.position; 21 missile.position = new Vector3(position.x, position.y * 2 / 3, position.z); 22 Vector3 dirPos = (hitInfo.point - missile.position); 23 dirPos.Normalize(); 24 missile.rotation = Quaternion.FromToRotation( //从螃蟹式到科学式,需要这样的旋转。 25 missileInitialRotation, //螃蟹式的旋转向量 26 dirPos); //科学式的旋转向量 27 missile.gameObject.rigidbody.velocity = dirPos * 150; 28 SoundEffectHelper.Instance.MakePlayerShotSound(); 29 } 30 } 31 } 32 }
目前的缺点
如你所见,有的坦克由于前后撞击加上地形起伏,竟然飞了起来。我已经用代码和物理属性调整过,但还是没有完全消除这种情况。
导弹尾焰和爆炸效果还不是很理想。
没有开始、存档、选项等菜单,没有我方HP、关卡、敌方剩余坦克数等信息。
敌方坦克还不会开炮。(欺负人。。。)
敌方坦克只知道向右(Z轴正方向)走,没有一点AI。
导弹只能攻击命中的坦克,对附近的坦克没有波及伤害。
总结
有了Unity3D,做游戏涉及的很多算法都不需要自己写了。Unity3D对提高生产效率的确有非常大的帮助。
需要源代码和发布的Windows版、网页版程序的同学麻烦支付100元并留下你的邮箱~
微信扫码,自愿捐赠。天涯同道,共谱新篇。
微信捐赠不显示捐赠者个人信息,如需要,请注明联系方式。 |