unity学习笔记(三)
综合练习小案例
玩家控制
基本流程
-
设定移速(全局,以便在unity界面中直接修改)(如
public float speed = 5;
) -
将移动单独封装成方法
-
在移动方法中完成获取输入、设置移动动画、设置移动时朝向以及移动角色
private void Move() { //获取输入 int inputX = (int)UnityEngine.Input.GetAxisRaw("Horizontal"); int inputY = (int)UnityEngine.Input.GetAxisRaw("Vertical"); //设置动画 bool isMoving = (inputX!= 0 || inputY!= 0); animator.SetBool("Move", isMoving); //设置角色的朝向 if (inputX > 0) transform.localScale = new Vector3(5, 5, 1); else if (inputX < 0) transform.localScale = new Vector3(-5, 5, 1); //移动角色 Vector3 moveDirection = new Vector3(inputX, inputY, 0); transform.Translate(moveDirection * moveSpace * Time.deltaTime); }
要点:
- 最好给玩家同时添加碰撞体(人形的话椭圆形最好)和刚体,并设置为触发器,以在节约资源的情况下完成与其他物体的互动(物理碰撞,被攻击死亡之类的)
- 当获取X,Y轴输入时,
GetAxis()
函数传递的值类型为float
,移动效果为有惯性;
GetAxisRaw()
函数传递的值类型为int
,移动效果为无惯性。注意区分并转换类型
发射子弹
基本流程
-
在角色上创建一个空物体作为子弹发生点
-
需要给操作角色封装一个攻击方法的同时,给子弹一个运行脚本
-
攻击方法:
实例化子弹(gemPrefab
需要在unity中选中) -> 子弹发射(移动)public GameObject gemPrefab; public Transform gemShootPoint; public float shootCD = 1; private float shootCDTimer; private void Attack() { //子弹冷却 if (shootCDTimer > 0) { shootCDTimer -= Time.deltaTime; //如果子弹冷却时间未到,则不发射子弹 return; } //按下空格发射子弹 if (UnityEngine.Input.GetKeyDown(KeyCode.Space)) { //设置子弹冷却时间 shootCDTimer = shootCD; //实例化一个Gem子弹 GameObject gem = GameObject.Instantiate(gemPrefab); //设置子弹的位置 gem.transform.position = gemShootPoint.position; //设置子弹的方向 bool isRight = transform.localScale.x == 5; Vector3 moveDir = isRight? Vector3.right * 5 : Vector3.left * 5; gem.GetComponent<Gem>().Init(moveDir); } } -
子弹脚本:
设置子弹基本属性(速度、销毁时间)public class Gem : MonoBehaviour { public float moveSpeed = 5f; //子弹移动方向 private Vector3 moveDir; public float destroyTime = 2; private float destroyTimer; //初始化 public void Init(Vector3 dir) { moveDir = dir; destroyTimer = destroyTime; } void Update() { //gem子弹移动 transform.Translate(moveDir * moveSpeed * Time.deltaTime, Space.World); //gem子弹销毁 if (destroyTimer <= 0) { Debug.Log("Gem Destroyed"); Destroy(gameObject); } //gem子弹销毁计时器 else { destroyTimer -= Time.deltaTime; } } }
怪物生成
基本流程
- 确定好怪物生成的坐标点位
- 确定好怪物生成的时间间隔(CD)
public class MonsterManager : MonoBehaviour { //序列化(将数据可视化到unity的Inspector面板中) [System.Serializable] //怪物生成点 public class MonsterSpawnPoint { //生成点 public Transform spawnPoint; //怪物预制体 public GameObject monsterPrefab; //生成间隔 public float spawnInterval; //计时器(需要非序列化) private float spawnTimer; public void Update(float deltaTime) { //更新计时器 spawnTimer -= Time.deltaTime; if (spawnTimer <= 0) { spawnTimer = spawnInterval; //生成怪物 GameObject monster = GameObject.Instantiate(monsterPrefab); //将不同点生成的怪物归类到对应点下 monster.transform.SetParent(spawnPoint); //设置怪物位置 monster.transform.position = spawnPoint.position; } } } //怪物生成点数组 public MonsterSpawnPoint[] spawnPoints; private void Update() { //更新所有生成点 for (int i = 0; i < spawnPoints.Length; i++) { spawnPoints[i].Update(Time.deltaTime); } } }
创建MonsterManager
后可以再在其下设置好预设的点位Points
怪物逻辑
移动逻辑
基本流程
-
确定怪物移动逻辑是当玩家存在时,向玩家移动
将Player
设置为单例模式//单例模式:通过将当前Player对象赋值给静态变量instance,确保游戏中只有一个Player实例,并且可以通过Player.instance全局访问。 public static Player instance; //当游戏开始时,将当前Player对象设置为静态变量instance。 //优点:无论在游戏的任何地方,我们都可以通过Player.instance来访问这个唯一的Player实例,从而更方便地进行全局访问和控制 //初始化:在游戏开始前进行必要的初始化操作,确保Player对象在游戏中的唯一性和可访问性。 private void Awake() { instance = this; } Monster
检查玩家是否存在,不存在的话就不执行移动逻辑if (Player.instance == null) return; -
怪物执行向玩家移动的逻辑
//利用向量知识,将怪物的方向始终对准玩家方向 Vector3 moveDir = (Player.instance.transform.position - transform.position).normalized; //怪物移动 transform.Translate(Time.deltaTime * moveSpeed * moveDir, Space.World); //判断怪物移动时的左右方向 bool isRight = transform.position.x > Player.instance.transform.position.x; //根据移动时单独左右方向旋转贴图 if (isRight) transform.localScale = new Vector3(-5, 5, 1); else transform.localScale = new Vector3(5, 5, 1);
受击死亡逻辑
基本流程
-
为怪物添加HP变量,并设置好触发器事件函数并添加死亡动画事件
public int HP; //动画控制器 public Animator animator; private void OnTriggerEnter2D(Collider2D collision) { if (HP == 0) return; //接触子弹 if (collision.gameObject.CompareTag("Bullet")) { //子弹碰撞后,怪物生命值减一,子弹消失 Destroy(collision.gameObject); HP-=1; //HP归零时触发 if (HP == 0) { animator.SetTrigger("Death"); } } } #region 动画事件 //死亡动画结束 private void OnDeathAnimationEnd() { //对象消亡 Destroy(gameObject); } #endregion -
为怪物设置好死亡动画,并在动画最后一阵添加动画事件(选定脚本里写好的)
设置动画状态,并把条件设置为触发器(无后摇)
-
将怪物添加上碰撞体组件,并勾选触发器选项
-
将子弹添加刚体和碰撞体,锁定三个方向的轴并加上
“Bullet”
标签
鼠标指向为攻击方向
基本流程
-
通过摄像机(
Camera
获取鼠标的坐标),再将角色朝向翻转逻辑和攻击方向逻辑改写//使用new关键字明确地表示camera字段是当前类特有的,与基类中的任何同名字段无关。 public new Camera camera; void Update() { //获取鼠标位置 Vector3 mousePos = camera.ScreenToWorldPoint(Input.mousePosition); mousePos.z = 0; Move(mousePos); Attack(mousePos); } private void Move(Vector3 mousePos) { //获取输入 int inputX = (int)UnityEngine.Input.GetAxisRaw("Horizontal"); int inputY = (int)UnityEngine.Input.GetAxisRaw("Vertical"); //设置动画 bool isMoving = (inputX!= 0 || inputY!= 0); animator.SetBool("Move", isMoving); //设置角色的朝向 if (mousePos.x > transform.position.x) transform.localScale = new Vector3(5, 5, 1); else if (mousePos.x < transform.position.x) transform.localScale = new Vector3(-5, 5, 1); //移动角色 Vector3 moveDirection = new Vector3(inputX, inputY, 0); transform.Translate(moveDirection * moveSpace * Time.deltaTime); } private void Attack(Vector3 mousePos) { //子弹冷却 if (shootCDTimer > 0) { shootCDTimer -= Time.deltaTime; //如果子弹冷却时间未到,则不发射子弹 return; } //鼠标左键按下发射子弹,持续按下可以持续发射子弹 if (Input.GetMouseButton(0)) { //设置子弹冷却时间 shootCDTimer = shootCD; //实例化一个Gem子弹 GameObject gem = GameObject.Instantiate(gemPrefab); //设置子弹的位置 gem.transform.position = gemShootPoint.position; //设置子弹的方向 Vector3 moveDir = (mousePos - transform.position).normalized; gem.GetComponent<Gem>().Init(moveDir); } }
玩家生命值以及状态(模拟UI)
基本流程
-
为解决人物移动时,
Camera
会带着UI一起翻转的问题,需要将Camare
单独列出并使用脚本的方法让Camera
跟随玩家视角
-
为保证能稳定获取玩家最后一帧的位置,所以使用
LateUpdate
public Transform target; private void LateUpdate() { Vector3 pos = target.position; //避免摄像机与其他素材重叠 pos.z = -10; transform.position = pos; } 可新建一个
CameraPoint
在玩家身上,用于挂起target
-
给玩家新建一个HP字段并定义属性,后继续设置一个HP精灵管理器以及HP状态精灵数组
//声明一个HpSpriteRenderer字段,用于渲染角色的血条 public SpriteRenderer HpSpriteRenderer; //声明一个HpSprites字段,用于存储角色的血条图片 public Sprite[] HpSprites; //[SerializeField]:这是一个属性(Attribute),用于指示Unity在Inspector窗口中显示私有字段hp,即使它是私有的。 //private int hp:声明一个私有的整型变量hp,用于存储角色的生命值。 [SerializeField] private int hp; //声明一个公共属性HP,用于外部访问和修改私有字段hp。 public int HP { //返回私有字段hp的值。 get => hp; //用于设置私有字段hp的值,并在设置值时更新HpSpriteRenderer的精灵图像。 set { //将传入的值value赋给私有字段hp hp = value; HpSpriteRenderer.sprite = HpSprites[hp]; } } private void Awake() { instance = this; //血量初始化 HP = HpSprites.Length - 1; } -
在unity中拖入对应组件
玩家受伤及死亡
基本流程
-
在怪物的脚本下设置好玩家触发到怪物后的事件
private void OnTriggerEnter2D(Collider2D collision) { //接触玩家 //如果发生碰撞的物体Tag为“Player”的话,调用对应组件的Hurt()方法 if (collision.gameObject.CompareTag("Player")) { collision.gameObject.GetComponent<Player>().Hurt(); } } -
在玩家脚本下写好受伤方法,死亡方法以及死亡动画事件
[SerializeField] private int hp; public int HP { get => hp; set { hp = value; HpSpriteRenderer.sprite = HpSprites[hp]; //将和hp归零判断放在字段中更方便 if (hp == 0) { Death(); } } } public void Hurt() { if (HP <= 0) return; HP-=1; } private void Death() { //触发死亡动画条件 animator.SetTrigger("Death"); } #region 动画事件 //死亡动画结束 private void OnDeathAnimationEnd() { //隐藏玩家组件 gameObject.SetActive(false); } #endregion 编辑好死亡动画后,在动画最后一帧设置动画事件,选定
OnDeathAnimationEnd()
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· [翻译] 为什么 Tracebit 用 C# 开发
· 腾讯ima接入deepseek-r1,借用别人脑子用用成真了~
· Deepseek官网太卡,教你白嫖阿里云的Deepseek-R1满血版
· DeepSeek崛起:程序员“饭碗”被抢,还是职业进化新起点?
· RFID实践——.NET IoT程序读取高频RFID卡/标签