当前位置:   article > 正文

第二天 Unity基本概念及脚本编程_unity编程

unity编程

讲解实例:3D射击游戏

 

注:今天所学的知识是重中之重,是Unity的基础,也是核心,掌握了本章内容,在自行设计一些玩法,在简洁的Unity框架下,理论上编写一个小游戏是很简单的,因为Unity中脚本的编写几乎都要用到今天所学的内容,万变不离其宗。让我们开始今天的学习吧。

一、Unity基本概念

用Unity创建游戏是由一个或多个场景(Scene)组成的,打开Unity会默认创建一个场景。

  

1、游戏物体和组件

(1)空物体

 

 (2) 球体等3D原型物体

Tip:

 

 

 (3)灯光

 

 (4)摄像机

 

2、变换组件

 

(1)位置

 

(2)朝向

(3)缩放比例

(4)父子关系

 

3、“父子关系”详解

(1)使用“父子关系”复用零件

 

(2)父子关系与局部坐标系

 

(3)用父子关系表示角色骨骼

  Tip:

(4)利用父物体统一管理大量物体

4、物体的标签和层

Tip:

 (1)标签(Tag)的简要说明

 (2)层(Layer)的简要说明

 

5、常用组件

 

二、用脚本获取物体和组件

1、物体、组件和对象

Tip:

一个脚本可以挂载到多个物体,一个物体也可以挂载多个脚本。

 

  Tip:

什么是物体:

简单来讲,就是这些东西。

 什么是组件:

简单来讲,就是这些东西。

什么是对象:

简单来讲,对象是相对而言的,一个组件所挂载的物体就是对象。

一个物体的组件也可以是对象。 

2、获取组件的方法

组件已挂载到物体上: 

 

  1. using UnityEngine;
  2. public class Test : MonoBehaviour
  3. {
  4. SphereCollider collider;
  5. void Start()
  6. {
  7. //获取到组件后,将它的引用保存在cllider字段中,方便下次使用
  8. collider = gameObject.GetComponent<SphereCollider>();
  9. }
  10. }

 

  1. using UnityEngine;
  2. public class Test : MonoBehaviour
  3. {
  4. SphereCollider collider;
  5. void Start()
  6. {
  7. //获取到组件后,将它的引用保存在cllider字段中,方便下次使用
  8. collider = gameObject.GetComponent<SphereCollider>();
  9. //一下每一句的写法均等同于上一句
  10. collider = this.gameObject.GetComponent<SphereCollider>();//this指脚本对象自身,gameObject是它的属性
  11. collider = transform.GetComponent<SphereCollider>();//通过transform组件获取其他组件
  12. collider = transform.GetComponent<MeshRenderer>().GetComponent<SphereCollider>();
  13. collider = transform.GetComponent<SphereCollider>().GetComponent<SphereCollider>();
  14. //多此一举的写法,但结果不错
  15. }
  16. }

 

 

  1. using UnityEngine;
  2. public class Test : MonoBehaviour
  3. {
  4. SphereCollider collider;
  5. void Start()
  6. {
  7. //获取到组件后,将它的引用保存在cllider字段中,方便下次使用
  8. collider = gameObject.GetComponent<SphereCollider>();
  9. //一下每一句的写法均等同于上一句
  10. Test[] tests = GetComponents<Test>();
  11. Debug.Log("共有" + tests.Length + "个Test组件");
  12. }
  13. }

3、获取物体的方法

(1)通过名称获取物体

  1. using UnityEngine;
  2. public class TestGameObject : MonoBehaviour
  3. {
  4. GameObject objMainCam;
  5. GameObject objMainLight;
  6. void Start()
  7. {
  8. objMainCam = GameObject.Find("Main Camera");
  9. objMainLight = GameObject.Find("Directional Light");
  10. Debug.Log("主摄像机:" + objMainCam);
  11. Debug.Log("主光源:" + objMainLight);
  12. //将主摄像机放在这个物体后方一米处
  13. objMainCam.transform.position = transform.position - transform.forward;
  14. }
  15. }

 

Tip:

也可以通过禁用摄像机进行尝试。 

(2)通过标签查找物体

  1. //查找第一个标签为Player的物体
  2. GameObject player = GameObject.FindGameObjectWithTag("Player");
  3. //查找所有标签为Monster的物体,注意返回值是一个数组,结果可以是0个或多个
  4. GameObject[] monsters = GameObject.FindGameObjectsWithTag("Monster");
  5. //以上两个方法名称的区别是两者差了一个“s”,也就是说Monster是多个物体的标签

在编译器中修改标签 

 在脚本中修改标签:

  1. //获得某个Player物体
  2. GameObject p = GameObject.FindGameObjectWithTag("Player");
  3. //将它的标签设置为Cube
  4. p.tag = "Cube";
  5. //判断player的标签是不是Cube
  6. if(p.CompareTag("Cube"))
  7. {
  8. Debug.Log("yes");
  9. }
  10. //上面得CompareTag用法等价于player.tag == "Cube",推荐使用CompareTag

4、在物体和组件之间任意遨游

(1)通过“父子关系”获取物体

 

(2)通过父子路径获取物体

(3)其他查找父子物体的方式

(4)一些有用的技巧

5、利用公开变量引用物体和组件

  1. using UnityEngine;
  2. public class TestGetTransform : MonoBehaviour
  3. {
  4. public GameObject other;
  5. void Start()
  6. {
  7. if(other!=null)
  8. {
  9. Debug.Log("other 物体名称为" + other.name);
  10. }
  11. else
  12. {
  13. Debug.Log("未指定other物体");
  14. }
  15. }
  16. }

 

  1. using UnityEngine;
  2. public class TestGetTransform : MonoBehaviour
  3. {
  4. public GameObject other;
  5. public TestGetTransform otherTrans;
  6. public MeshFilter otherMesh;
  7. public Rigidbody otherRigid;
  8. void Start()
  9. {
  10. //任意使用前面定义的变量
  11. }
  12. }

 以上代码会改变Inspector中的信息:

三、用脚本创建物体

1、预制体

(1)场景物体与预制体的关联

(2)编辑预制体

2、创建物体

Tip:

 

Tip:

  1. using UnityEngine;
  2. public class TestInstantiate : MonoBehaviour
  3. {
  4. public GameObject prefab;
  5. void Start()
  6. {
  7. //在场景根结点创建物体
  8. GameObject objA = Instantiate(prefab, null);
  9. //创建一个物体,作为当前脚本所在物体的子物体
  10. GameObject objB = Instantiate(prefab, transform);
  11. //创建一个物体,指定他的位置和朝向
  12. GameObject objC = Instantiate(prefab, new Vector3(3, 0, 3), Quaternion.identity);
  13. }
  14. }

  

  1. using UnityEngine;
  2. public class TestInstantiate : MonoBehaviour
  3. {
  4. public GameObject prefab;
  5. void Start()
  6. {
  7. //创建十个物体围成环
  8. for(int i = 0; i<10; i++)
  9. {
  10. Vector3 pos = new Vector3(Mathf.Cos(i * (2 * Mathf.PI) / 10), 0, Mathf.Sin(i * (2 * Mathf.PI) / 10));
  11. pos *= 5;
  12. Instantiate(prefab, pos, Quaternion.identity);
  13. }
  14. }
  15. }

3、创建组件

  1. using UnityEngine;
  2. public class TestInstantiate : MonoBehaviour
  3. {
  4. public GameObject prefab;
  5. void Start()
  6. {
  7. GameObject go = GameObject.Find("Cube");
  8. go.AddComponent<Rigidbody>();
  9. }
  10. }

执行前:

执行后:因为加了物理组件受重力影响,物体已经开始下落了。

4、销毁物体和组件

  1. using UnityEngine;
  2. public class TestDestrory : MonoBehaviour
  3. {
  4. public GameObject prefab;
  5. void Start()
  6. {
  7. //创建20个物体围城环形
  8. for(int i = 0; i < 20; i++)
  9. {
  10. Vector3 pos = new Vector3(Mathf.Cos(i * (2 * Mathf.PI) / 20), 0, Mathf.Sin(i * (2 * Mathf.PI) / 20));
  11. pos *= 5;
  12. Instantiate(prefab, pos, Quaternion.identity);
  13. }
  14. }
  15. void Update()
  16. {
  17. if(Input.GetKeyDown(KeyCode.D))
  18. {
  19. GameObject cube = GameObject.Find("Cube(Clone)");
  20. Destroy(cube);
  21. }
  22. }
  23. }

 

  1. void Update()
  2. {
  3. if(Input.GetKeyDown(KeyCode.D))
  4. {
  5. GameObject cube = GameObject.Find("Cube(Clone)");
  6. Destroy(cube);
  7. cube.AddComponent<Rigidbody>();
  8. }
  9. }

5、定时创建和销毁物体

  1. using UnityEngine;
  2. public class TestInvoke : MonoBehaviour
  3. {
  4. public GameObject prefab;
  5. int counter = 0;
  6. void Start()
  7. {
  8. Invoke("CreatePrefab", 0.5f);
  9. }
  10. void CreatePrefab()
  11. {
  12. Vector3 pos = new Vector3(Mathf.Cos(counter * (2 * Mathf.PI) / 10), 0, Mathf.Sin(counter * (2 * Mathf.PI) / 10));
  13. pos *= 5;
  14. Instantiate(prefab, pos, Quaternion.identity);
  15. counter++;
  16. if(counter<10)
  17. {
  18. Invoke("CreatePrefab", 0.5f);
  19. }
  20. }
  21. }

执行结果: 

  1. using UnityEngine;
  2. public class TestDestroy : MonoBehaviour
  3. {
  4. public GameObject prefab;
  5. void Update()
  6. {
  7. if(Input.GetKeyDown(KeyCode.D))
  8. {
  9. GameObject cube = GameObject.Find("Cube(Clone)");
  10. //Destroy(cube);
  11. //将上一句改为延迟0.8秒
  12. Destroy(cube, 0.8f);
  13. cube.AddComponent<Rigidbody>();
  14. }
  15. }
  16. }

执行结果:

四、脚本的生命周期

1、理解脚本的生命周期

Tip:

 

 

2、常见的事件方法

 

3、实例:跟随主角的摄像机

  1. using UnityEngine;
  2. public class FollowCam : MonoBehaviour
  3. {
  4. //追踪的目标,在编辑器里指定
  5. public Transform followTarget;
  6. Vector3 offset;
  7. void Start()
  8. {
  9. //算出从目标到摄像机的向量,作为摄像机的偏移量
  10. offset = transform.position - followTarget.position;
  11. }
  12. void LateUpdate()
  13. {
  14. //每帧更新摄像机的位置
  15. transform.position = followTarget.position + offset;
  16. }
  17. }

在这里拖拽指定追踪目标即可。 

Tip:

也可以在Vector3前面加上public这样就可以在inspector里面自由调试了。 

 

4、触发器事件

Tip:

五、协程入门

  1. using UnityEngine;
  2. public class TestCoroutine : MonoBehaviour
  3. {
  4. void Start()
  5. {
  6. //开启一个协程,协程函数为Time
  7. StartCoroutine(Timer());
  8. }
  9. //协程函数
  10. IEnumerator Timer()
  11. {
  12. //不断循环执行,但是并不会导致死循环
  13. while(true)
  14. {
  15. //打印4个汉字
  16. Debug.Log("测试协程");
  17. //等待一秒
  18. yield return new WaitForSeconds(1);
  19. //打印当前游戏经历的时间
  20. Debug.Log(Time.time);
  21. //在等待1秒
  22. yield return new WaitForSeconds(1);
  23. }
  24. }
  25. }

六、实例:3D射击游戏

接下来做一个简易的俯视角度的射击游戏。

1、游戏总体设计

2、游戏的实现要点

3、创建主角

先搭建场景,在创建主角。

(1)先创建一个蓝灰色平面(Plane)作为地板命名Ground,位置归零(Reset),再放大4倍。

(2)创建一个3D胶囊体(Capsule),命名为Player,适当调整位置。

 (3)给主角设置正面,添加一个正方体做脸(脸是主角的子物体),调整大小,并附上红色材质。

 (4)创建脚本Player以控制主角移动。

  1. using UnityEngine;
  2. public class Player : MonoBehaviour
  3. {
  4. //移动速度
  5. public float speed = 3;
  6. //最大血量
  7. public float maxHp = 20;
  8. //变量,输入方向用
  9. Vector3 input;
  10. //是否死亡
  11. bool dead = false;
  12. //当前血量
  13. float hp;
  14. void Start()
  15. {
  16. //初始满血状态
  17. hp = maxHp;
  18. }
  19. void Update()
  20. {
  21. //将键盘的横向、纵向输入,保存在input变量中
  22. input = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
  23. //未死亡则执行移动逻辑
  24. if(!dead)
  25. {
  26. Move();
  27. }
  28. }
  29. void Move()
  30. {
  31. //先归一化输入向量,让输入更直接,同时避免斜向移动时速度超过最大速度
  32. input = input.normalized;
  33. transform.position += input * speed * Time.deltaTime;
  34. //令角色前方与移动方向一致
  35. if(input.magnitude>0.1f)
  36. {
  37. transform.forward = input;
  38. }
  39. //以上移动方式没有考虑阻挡,因此使用下面的代码限制移动范围
  40. Vector3 temp = transform.position;
  41. const float BORDER = 20;
  42. if (temp.z > BORDER) { temp.z = BORDER; }
  43. if (temp.z < -BORDER) { temp.z = -BORDER; }
  44. if (temp.x > BORDER) { temp.x = BORDER; }
  45. if (temp.x < -BORDER) { temp.x = -BORDER; }
  46. transform.position = temp;
  47. }
  48. }

执行结果:

 

由测试结果可知,角色移动的范围确实是|20|

 (5)可以将GetAxis()改为GetButton()或者修改工程面板,减少延时感。

 (1)

(2) 

 

 

4、调整摄像机

 摄像机脚本

  1. using UnityEngine;
  2. public class FollowCam : MonoBehaviour
  3. {
  4. //追踪的目标,在编辑器里指定
  5. public Transform followTarget;
  6. Vector3 offset;
  7. void Start()
  8. {
  9. //算出从目标到摄像机的向量,作为摄像机的偏移量
  10. offset = transform.position - followTarget.position;
  11. }
  12. void LateUpdate()
  13. {
  14. //每帧更新摄像机的位置
  15. transform.position = followTarget.position + offset;
  16. }
  17. }

 

 

5、实现武器系统和子弹

  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 散弹,2 自动步枪
  14. //开火函数,由角色脚本调用
  15. //keyDown代表这一帧按下开火键,keyPressed代表开火键正在持续按下
  16. //这样区分是为了实现手枪和步枪的不同手感
  17. public void Fire(bool keyDown,bool keyPressed)
  18. {
  19. //根据当前武器,调用对应的开火函数
  20. switch(curGun)
  21. {
  22. case 0:
  23. if(keyDown)
  24. {
  25. PistolFire();
  26. }
  27. break;
  28. case 1:
  29. if(keyDown)
  30. {
  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 + transform.forward * 1.0f;
  62. bullet.transform.forward = transform.forward;
  63. }
  64. //自动步枪射击专用函数
  65. public void RifleFire()
  66. {
  67. if (lastFireTime + rifleCD > Time.time)
  68. {
  69. return;
  70. }
  71. lastFireTime = Time.time;
  72. GameObject bullet = Instantiate(prefabBullet, null);
  73. bullet.transform.position = transform.position + transform.forward * 1.0f;
  74. bullet.transform.forward = transform.forward;
  75. }
  76. //散弹枪射击专用函数
  77. public void shotgunFire()
  78. {
  79. if(lastFireTime+shotgunFireCD>Time.time)
  80. {
  81. return;
  82. }
  83. lastFireTime = Time.time;
  84. //创建5颗子弹,间隔10度,扇形分布
  85. for(int i = -2; i <= 2; i++)
  86. {
  87. GameObject bullet = Instantiate(prefabBullet, null);
  88. Vector3 dir = Quaternion.Euler(0, i * 10, 0) * transform.forward;
  89. bullet.transform.position = transform.position + dir * 1.0f;
  90. bullet.transform.forward = dir;
  91. //散弹枪的子弹射击距离很短,所以要修改子弹生命周期
  92. Bullet b = bullet.GetComponent<Bullet>();
  93. b.lifeTime = 0.3f;
  94. }
  95. }
  96. }

 

 

(1) 

(2)

(3)

 (4)

  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 * Time.deltaTime;
  18. //超过一定时间销毁自身
  19. if(startTime+lifeTime<Time.time)
  20. {
  21. Destroy(gameObject);
  22. }
  23. }
  24. //当子弹碰到其他物体时触发
  25. private void OntriggerEnter(Collider other)
  26. {
  27. //稍后补充碰撞逻辑
  28. }
  29. }

(5)

 

 

  1. Weapon weapon;
  2. void Start()
  3. {
  4. //初始满血状态
  5. hp = maxHp;
  6. weapon = GetComponent<Weapon>();
  7. }

  1. void Update()
  2. {
  3. //将键盘的横向、纵向输入,保存在input变量中
  4. input = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
  5. Debug.Log(input);
  6. bool fireKeyDown = Input.GetKeyDown(KeyCode.J);
  7. bool fireKeyPressed = Input.GetKeyDown(KeyCode.J);
  8. bool changeWeapon = Input.GetKeyDown(KeyCode.Q);
  9. //未死亡则执行移动逻辑
  10. if (!dead)
  11. {
  12. Move();
  13. weapon.Fire(fireKeyDown, fireKeyPressed);
  14. if(changeWeapon)
  15. {
  16. ChangeWeapon();
  17. }
  18. }
  19. }

 

  1. private void ChangeWeapon()
  2. {
  3. int w = weapon.Change();
  4. }

 

 

 

6、实现敌人角色

我做的是这样的:

为敌人编写脚本:

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

 

7、子弹击中的逻辑

 

 

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

  1. private void OnTriggerEnter(Collider other)
  2. {
  3. if(other.CompareTag("EnemyBullet"))
  4. {
  5. if (hp <= 0) { return; }
  6. hp--;
  7. if (hp <= 0) { dead = true; }
  8. }
  9. }

 

  1. private void OnTriggerEnter(Collider other)
  2. {
  3. if(other.CompareTag("PlayerBullet"))
  4. {
  5. Destroy(other.gameObject);
  6. hp--;
  7. if(hp<=0)
  8. {
  9. dead = true;
  10. //这里可以添加死亡特效
  11. // Instantiate(prefabBoomEffect,transform.position,transform.rotation);
  12. Destroy(gameObject);
  13. }
  14. }
  15. }

 

8、完善游戏

  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. public class BoomEffect : MonoBehaviour
  4. {
  5. List<Transform> objs = new List<Transform>();
  6. const int N = 15;
  7. void Start()
  8. {
  9. for(int i=0;i<N; i++)
  10. {
  11. GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
  12. obj.transform.parent = transform;
  13. obj.transform.localPosition = new Vector3(Mathf.Cos(i * 2 * Mathf.PI / N), 0, Mathf.Sin(i * 2 * Mathf.PI / N));
  14. obj.transform.forward = obj.transform.position - transform.position;
  15. objs.Add(obj.transform);
  16. }
  17. }
  18. void Update()
  19. {
  20. foreach(Transform trans in objs)
  21. {
  22. trans.Translate(0, 0, 10 * Time.deltaTime);
  23. trans.localScale *= 0.9f;
  24. if(trans.localScale.x<=0.05f)
  25. {
  26. Destroy(gameObject);
  27. }
  28. }
  29. }
  30. }

9、测试和改进

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

闽ICP备14008679号