赞
踩
讲解实例:3D射击游戏
注:今天所学的知识是重中之重,是Unity的基础,也是核心,掌握了本章内容,在自行设计一些玩法,在简洁的Unity框架下,理论上编写一个小游戏是很简单的,因为Unity中脚本的编写几乎都要用到今天所学的内容,万变不离其宗。让我们开始今天的学习吧。
用Unity创建游戏是由一个或多个场景(Scene)组成的,打开Unity会默认创建一个场景。
Tip:
Tip:
Tip:
Tip:
一个脚本可以挂载到多个物体,一个物体也可以挂载多个脚本。
Tip:
什么是物体:
简单来讲,就是这些东西。
什么是组件:
简单来讲,就是这些东西。
什么是对象:
简单来讲,对象是相对而言的,一个组件所挂载的物体就是对象。
一个物体的组件也可以是对象。
组件已挂载到物体上:
- using UnityEngine;
-
- public class Test : MonoBehaviour
- {
- SphereCollider collider;
-
- void Start()
- {
- //获取到组件后,将它的引用保存在cllider字段中,方便下次使用
- collider = gameObject.GetComponent<SphereCollider>();
- }
- }
- using UnityEngine;
-
- public class Test : MonoBehaviour
- {
- SphereCollider collider;
-
- void Start()
- {
- //获取到组件后,将它的引用保存在cllider字段中,方便下次使用
- collider = gameObject.GetComponent<SphereCollider>();
- //一下每一句的写法均等同于上一句
- collider = this.gameObject.GetComponent<SphereCollider>();//this指脚本对象自身,gameObject是它的属性
- collider = transform.GetComponent<SphereCollider>();//通过transform组件获取其他组件
- collider = transform.GetComponent<MeshRenderer>().GetComponent<SphereCollider>();
- collider = transform.GetComponent<SphereCollider>().GetComponent<SphereCollider>();
- //多此一举的写法,但结果不错
- }
- }
- using UnityEngine;
-
- public class Test : MonoBehaviour
- {
- SphereCollider collider;
-
- void Start()
- {
- //获取到组件后,将它的引用保存在cllider字段中,方便下次使用
- collider = gameObject.GetComponent<SphereCollider>();
- //一下每一句的写法均等同于上一句
- Test[] tests = GetComponents<Test>();
- Debug.Log("共有" + tests.Length + "个Test组件");
- }
- }
- using UnityEngine;
-
- public class TestGameObject : MonoBehaviour
- {
- GameObject objMainCam;
- GameObject objMainLight;
-
- void Start()
- {
- objMainCam = GameObject.Find("Main Camera");
- objMainLight = GameObject.Find("Directional Light");
- Debug.Log("主摄像机:" + objMainCam);
- Debug.Log("主光源:" + objMainLight);
- //将主摄像机放在这个物体后方一米处
- objMainCam.transform.position = transform.position - transform.forward;
- }
- }
Tip:
也可以通过禁用摄像机进行尝试。
- //查找第一个标签为Player的物体
- GameObject player = GameObject.FindGameObjectWithTag("Player");
- //查找所有标签为Monster的物体,注意返回值是一个数组,结果可以是0个或多个
- GameObject[] monsters = GameObject.FindGameObjectsWithTag("Monster");
- //以上两个方法名称的区别是两者差了一个“s”,也就是说Monster是多个物体的标签
在编译器中修改标签
在脚本中修改标签:
- //获得某个Player物体
- GameObject p = GameObject.FindGameObjectWithTag("Player");
- //将它的标签设置为Cube
- p.tag = "Cube";
- //判断player的标签是不是Cube
- if(p.CompareTag("Cube"))
- {
- Debug.Log("yes");
- }
- //上面得CompareTag用法等价于player.tag == "Cube",推荐使用CompareTag
- using UnityEngine;
-
- public class TestGetTransform : MonoBehaviour
- {
- public GameObject other;
- void Start()
- {
- if(other!=null)
- {
- Debug.Log("other 物体名称为" + other.name);
- }
- else
- {
- Debug.Log("未指定other物体");
- }
- }
- }
- using UnityEngine;
-
- public class TestGetTransform : MonoBehaviour
- {
- public GameObject other;
-
- public TestGetTransform otherTrans;
-
- public MeshFilter otherMesh;
-
- public Rigidbody otherRigid;
-
- void Start()
- {
- //任意使用前面定义的变量
- }
- }
以上代码会改变Inspector中的信息:
(1)场景物体与预制体的关联
(2)编辑预制体
Tip:
Tip:
- using UnityEngine;
-
- public class TestInstantiate : MonoBehaviour
- {
- public GameObject prefab;
-
- void Start()
- {
- //在场景根结点创建物体
- GameObject objA = Instantiate(prefab, null);
- //创建一个物体,作为当前脚本所在物体的子物体
- GameObject objB = Instantiate(prefab, transform);
- //创建一个物体,指定他的位置和朝向
- GameObject objC = Instantiate(prefab, new Vector3(3, 0, 3), Quaternion.identity);
- }
- }
- using UnityEngine;
-
- public class TestInstantiate : MonoBehaviour
- {
- public GameObject prefab;
-
- void Start()
- {
- //创建十个物体围成环
- for(int i = 0; i<10; i++)
- {
- Vector3 pos = new Vector3(Mathf.Cos(i * (2 * Mathf.PI) / 10), 0, Mathf.Sin(i * (2 * Mathf.PI) / 10));
- pos *= 5;
- Instantiate(prefab, pos, Quaternion.identity);
- }
- }
- }
- using UnityEngine;
-
- public class TestInstantiate : MonoBehaviour
- {
- public GameObject prefab;
-
- void Start()
- {
- GameObject go = GameObject.Find("Cube");
- go.AddComponent<Rigidbody>();
- }
- }
执行前:
执行后:因为加了物理组件受重力影响,物体已经开始下落了。
- using UnityEngine;
-
- public class TestDestrory : MonoBehaviour
- {
- public GameObject prefab;
- void Start()
- {
- //创建20个物体围城环形
- for(int i = 0; i < 20; i++)
- {
- Vector3 pos = new Vector3(Mathf.Cos(i * (2 * Mathf.PI) / 20), 0, Mathf.Sin(i * (2 * Mathf.PI) / 20));
- pos *= 5;
- Instantiate(prefab, pos, Quaternion.identity);
- }
- }
- void Update()
- {
- if(Input.GetKeyDown(KeyCode.D))
- {
- GameObject cube = GameObject.Find("Cube(Clone)");
- Destroy(cube);
- }
- }
- }
- void Update()
- {
- if(Input.GetKeyDown(KeyCode.D))
- {
- GameObject cube = GameObject.Find("Cube(Clone)");
- Destroy(cube);
- cube.AddComponent<Rigidbody>();
- }
- }
- using UnityEngine;
-
- public class TestInvoke : MonoBehaviour
- {
- public GameObject prefab;
- int counter = 0;
- void Start()
- {
- Invoke("CreatePrefab", 0.5f);
- }
- void CreatePrefab()
- {
- Vector3 pos = new Vector3(Mathf.Cos(counter * (2 * Mathf.PI) / 10), 0, Mathf.Sin(counter * (2 * Mathf.PI) / 10));
- pos *= 5;
- Instantiate(prefab, pos, Quaternion.identity);
- counter++;
- if(counter<10)
- {
- Invoke("CreatePrefab", 0.5f);
- }
- }
- }
执行结果:
- using UnityEngine;
-
- public class TestDestroy : MonoBehaviour
- {
- public GameObject prefab;
-
- void Update()
- {
- if(Input.GetKeyDown(KeyCode.D))
- {
- GameObject cube = GameObject.Find("Cube(Clone)");
- //Destroy(cube);
- //将上一句改为延迟0.8秒
- Destroy(cube, 0.8f);
- cube.AddComponent<Rigidbody>();
- }
- }
- }
执行结果:
Tip:
- using UnityEngine;
-
- public class FollowCam : MonoBehaviour
- {
- //追踪的目标,在编辑器里指定
- public Transform followTarget;
- Vector3 offset;
- void Start()
- {
- //算出从目标到摄像机的向量,作为摄像机的偏移量
- offset = transform.position - followTarget.position;
- }
- void LateUpdate()
- {
- //每帧更新摄像机的位置
- transform.position = followTarget.position + offset;
- }
- }
在这里拖拽指定追踪目标即可。
Tip:
也可以在Vector3前面加上public这样就可以在inspector里面自由调试了。
Tip:
- using UnityEngine;
-
- public class TestCoroutine : MonoBehaviour
- {
- void Start()
- {
- //开启一个协程,协程函数为Time
- StartCoroutine(Timer());
- }
-
- //协程函数
- IEnumerator Timer()
- {
- //不断循环执行,但是并不会导致死循环
- while(true)
- {
- //打印4个汉字
- Debug.Log("测试协程");
- //等待一秒
- yield return new WaitForSeconds(1);
- //打印当前游戏经历的时间
- Debug.Log(Time.time);
- //在等待1秒
- yield return new WaitForSeconds(1);
- }
- }
- }
接下来做一个简易的俯视角度的射击游戏。
先搭建场景,在创建主角。
(4)创建脚本Player以控制主角移动。
- using UnityEngine;
-
- public class Player : MonoBehaviour
- {
- //移动速度
- public float speed = 3;
- //最大血量
- public float maxHp = 20;
- //变量,输入方向用
- Vector3 input;
- //是否死亡
- bool dead = false;
- //当前血量
- float hp;
-
- void Start()
- {
- //初始满血状态
- hp = maxHp;
- }
- void Update()
- {
- //将键盘的横向、纵向输入,保存在input变量中
- input = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
- //未死亡则执行移动逻辑
- if(!dead)
- {
- Move();
- }
- }
- void Move()
- {
- //先归一化输入向量,让输入更直接,同时避免斜向移动时速度超过最大速度
- input = input.normalized;
- transform.position += input * speed * Time.deltaTime;
- //令角色前方与移动方向一致
- if(input.magnitude>0.1f)
- {
- transform.forward = input;
- }
- //以上移动方式没有考虑阻挡,因此使用下面的代码限制移动范围
- Vector3 temp = transform.position;
- const float BORDER = 20;
- if (temp.z > BORDER) { temp.z = BORDER; }
- if (temp.z < -BORDER) { temp.z = -BORDER; }
- if (temp.x > BORDER) { temp.x = BORDER; }
- if (temp.x < -BORDER) { temp.x = -BORDER; }
- transform.position = temp;
- }
- }
执行结果:
由测试结果可知,角色移动的范围确实是|20|
(1)
(2)
摄像机脚本
- using UnityEngine;
-
- public class FollowCam : MonoBehaviour
- {
- //追踪的目标,在编辑器里指定
- public Transform followTarget;
- Vector3 offset;
- void Start()
- {
- //算出从目标到摄像机的向量,作为摄像机的偏移量
- offset = transform.position - followTarget.position;
- }
- void LateUpdate()
- {
- //每帧更新摄像机的位置
- transform.position = followTarget.position + offset;
- }
- }
- using UnityEngine;
-
- public class Weapon : MonoBehaviour
- {
- //子弹的prefab
- public GameObject prefabBullet;
- //几种武器的CD时间长度
- public float pistolFireCD = 0.2f;//手枪
- public float shotgunFireCD = 0.5f;//散弹
- public float rifleCD = 0.1f;//步枪
- //上次开火时间
- float lastFireTime;
- //当前使用哪种武器
- public int curGun { get; private set; }//0 手枪,1 散弹,2 自动步枪
- //开火函数,由角色脚本调用
- //keyDown代表这一帧按下开火键,keyPressed代表开火键正在持续按下
- //这样区分是为了实现手枪和步枪的不同手感
- public void Fire(bool keyDown,bool keyPressed)
- {
- //根据当前武器,调用对应的开火函数
- switch(curGun)
- {
- case 0:
- if(keyDown)
- {
- PistolFire();
- }
- break;
- case 1:
- if(keyDown)
- {
- shotgunFire();
- }
- break;
- case 2:
- if(keyPressed)
- {
- RifleFire();
- }
- break;
- }
- }
- //更换武器
- public int Change()
- {
- curGun += 1;
- if(curGun==3)
- {
- curGun = 0;
- }
- return curGun;
- }
- //手枪射击专用函数
- public void PistolFire()
- {
- if (lastFireTime + pistolFireCD > Time.time)
- {
- return;
- }
- lastFireTime = Time.time;
- GameObject bullet = Instantiate(prefabBullet, null);
- bullet.transform.position = transform.position + transform.forward * 1.0f;
- bullet.transform.forward = transform.forward;
- }
- //自动步枪射击专用函数
- public void RifleFire()
- {
- if (lastFireTime + rifleCD > Time.time)
- {
- return;
- }
- lastFireTime = Time.time;
- GameObject bullet = Instantiate(prefabBullet, null);
- bullet.transform.position = transform.position + transform.forward * 1.0f;
- bullet.transform.forward = transform.forward;
- }
- //散弹枪射击专用函数
- public void shotgunFire()
- {
- if(lastFireTime+shotgunFireCD>Time.time)
- {
- return;
- }
- lastFireTime = Time.time;
- //创建5颗子弹,间隔10度,扇形分布
- for(int i = -2; i <= 2; i++)
- {
- GameObject bullet = Instantiate(prefabBullet, null);
- Vector3 dir = Quaternion.Euler(0, i * 10, 0) * transform.forward;
-
- bullet.transform.position = transform.position + dir * 1.0f;
- bullet.transform.forward = dir;
- //散弹枪的子弹射击距离很短,所以要修改子弹生命周期
- Bullet b = bullet.GetComponent<Bullet>();
- b.lifeTime = 0.3f;
- }
- }
- }
(1)
(2)
(3)
(4)
- using UnityEngine;
-
- public class Bullet : MonoBehaviour
- {
- //子弹飞行速度
- public float speed = 10.0f;
- //子弹生命周期(几秒后消失)
- public float lifeTime = 2;
- //子弹"出生"的时间
- float startTime;//上升空间小了就可以享受了
- void Start()
- {
- startTime = Time.time;
- }
- void Update()
- {
- //子弹移动
- transform.position += speed * transform.forward * Time.deltaTime;
- //超过一定时间销毁自身
- if(startTime+lifeTime<Time.time)
- {
- Destroy(gameObject);
- }
- }
- //当子弹碰到其他物体时触发
- private void OntriggerEnter(Collider other)
- {
- //稍后补充碰撞逻辑
- }
- }
(5)
- Weapon weapon;
-
- void Start()
- {
- //初始满血状态
- hp = maxHp;
- weapon = GetComponent<Weapon>();
- }
- void Update()
- {
- //将键盘的横向、纵向输入,保存在input变量中
- input = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
- Debug.Log(input);
-
- bool fireKeyDown = Input.GetKeyDown(KeyCode.J);
- bool fireKeyPressed = Input.GetKeyDown(KeyCode.J);
- bool changeWeapon = Input.GetKeyDown(KeyCode.Q);
-
- //未死亡则执行移动逻辑
- if (!dead)
- {
- Move();
-
- weapon.Fire(fireKeyDown, fireKeyPressed);
-
- if(changeWeapon)
- {
- ChangeWeapon();
- }
- }
- }
- private void ChangeWeapon()
- {
- int w = weapon.Change();
- }
我做的是这样的:
为敌人编写脚本:
- using UnityEngine;
-
- public class Enemy : MonoBehaviour
- {
- //用于制作死亡效果的预制体,暂时不用
- public GameObject prefabBoomEffect;
-
- public float speed = 2;
- public float fireTime = 0.1f;
- public float maxHp = 1;
-
- Vector3 input;
-
- Transform player;
- float hp;
- bool dead = false;
-
- Weapon weapon;
-
- void Start()
- {
- //根据Tag查找玩家物体
- player = GameObject.FindGameObjectWithTag("Player").transform;
- weapon = GetComponent<Weapon>();
- }
- void Update()
- {
- Move();
- Fire();
- }
- void Move()
- {
- //玩家的input是从键盘输入而来,而敌人的input是始终指向玩家方向
- input = player.position - transform.position;
- input = input.normalized;
- transform.position += input * speed * Time.deltaTime;
- if(input.magnitude>0.1f)
- {
- transform.forward = input;
- }
- }
- void Fire()
- {
- //一直开枪,开枪的频率可以通过武器启动控制
- weapon.Fire(true, true);
- }
- private void OnTriggerEnter(Collider other)
- {
- //稍后实现
- }
-
- }
- //当子弹碰到其他物体时触发
- private void OntriggerEnter(Collider other)
- {
- //如果子弹的Tag与被碰撞的物体的Tag相同,则不算打中
- //这是为了防止同类子弹相互碰撞抵消
- if(CompareTag(other.tag))
- {
- return;
- }
- Destroy(gameObject);
- private void OnTriggerEnter(Collider other)
- {
- if(other.CompareTag("EnemyBullet"))
- {
- if (hp <= 0) { return; }
- hp--;
- if (hp <= 0) { dead = true; }
- }
- }
- private void OnTriggerEnter(Collider other)
- {
- if(other.CompareTag("PlayerBullet"))
- {
- Destroy(other.gameObject);
- hp--;
- if(hp<=0)
- {
- dead = true;
- //这里可以添加死亡特效
- // Instantiate(prefabBoomEffect,transform.position,transform.rotation);
- Destroy(gameObject);
- }
- }
- }
- using UnityEngine;
- using System.Collections.Generic;
-
- public class BoomEffect : MonoBehaviour
- {
- List<Transform> objs = new List<Transform>();
-
- const int N = 15;
-
- void Start()
- {
- for(int i=0;i<N; i++)
- {
- GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
- obj.transform.parent = transform;
- obj.transform.localPosition = new Vector3(Mathf.Cos(i * 2 * Mathf.PI / N), 0, Mathf.Sin(i * 2 * Mathf.PI / N));
- obj.transform.forward = obj.transform.position - transform.position;
- objs.Add(obj.transform);
- }
- }
- void Update()
- {
- foreach(Transform trans in objs)
- {
- trans.Translate(0, 0, 10 * Time.deltaTime);
- trans.localScale *= 0.9f;
- if(trans.localScale.x<=0.05f)
- {
- Destroy(gameObject);
- }
- }
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。