[Unity完整游戏流程笔记]一、生存射击_void if damaged
目录
前言
准备工作
玩家角色
移动
核心代码
相关知识点
获取键盘输入
增量时间delatime
移动的核心逻辑
相机跟随
核心代码
相关知识点
利用标签在脚本中寻找场景中的其他物体
平滑移动
摄像机的两种投影方法
旋转
核心代码
相关知识点
Ray、RaycastHit、Raycast及其使用
动画控制
射击
核心代码
相关知识点
生成射线
射击检测
生命值
核心代码
敌人角色
AI
核心代码
相关知识点
导航系统
攻击
核心代码
生命值
核心代码
敌人生成
核心代码
UI
打包/结束语
前言
整个游戏完全跟随up主丑萌气质狗的教程完成,感觉算是我在B站看到的最好的那一批unity游戏入门教程了。以下是教程链接【Unity零基础入门教程 】前十个游戏都是垃圾 - 生存射击(2/9)_哔哩哔哩_bilibili
我大概花了一天半的时间完成了整个教程,完成度还不错。
准备工作
新建3D模板(版本2021.3),资源下载,场景添加,角色添加。
参考教程1-4节,不多赘述
玩家角色
移动
核心代码
//获取键盘输入 float moveX = Input.GetAxis(\"Horizontal\"); float moveZ = Input.GetAxis(\"Vertical\"); //移动 Move(moveX, moveZ); void Move(float moveX,float moveZ) { //将键盘输入变为一个三维向量 Vector3 vector3 = new Vector3(moveX, 0, moveZ); //它返回大小为1的向量,保持方向不变。归一化主要用于表示方向,特别是当仅关心方向而不关心具体长度时。 vector3 = vector3.normalized; //增量时间是实时变动的,1秒30帧,Time.deltaTime就是1/30秒;1秒60帧,Time.deltaTime就是1/60秒 vector3 = vector3 * speed * Time.deltaTime; //让物体当前的位置加上这个三维向量和速度的乘积来表示移动 rb.MovePosition(transform.position + vector3); }
相关知识点
获取键盘输入
选择 编辑——>项目设置——>输入管理器
可以看到Input.GetAxis
是 Unity 中用于获取输入设备(如键盘、鼠标、手柄)轴向输入的方法,通常用于处理平滑的移动和旋转控制。在 Unity 中,有多种轴类型,如 \"Horizontal\"(水平轴)、\"Vertical\"(垂直轴)、\"Mouse X\"(鼠标水平滚轮轴)和 \"Mouse Y\"(鼠标垂直滚轮轴)等。这些轴的值可以用来控制游戏角色的移动、缩放或旋转等动作。这些数值都处于-1 到1 之间。
增量时间delatime
增量时间是实时变动的,而且每一帧都在变动
1秒30帧,那增量时间就是 1/30 秒
1秒60帧,那增量时间就是 1/60 秒
1秒166帧,那增量时间就是 1/166 秒
这样的设计理念,就保证了无论帧率是多是少,我们让物体1秒移动10米,最后1秒移动的就一定是10米
移动的核心逻辑
1、根据键盘输入获得一个三维向量a,
2、然后进行归一化得到三维向量b(这样子使得b 只代表方向),
3、然后将b * 速度 * 增量时间得到三维向量c(c就是求得的位移)
4、使用MovePosition函数控制玩家角色刚体组件的位移
相机跟随
核心代码
public float Smooth = 5f;private GameObject gameObject;private Vector3 offset;//相机和角色之间的距离private void Awake(){ gameObject = GameObject.FindGameObjectWithTag(\"Player\");}void Start(){ offset = transform.position-gameObject.transform.position;} // Update is called once per framevoid Update(){ transform.position = Vector3.Lerp(transform.position, offset + gameObject.transform.position,Smooth*Time.deltaTime);}
相关知识点
利用标签在脚本中寻找场景中的其他物体
1、在玩家角色上设置标签
2、在脚本中定义一个GameObject, 并通过GameObject.FindGameObjectWithTag(\"Player\");
}找到玩家角色
平滑移动
Vector3.Lerp(transform.position, offset +gameObject.transform.position,Smooth*Time.deltaTime);
从transform.position移动到offset +gameObject.transform.position,平滑移动的速度为Smooth*Time.deltaTime
摄像机的两种投影方法
1、正交Orthographic(无消失点投影)
无法判断距离
正交视图无法看到一个物体是远离自己还是正在我们面前。为什么?
因为它不会根据距离收缩。所以如果你如果你画一个固定大小的物体在视点前面,同时画一个同样大小的物体在第一个物体的远后方,你无法说那个物体是第一个。
因为两个都是一样的大小,根距离无关。他们不会随着距离而收缩。
UI或2D游戏使用正交相机
2、透视Perspective(有消失点投影)
透视视图和我们从眼睛看到的视图是一样的。
例子:远小近大
旋转
核心代码
void Turn() { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); // RaycastHit是一个数据结构,记录射线投射的结果 RaycastHit raycastHit; int floor = LayerMask.GetMask(\"Floor\"); bool isTouchFloor = Physics.Raycast(ray,out raycastHit, 100, floor); if (isTouchFloor) { Vector3 vector3 = raycastHit.point - transform.position; //在y方向不希望有旋转 vector3.y = 0; //获得三维向量的旋转 Quaternion quaternion = Quaternion.LookRotation(vector3); //将旋转施加到player上 rb.MoveRotation(quaternion); } }
相关知识点
Ray、RaycastHit、Raycast及其使用
Unity 关于Ray、RaycastHit、Raycast及其使用_unity ray-CSDN博客
1、Ray可以理解为射线,它是一条从起点沿着特定方向延伸的无限长线段。
2、RaycastHit可以理解为射线命中信息,他是一个数据结构,用于存储射线投射过程中的结果。当进行射线投射后,如果射线与场景中的物体相交,RaycastHit会存储与射线相交的物体的信息
其中常用信息有:
- collider:相交物体的碰撞器组件。
- point:相交点的位置。
- normal:相交点的法线方向。
- distance:射线起点到相交点的距离。
动画控制
1、创建一个动画控制器,并添加动画,在各个动画之间创建过度,并创建变量控制(bool和trriger)
2、 在脚本中进行改变动画控制器中的参数,比如:
void AnimatorCotroller(float moveX, float moveZ) { bool isMove = moveX != 0 || moveZ != 0; //根据是否移动,去修改动画控制器中的参数 animator.SetBool(\"IsWalking\", isMove); }
3、这里有个知识点值得注意 ,就是在动画播放的过程中,可以调用一个函数(如果程序中没有这这个函数,还会报错)
public void RestartLevel() { SceneManager.LoadScene(0); MyPlayerScores.Scores = 0; }
射击
核心代码
public class MyPlayerShooting : MonoBehaviour{ public float timesc = 0.15f; //对敌人造成的伤害 public int hurtEnemy = 10; private float time; private float effectTime = 0.2f; private AudioSource m_AudioSource; private Light light; private LineRenderer lineRenderer; private ParticleSystem ParticleSystem; //开枪相关的变量 private Ray Ray; private RaycastHit RaycastHit; private int shootMask; private void Awake() { m_AudioSource = GetComponent(); light = GetComponent(); lineRenderer = GetComponent(); ParticleSystem = GetComponent(); //获取射线检测的图层 shootMask = LayerMask.GetMask(\"Shootable\"); } void Update() { //当时间间隔大于timesc时再执行射击脚本 time = time + Time.deltaTime; if (Input.GetButton(\"Fire1\")&&time > timesc) { //射击 Shoot(); } if (time > timesc * effectTime ) { lineRenderer.enabled = false; light.enabled = false; } } void Shoot() { //重置计时器 time = 0; //播放射击音乐 m_AudioSource.Play(); //启用灯光 light.enabled = true; //绘制线条 lineRenderer.SetPosition(0, transform.position); lineRenderer.SetPosition(1, transform.position+ 100*transform.forward); lineRenderer.enabled = true; //播放粒子系统组件 ParticleSystem.Play(); //发射射线 检测是否命中 //定义射线 Ray.origin = transform.position;//射线的起点 Ray.direction = transform.forward;//射线的方向 if (Physics.Raycast(Ray, out RaycastHit, 100, shootMask))//如果射线射中了可射击图层,就把信息储存到RaycastHit,并返回true { lineRenderer.SetPosition(1, RaycastHit.point); //通过collider方法获得图层相应的物体 MyEnemyHealth myEnemyHealth = RaycastHit.collider.GetComponent(); //传出参数,扣除血量和击中的位置 myEnemyHealth.TakeDamage(hurtEnemy, RaycastHit.point); } else { lineRenderer.SetPosition(1, transform.position + 100 * transform.forward); } }}
相关知识点
生成射线
1、在枪械上创建组件,这里的粒子系统/声音/Line Renderer/Light 都使用预制体
2、在脚本中实现:射击时启用这些组件
//播放射击音乐 m_AudioSource.Play(); //启用灯光 light.enabled = true; //绘制线条 lineRenderer.SetPosition(0, transform.position); lineRenderer.SetPosition(1, transform.position+ 100*transform.forward); lineRenderer.enabled = true; //播放粒子系统组件 ParticleSystem.Play();
射击检测
1、把敌人的图层设计为\"Shootable\"
2、通过Physics.Raycast方法,如果射线射中了可射击图层,就把信息储存到RaycastHit,并返回true。然后修改开枪时射线的终点(这个时候终点就应该在敌人身上了)
//发射射线 检测是否命中 //定义射线 Ray.origin = transform.position;//射线的起点 Ray.direction = transform.forward;//射线的方向 if (Physics.Raycast(Ray, out RaycastHit, 100, shootMask))//如果射线射中了可射击图层,就把信息储存到RaycastHit,并返回true { lineRenderer.SetPosition(1, RaycastHit.point); //通过collider方法获得图层相应的物体 MyEnemyHealth myEnemyHealth = RaycastHit.collider.GetComponent(); //传出参数,扣除血量和击中的位置 myEnemyHealth.TakeDamage(hurtEnemy, RaycastHit.point); } else { lineRenderer.SetPosition(1, transform.position + 100 * transform.forward); }
3、通过collider方法获得图层相应的物体,传出参数,扣除血量和击中的位置。这个和后面敌人的代码是相关联的
生命值
核心代码
using System;using System.Collections;using System.Collections.Generic;using Unity.VisualScripting;using TMPro;using UnityEngine;using UnityEngine.SceneManagement;using UnityEngine.UI;public class MyPlayerHealth : MonoBehaviour{ public float health = 100f; public bool IsPlayerDead = false; public AudioClip PlayerDearhClip; //玩家UI public TextMeshProUGUI PlayerHealth; //玩家受伤遮挡 public Image ImageHealth; public Color Color = new Color(1f, 0f, 0f, 0.1f); private AudioSource AudioSource; private Animator Animator; private PlayerMovement playerMovement; private MyPlayerShooting MyPlayerShooting; private bool damaged = false; private void Awake() { AudioSource = GetComponent(); Animator = GetComponent(); playerMovement = GetComponent(); MyPlayerShooting = GetComponentInChildren(); } // Update is called once per frame void Update() { if (damaged) { ImageHealth.color = Color; } else { ImageHealth.color = Color.Lerp(ImageHealth.color,Color.clear,Time.deltaTime*3f); } damaged = false; } //玩家受伤逻辑 public void TakeDamage(int a) { damaged = true; if (IsPlayerDead) { return; } //播放受伤的声音 AudioSource.Play(); health -= a; //更新血量UI PlayerHealth.text = health.ToString(); //判断是否死亡 if(health <= 0) { IsPlayerDead = true; Death(); } } private void Death() { //播放死亡声音 AudioSource.clip = PlayerDearhClip; AudioSource.Play(); //播放死亡动画 Animator.SetTrigger(\"PlayerDeath\"); //禁止移动和射击,通过禁用两个组件 playerMovement.enabled = false; MyPlayerShooting.enabled = false; } public void RestartLevel() { SceneManager.LoadScene(0); MyPlayerScores.Scores = 0; }}
敌人角色
AI
核心代码
public class MyEnemyMovement : MonoBehaviour{ private NavMeshAgent agent; private GameObject player; private MyEnemyHealth MyEnemyHealth; private void Awake() { agent = GetComponent(); player = GameObject.FindGameObjectWithTag(\"Player\"); MyEnemyHealth = GetComponent(); } void Update() { //先判断是否死亡,所有获取是否存活 if (!MyEnemyHealth.IsDeath) { //把player设置为NavMeshAgent组件的目标,这样子敌人就会跟随了 agent.SetDestination(player.transform.position); } }}
相关知识点
nav mesh agent
NavMeshAgent 组件可帮助您创建在朝目标移动时能够彼此避开的角色。代理 (Agent) 使用导航网格来推断游戏世界,并知道如何避开彼此以及其他移动障碍物。
导航网格代理 (NavMesh Agent) - Unity 手册
导航系统
【Unity 导航系统】导航系统详解 - 知乎
攻击
核心代码
using System;using System.Collections;using System.Collections.Generic;using UnityEngine;public class MyEnemyAttack : MonoBehaviour{ public int enemyDamege = 10; public float timesc = 2.5f;//2.5秒对玩家造成一次伤害 private float time = 0; private GameObject Player; private bool playerInRange; private MyPlayerHealth myPlayerHealth; private Animator Animator; private void Awake() { Player = GameObject.FindGameObjectWithTag(\"Player\"); myPlayerHealth = Player.GetComponent(); Animator = GetComponent(); } void Update() { //一直累加时间。直到超出设定的时间范围 time += Time.deltaTime; if (!myPlayerHealth.IsPlayerDead && playerInRange && time>timesc) { //敌人离玩家很近 就造成伤害 Attack(); } if (myPlayerHealth.IsPlayerDead) { //玩家死亡的话就让敌人进入Idle状态 Animator.SetTrigger(\"PlayerDead\"); } } private void Attack() { time = 0; myPlayerHealth.TakeDamage(enemyDamege); } //检测是否和player触碰 private void OnTriggerEnter(Collider other) { if (other.gameObject == Player) { playerInRange = true; } } private void OnTriggerExit(Collider other) { if (other.gameObject == Player) { playerInRange = false; } }}
生命值
核心代码
using System;using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.AI;public class MyEnemyHealth : MonoBehaviour{ public int health = 100; public AudioClip deathSound; public bool IsDeath = false; public NavMeshAgent navMeshAgent; private bool IsSiking = false;//判断是否执行了死亡动画 private AudioSource AudioSource; private ParticleSystem ParticleSystem; private Animator animator; private CapsuleCollider CapsuleCollider; private void Awake() { AudioSource = GetComponent(); ParticleSystem = GetComponentInChildren(); animator = GetComponent(); CapsuleCollider = GetComponent(); navMeshAgent = GetComponent(); } void Update() { if (IsSiking) { transform.Translate(-transform.up * Time.deltaTime*2.5f); } } public void TakeDamage(int a,Vector3 hitpoint) { //判断敌人的死亡,如果死亡 就不造成伤害,直接退出 if (IsDeath) { return; } //播放受伤声音 AudioSource.Play(); //播放粒子特效 ParticleSystem.transform.position = hitpoint; ParticleSystem.Play(); health -= a; if (health <= 0) { Death(); } } private void Death() { IsDeath = true; //死亡后加分 MyPlayerScores.Scores += 10; //播放死亡动画 animator.SetTrigger(\"Death\"); //将死亡之后的敌人设置为触发器,从而不阻挡player的移动 CapsuleCollider.isTrigger = true; GetComponent().isKinematic = true; //播放死亡音效 AudioSource.clip = deathSound; AudioSource.Play(); } //这个函数是在敌人死亡动画中的事件里需要执行的函数 public void StartSinking() { navMeshAgent.enabled = false; IsSiking = true; Destroy(gameObject, 2f); }}
敌人生成
核心代码
using System.Collections;using System.Collections.Generic;using UnityEngine;public class MyEnemyManager : MonoBehaviour{ public GameObject m_Enemy; public GameObject enemyPoint; public float enemyGTime = 1f; public float StartTime = 0f; private void Start() { //这个方法含义是第StartTime秒是调用方Spawn函数,并且在之后每enemyGTime秒调用一次Spawn函数 InvokeRepeating(\"Spawn\", StartTime, enemyGTime); } private void Spawn() { Instantiate(m_Enemy,enemyPoint.transform.position, enemyPoint.transform.rotation); }}
unity中instantiate()函数用法_unity instantiate-CSDN博客
UI
这里强调一下新版UI和旧版UI在脚本中的定义是不一样,注意区别。
打包/结束语
直接打包运行游戏即可。有幸刷到这个视频。之前一直有接触unity但是都学的零零散散的,一直想跟着教程做个游戏,但是总半途而废。感觉up这个视频讲的很有条理,差不多花了一天半的时间就跟着吧游戏做出来了。很多时候真的要勇敢踏出那一步吧,感谢up,让我完成了十坨💩中的第一坨💩。最重要的让我觉得我真的可以用unity开发一个游戏。而且我认为游戏的逻辑性是入门游戏的基础,再之后才是建模 渲染 故事等等等等,对于新手小白来说up的教程能让人完整的了解整个unity的开发逻辑和框架。这个笔记也算是一个小小的记录了,后面得多学习学习,并继续更新笔记!!!