当前位置:   article > 正文

Unity 3D脚本编程与游戏开发(1.8)

Unity 3D脚本编程与游戏开发(1.8)
2.6.4 调整摄像机

        先将摄像机调整到合适位置。将主⾓的位置参数中的X、Z设置为0,把摄像机置于主⾓后上⽅、斜向下45°即可。摄像机位置参数如图2-39所⽰。
        摄像机⽬前还是在固定位置,应改为随主⾓移动⽽平移。将摄像机直接作为主⾓的⼦物体也是可⾏的,但在第2.4.1⼩节已经给出了通⽤的跟随式摄像机实现⽅法,因此在这⾥直接应⽤即可。
        将脚本挂载到主摄像机上以后,要注意在编辑器中指定Target对象。指定⽅法是将Hierarchy窗⼝中的Player物体拖曳到脚本组件的Target字段上,如图2-40所⽰。

        最后测试游戏,会发现摄像机跟随玩家移动。在商业级游戏开发中,会让摄像机的移动更平滑,⽽这只需要在FollowCam脚本中做⼀些修改即可。

2.6.5 实现武器系统和⼦弹

        实现玩家的移动不算困难,实现武器系统才是本游戏的特⾊和开发重点。为了充分发挥组件式编程的优点,要把武器系统写成⼀个独⽴的组件Weapon,⽆论主⾓还是敌⼈,都可以调⽤这个武器组件。这会让逻辑的实现更严谨,同时也具有充分的灵活性。

  1. using UnityEngine;
  2. public class Weapon : MonoBehaviour
  3. {
  4. // ⼦弹的prefab
  5. public GameObject prefabBullet;
  6. // ⼏种武器的CD时间⻓度
  7. public float pistolFireCD = 0.2f;
  8. public float shotgunFireCD = 0.5f;
  9. public float rifleCD = 0.1f;
  10. // 上次开⽕时间
  11. float lastFireTime;
  12. // 当前使⽤哪种武器
  13. public int curGun { get; private set; } // 0 ⼿枪,1 霰
  14. 弹枪,2 ⾃动步枪
  15. // 开⽕函数,由⾓⾊脚本调⽤
  16. // keyDown代表这⼀帧按下开⽕键,keyPressed 代表开⽕键正在持续按下
  17. // 这样区分是为了实现⼿枪和⾃动步枪的不同⼿感
  18. public void Fire(bool keyDown, bool keyPressed)
  19. {
  20. // 根据当前武器,调⽤对应的开⽕函数
  21. switch (curGun)
  22. {
  23. case 0:
  24. if (keyDown)
  25. {
  26. pistolFire();
  27. }
  28. break;
  29. case 1:
  30. if (keyDown){
  31. shotgunFire();
  32. }
  33. break;
  34. case 2:
  35. if (keyPressed)
  36. {
  37. rifleFire();
  38. }
  39. break;
  40. }
  41. }
  42. // 更换武器
  43. public int Change()
  44. {
  45. curGun += 1;
  46. if (curGun == 3)
  47. {
  48. curGun = 0;
  49. }
  50. return curGun;
  51. }
  52. // ⼿枪射击专⽤函数
  53. public void PistolFire()
  54. {
  55. if (lastFireTime + pistolFireCD > Time.time)
  56. {
  57. return;
  58. }
  59. lastFireTime = Time.time;
  60. GameObject bullet = Instantiate(prefabBullet, null);
  61. bullet.transform.position = transform.position +
  62. transform.forward * 1.0f;
  63. bullet.transform.forward = transform.forward;
  64. }
  65. // ⾃动步枪射击专⽤函数
  66. public void RifleFire()
  67. {
  68. if (lastFireTime + rifleCD > Time.time)
  69. {
  70. return;
  71. }
  72. lastFireTime = Time.time;
  73. GameObject bullet = Instantiate(prefabBullet, null);
  74. bullet.transform.position = transform.position +transform.forward * 1.0f;
  75. bullet.transform.forward = transform.forward;
  76. }
  77. // 霰弹枪射击专⽤函数
  78. public void shotgunFire()
  79. {
  80. if (lastFireTime + shotgunFireCD > Time.time)
  81. {
  82. return;
  83. }
  84. lastFireTime = Time.time;
  85. // 创建5颗⼦弹,相互间隔10°,分布于前⽅扇形区域
  86. for (int i=-2; i<=2; i++)
  87. {
  88. GameObject bullet = Instantiate(prefabBullet,
  89. null);
  90. Vector3 dir = Quaternion.Euler(0, i * 10, 0) *
  91. transform.forward;
  92. bullet.transform.position = transform.position +
  93. dir * 1.0f;
  94. bullet.transform.forward = dir;
  95. // 霰弹枪的⼦弹射击距离很短,因此修改⼦弹的⽣命周期
  96. Bullet b = bullet.GetComponent<Bullet>();
  97. b.lifeTime = 0.3f;
  98. }
  99. }
  100. }

        上⾯直接给出了完整的Weapon代码,其中的霰弹枪射击的代码可能会让初学者很费解,读者可以暂时注释掉ShotgunFire()函数的后半部分。武器实际上是通过不断创建⼦弹的prefab来发射⼦弹的,因此还需要创建⼦弹的预制体,如图2-41所⽰,其步骤如下。
01 创建⼀个球体,⼤⼩缩放为0.3倍,并增添⾦⻩⾊材质。
02 添加Rigidbody组件,勾选Is Kinematic选项。
03 勾选Sphere Collider组件中的Is Trigger选项。
04 新建Bullet脚本,其内容如下,并将其挂载于⼦弹上。
05 将⼦弹拖曳到Project窗⼝中,做成prefab。
 

  1. using UnityEngine;
  2. public class Bullet : MonoBehaviour
  3. {
  4. // ⼦弹⻜⾏速度
  5. public float speed = 10.0f;
  6. // ⼦弹⽣命期(⼏秒之后消失)
  7. public float lifeTime = 2;
  8. // ⼦弹“出⽣”的时间
  9. float startTime;
  10. void Start()
  11. {
  12. startTime = Time.time;
  13. }
  14. void Update()
  15. {
  16. // ⼦弹移动
  17. transform.position += speed * transform.forward *
  18. Time.deltaTime;
  19. // 超过⼀定时间销毁⾃⾝
  20. if (startTime + lifeTime < Time.time)
  21. {
  22. Destroy(gameObject);
  23. }
  24. }
  25. // 当⼦弹碰到其他物体时触发
  26. private void OnTriggerEnter(Collider other)
  27. {
  28. // 稍后补充碰撞的逻辑
  29. }

        接下来将武器逻辑与玩家逻辑联系起来,确保将Weapon脚本挂载到玩家物体上,将Bullet脚本挂载到⼦弹物体上,将Bullet的预制体拖曳到Weapon脚本的prefabBullet字段上。
        如果这时测试,会发现缺少按键开⽕的逻辑,因此还需要修改Player脚本。⾸先,给Player脚本添加Weapon组件的引⽤,并在Start()函数中初始化,⽅便稍后使⽤武器。

  1. // 以下是代码⽚段,添加于Player脚本中
  2. Weapon weapon;
  3. void Start()
  4. {
  5. hp = maxHp;
  6. weapon = GetComponent<Weapon>();
  7. }

Update()函数修改较多,因为要增加按键逻辑。其中J键⽤于开⽕,Q键⽤于切换3种武器。修改后的Update()函数如下:
 

  1. void Update()
  2. {
  3. input = new Vector3(Input.GetAxis("Horizontal"), 0,
  4. Input.GetAxis("Vertical"));
  5. Debug.Log(input);
  6. bool fireKeyDown = Input.GetKeyDown(KeyCode.J);
  7. bool fireKeyPressed = Input.GetKey(KeyCode.J);
  8. bool changeWeapon = Input.GetKeyDown(KeyCode.Q);
  9. if (!dead)
  10. {
  11. Move();
  12. weapon.Fire(fireKeyDown, fireKeyPressed);
  13. if (changeWeapon)
  14. {
  15. ChangeWeapon();
  16. }
  17. }
  18. }
  19. 新增的更换武器的ChangeWeapon()函数内容如下。private void ChangeWeapon()
  20. {
  21. int w = weapon.Change();
  22. }

        ⾄此,武器逻辑已经基本完成,可以运⾏游戏进⾏测试,重点测试主⾓的移动、射击等功能,如图2-42所⽰。射击与切换武器的键分别是J和Q。也可以考虑加快主⾓的移动速度和⼦弹⻜⾏速度。

2.6.6 实现敌⼈⾓⾊

        由于敌⼈要随时找到并攻击玩家⾓⾊,因此敌⼈需要玩家⾓⾊的引⽤。先将玩家物体的标签设置为Player,然后创建敌⼈⾓⾊。敌⼈⾓⾊与玩家⾓⾊造型基本⼀致,只是将⽩⾊换成深蓝⾊,如图2-43所⽰。当然读者也可以⾃⼰发挥,做出更有趣的外形。

  1. 为敌⼈⾓⾊编写脚本Enemy,其内容如下。
  2. using UnityEngine;
  3. public class Enemy : MonoBehaviour
  4. {
  5. // ⽤于制作死亡效果的预制体,暂时不⽤
  6. public GameObject prefabBoomEffect;
  7. public float speed = 2;
  8. public float fireTime = 0.1f;
  9. public float maxHp = 1;
  10. Vector3 input;
  11. Transform player;
  12. float hp;
  13. bool dead = false;
  14. Weapon weapon;void Start()
  15. {
  16. // 根据Tag查找玩家物体
  17. player =
  18. GameObject.FindGameObjectWithTag("Player").transform;
  19. weapon = GetComponent<Weapon>();
  20. }
  21. void Update()
  22. {
  23. Move();
  24. Fire();
  25. }
  26. void Move()
  27. {
  28. // 玩家的input是从键盘输⼊⽽来,⽽敌⼈的input则是始终指向玩家的
  29. ⽅向
  30. input = player.position - transform.position;
  31. input = input.normalized;
  32. transform.position += input * speed * Time.deltaTime;
  33. if (input.magnitude > 0.1f)
  34. {
  35. transform.forward = input;
  36. }
  37. }
  38. void Fire()
  39. {
  40. // ⼀直开枪,开枪的频率可以通过武器启动控制
  41. weapon.Fire(true, true);
  42. }
  43. private void OnTriggerEnter(Collider other)
  44. {
  45. // 稍后实现
  46. }
  47. }

        可以看出,敌⼈⾓⾊的代码完全可以仿照玩家⾓⾊编写,且随着对脚本的逐渐熟悉,某些复杂的功能的代码,如AI,会显得越来越简单。以上代码还复⽤了之前写好的Weapon组件,可⻅之前对武器系统的组件化是很有⽤的。
        敌⼈⾓⾊脚本编写完成后,将Enemy脚本和Weapon脚本都挂载到敌⼈⾓⾊⾝上,那么敌⼈⾓⾊的实现就完成了。可以将敌⼈⾓⾊制作成预制体,⽅便之后创建多个敌⼈以进⾏测试。接下来创建⼀个敌⼈⾓⾊进⾏测试,会发现敌⼈⾓⾊正在追击主⾓并不断开⽕,如图2-44所⽰。        最后,可以在编辑器中调整Player脚本组件中的主⾓移动速度、Weapon组件中的武器冷却时间,以及Enemy脚本组件中的敌⼈移动速度。由于Weapon脚本已经分别挂载到了玩家和敌⼈⾝上,因此玩家⾓⾊和每个敌⼈⾓⾊的武器发射频率都可以单独调整,互不影响。

2.6.7 ⼦弹击中的逻辑

        碰撞事件是相对的,因此⼦弹碰撞的逻辑可以写在Bullet脚本、Player脚本或Enemy脚本中。在制作时最好单独考虑碰撞问题,然后统⼀编写。要制作的效果如下。
①玩家⾓⾊的⼦弹击中敌⼈⾓⾊,会让敌⼈⾓⾊掉⾎。
②敌⼈⾓⾊的⼦弹击中玩家⾓⾊,会让玩家⾓⾊掉⾎。
③玩家⾓⾊的⼦弹不会击中玩家⾓⾊,敌⼈⾓⾊的⼦弹也不会击中敌⼈⾓⾊。
④玩家⾓⾊的⼦弹与敌⼈⾓⾊的⼦弹可以相互抵消,但是同类⼦弹不能抵消。这⼀设计可以根据读者的偏好添加或取消。从以上分析可以看出,“敌⼈⾓⾊的⼦弹”与“玩家⾓⾊的⼦弹”是截然不同的,最好⽤某种机制区分出⼆者。这⾥编者采⽤的⽅法是把这两种⼦弹做成两个不同的预制体,⼀个命名为PlayerBullet,另⼀个命名为EnemyBullet。然后利⽤标签(Tag)做出区分,前者的Tag是PlayerBullet,后者的Tag是EnemyBullet。具体操作是,⾸先复制之前做好的⼦弹的预制体,然后分别命名为PlayerBullet和EnemyBullet,最后双击PlayerBullet,将其Tag设为PlayerBullet,并为prefab选中标签,如图2-45所⽰。同理给EnemyBullet设置Tag为EnemyBullet。

        做好两种⼦弹以后,注意要给玩家⾓⾊⾝上的Weapon组件和敌⼈⾓⾊⾝上的Weapon组件分别设置新的⼦弹预制体,这样就可以让两种⼦弹既使⽤同样的脚本,⼜有了明确区分。接着添加脚本碰撞逻辑,修改Bullet脚本,添加如下触发逻辑。

  1. // 当⼦弹碰到其他物体时触发
  2. private void OnTriggerEnter(Collider other)
  3. {
  4. // 如果⼦弹的Tag与被碰撞的物体的Tag相同,则不算打中
  5. // 这是为了防⽌同类⼦弹互相碰撞抵消
  6. if (CompareTag(other.tag))
  7. {
  8. return;
  9. }
  10. Destroy(gameObject);
  11. }

以上代码的作⽤是让⼦弹碰到任何其他物体后消失,包括碰到对⽅⼦弹也会消失。

  1. 接下来添加玩家被⼦弹击中的逻辑,修改Player脚本的
  2. OnTriggerEnter函数如下。
  3. private void OnTriggerEnter(Collider other)
  4. {
  5. if (other.CompareTag("EnemyBullet"))
  6. {
  7. if (hp <= 0) { return; }
  8. hp--;
  9. if (hp <= 0) { dead = true; }
  10. }
  11. }
  12. 最后是敌⼈被击中的逻辑,修改Enemy脚本的OnTriggerEnter函数如
  13. 下。
  14. private void OnTriggerEnter(Collider other)
  15. {
  16. if (other.CompareTag("PlayerBullet"))
  17. {
  18. Destroy(other.gameObject);
  19. hp--;
  20. if (hp <= 0)
  21. {
  22. dead = true;
  23. // 这⾥可以添加死亡特效
  24. //Instantiate(prefabBoomEffect,
  25. transform.position, transform.rotation);
  26. Destroy(gameObject);
  27. }
  28. }
  29. }

        通过以上修改,可以发现敌⼈的⼦弹已经能够击中玩家,且击中10次以后玩家会死亡,死亡的表现是不能再移动或射击了。如果有⼦弹⽆法碰撞的问题,则需要检查和确认。
        01 敌⼈和玩家的⼦弹都必须有碰撞体和刚体,在Rigidbody组件中已经勾选了Is Kinematic选项,且碰撞体组件中已经勾选了Is Trigger选项。
        02与触发相关的函数已正确编写,⼦弹Tag已经正确设置。
        03 如果读者设置的⼦弹速度很快,建议将⼦弹Rigidbody组件的Collision Detection选项设置为Continuous Dynamic(连续动态检测),如图2-46所⽰。这个选项可以确保⾼速⻜⾏的物体不会错过碰撞事件。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/105961?site
推荐阅读
相关标签
  

闽ICP备14008679号