zoukankan      html  css  js  c++  java
  • [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     }
    MissileScript.cs
     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     }
    TankHealth.cs

    最开始我用的是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     }
    ShootMissile.cs

    这时你会发现,导弹虽然按照要求的方向走了,但是全部是像螃蟹一样横着飞过去的。这不科学。所以要把导弹的旋转方向调整到飞行方向。这个问题我又琢磨了一天,找到了办法。

     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     }
    ShootMissile.cs

    目前的缺点

    如你所见,有的坦克由于前后撞击加上地形起伏,竟然飞了起来。我已经用代码和物理属性调整过,但还是没有完全消除这种情况。

    导弹尾焰和爆炸效果还不是很理想。

    没有开始、存档、选项等菜单,没有我方HP、关卡、敌方剩余坦克数等信息。

    敌方坦克还不会开炮。(欺负人。。。)

    敌方坦克只知道向右(Z轴正方向)走,没有一点AI。

    导弹只能攻击命中的坦克,对附近的坦克没有波及伤害。

    总结

    有了Unity3D,做游戏涉及的很多算法都不需要自己写了。Unity3D对提高生产效率的确有非常大的帮助。

    需要源代码和发布的Windows版、网页版程序的同学麻烦支付100元并留下你的邮箱~

  • 相关阅读:
    5 Things Every Manager Should Know about Microsoft SharePoint 关于微软SharePoint每个经理应该知道的五件事
    Microsoft SharePoint 2010, is it a true Document Management System? 微软SharePoint 2010,它是真正的文档管理系统吗?
    You think you use SharePoint but you really don't 你认为你使用了SharePoint,但是实际上不是
    Introducing Document Management in SharePoint 2010 介绍SharePoint 2010中的文档管理
    Creating Your Own Document Management System With SharePoint 使用SharePoint创建你自己的文档管理系统
    MVP模式介绍
    权重初始化的选择
    机器学习中线性模型和非线性的区别
    神经网络激励函数的作用是什么
    深度学习中,交叉熵损失函数为什么优于均方差损失函数
  • 原文地址:https://www.cnblogs.com/bitzhuwei/p/unity3d-tank-sniper.html
Copyright © 2011-2022 走看看