Unity核心实践小项目
要源码包的私信我。
简介
衔接Unity核心学习后的实操小项目
需求分析
准备工作
面板基类
为了能够控制一画布整体的透明度,所以需要给每个面板都添加一个 CanvasGroup组件
UI管理器
UGUI方面的参数设置
开始场景
场景搭建
直接用资源包搭建好的场景:Demo1 (PC端)
Demo2_mobile 是移动端的
将场景Demo1 复制到 Scenes文件夹下
删除
调整好相机 这样即可
开始界面
拼界面
拖入两个僵尸,创建动画状态机,拖入啃食动画和倒下动画到状态机里(注意要拖入循环动画),再把动画状态机拖入僵尸模型的Animator 组件中
界面逻辑
Main主路口
设置界面
拼界面
背景音乐数据
创建Data 数据文件夹
创建 BkMusic 用于管理背景音乐
界面逻辑
摄像机动画逻辑
先为摄像机做四个动画:idle(上下缓动)、turnLeft(左转摄像机)、turnRight(右转摄像机)、leftIdle(左上下缓动)
创建 CameraAnimator 脚本
BeginPanel 代码添加
人物选择界面
拼界面
资源准备
1.准备好人物模型
2.给人物都配好相应的武器
3.创建人物的动画状态机
双击进入
设置一些参数
分析出有9个动作
设置值和匹配动画
拖入翻滚动画
用到动画遮罩知识点,创建人物攻击动画
创建人物蹲下动画
一个人物的证套动作就完成了
运用 动画状态机复用功能为其他人物创建动画状态机
绑定相应动画就可以
数据准备
创建人物数据
转Json
创建玩家数据类
界面逻辑
先添加一个购买按钮
ChooseHeroPanel 逻辑
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;public class ChooseHeroPanel : BasePanel
{//左右键public Button btnLeft;public Button btnRight;//购买按钮public Button btnUnLock;public Text txtUnLock;//开始和返回public Button btnStart;public Button btnBack;//左上角拥有的钱public Text txtMoney;//角色信息public Text txtName;//英雄预设体需要创建在的位置private Transform heroPos;//当前场景中显示的对象private GameObject heroObj;//当前使用的数据private RoleInfo nowRoleData;//当前使用数据的索引private int nowIndex;public override void Init(){//一开始就找到场景中 放置对象预设体的位置heroPos = GameObject.Find("HeroPos").transform;//更新左上角玩家拥有的钱txtMoney.text = GameDataMgr.Instance.playerData.haveMoney.ToString();btnLeft.onClick.AddListener(() =>{--nowIndex;if (nowIndex < 0)nowIndex = GameDataMgr.Instance.roleInfoList.Count - 1;//模型的更新ChangeHero();});btnRight.onClick.AddListener(() =>{++nowIndex;if (nowIndex >= GameDataMgr.Instance.roleInfoList.Count)nowIndex = 0;//模型更新ChangeHero();});btnUnLock.onClick.AddListener(() =>{//点击解锁按钮的逻辑PlayerData data = GameDataMgr.Instance.playerData;//当有钱时if (data.haveMoney >= nowRoleData.lockMoney){//购买逻辑//减去花费data.haveMoney -= nowRoleData.lockMoney;//更新界面显示txtMoney.text = data.haveMoney.ToString();//记录购买的iddata.buyHero.Add(nowRoleData.id);//保存数据GameDataMgr.Instance.SavePlayerData();//更新解锁按钮UpdateLockBtn();//提示面板 显示购买成功print("购买成功");}else{//提示面板 显示 金钱不足print("金币不足!");}});btnStart.onClick.AddListener(() =>{//第一 是记录当前选择的角色//因为 GameDataMgr 是单例模式 所以就算切场景了数据也不会删除,它是唯一的//后面我们可以通过单例模式的对象去获取里面的信息,相当于将数据传递到了 GameDataMgr中,间接的帮我们存储数据GameDataMgr.Instance.nowSelRole = nowRoleData;//第二 是隐藏自己 显示场景选择界面UIManager.Instance.HidePanel<ChooseHeroPanel>();});btnBack.onClick.AddListener(() =>{//隐藏自己UIManager.Instance.HidePanel<ChooseHeroPanel>();//播放切换摄像机动画//先得到主摄像机Camera.main.GetComponent<CameraAnimator>().TurnRight(() =>{//动画播放完后 显示开始界面UIManager.Instance.ShowPanel<BeginPanel>();});});//更新模型显示ChangeHero();}/// <summary>/// 更新场景上要显示的模型/// </summary>private void ChangeHero(){if (heroObj != null){Destroy(heroObj);heroObj = null;}//取出数据的一条 根据索引值nowRoleData = GameDataMgr.Instance.roleInfoList[nowIndex];//实例化对象 并且记录下来 用于下次切换时 删除heroObj = Instantiate(Resources.Load<GameObject>(nowRoleData.res), heroPos.position, heroPos.rotation);//根据解锁相关数据 来决定是否显示解锁按钮UpdateLockBtn();}/// <summary>///更新解锁按钮显示情况/// </summary>private void UpdateLockBtn(){//如果该角色 需要解锁 并且没有解锁的话 就应该显示解锁按钮 并且隐藏开始按钮if (nowRoleData.lockMoney > 0 && !GameDataMgr.Instance.playerData.buyHero.Contains(nowRoleData.id)){//更新解锁按钮显示 并更新上面的钱btnUnLock.gameObject.SetActive(true); // 显示true 隐藏falsetxtUnLock.text = "¥ " + nowRoleData.lockMoney;//隐藏开始按钮 因为该角色没有解锁btnStart.gameObject.SetActive(false);}else{btnUnLock.gameObject.SetActive(false);btnStart.gameObject.SetActive(true);}}public override void HideMe(UnityAction callBack){base.HideMe(callBack);//每次隐藏自己时 要把当前显示的3D模型角色 删除掉if (heroObj != null){DestroyImmediate(heroObj); //马上删除,不用等到下一帧heroObj = null;}}}
其他补充逻辑
提示界面
拼界面
界面逻辑
场景选择界面
拼界面
数据准备
准备场景数据
创建Excel表格
转成 Json
创建图片数据
创建 场景数据类
GameDataMgr 中添加场景数据
界面逻辑
上节课遗留
要把图片资源的纹理类型(Texture Type)改为 精灵图片
ChooseScenePanel 面板逻辑
调用
小总结:所有的面板都是数据的体现。
游戏场景
场景搭建
遗留:ChooseHeroPanel 面板中 显示人物名字
自行添加对应的逻辑
场景搭建
因为丧尸要自动寻路,所以先烘焙地图
1.打开导航
2.烘焙前先设置一下——烘焙静态
不需要设置连接点,因为该地图没有断开的点 (Off Mesh Link Generation)
3.回到导航窗口(Navigation)-->打开 烘焙页签(Bake)-->点击 Back 烘焙
记得打开 辅助功能 -- Gizmos
调整
游戏界面
拼界面
界面逻辑
GamePanel 逻辑
创建组合控件的脚本
摄像机跟随逻辑
创建 CameraMove 脚本(挂载到主摄像机上)
玩家逻辑
玩家的控制其实就是调用动画的播放
分析 玩家的一些属性
给人物添加 角色碰撞器
添加怪物层
为每个武器添加一个开火点
给开火动画添加事件
PlayerObject 逻辑
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class PlayerObject : MonoBehaviour
{//为获得玩家身上的动画组件private Animator animator;//1.玩家属性的初始值//玩家攻击力private int atk;//玩家拥有的钱public int money;//旋转的速度private float roundSpeed = 50;//持枪对象才有的开火点public Transform gunPoint;//2.移动变化 动作变化//3.攻击动作的不同处理//4.金币变化的逻辑// Start is called before the first frame updatevoid Start(){//得到自己依附的 Animator组件animator = this.GetComponent<Animator>();}/// <summary>/// 初始化玩家基础属性/// </summary>/// <param name="atk"></param>/// <param name="money"></param>public void InitPlayerInfo(int atk, int money){this.atk = atk;this.money = money;//更新界面上金币的数量UpdateMoney();}// Update is called once per framevoid Update(){//2.移动变化 动作变化//移动动作的变换 由于动作有位移 我们也应用了动作的位移 所以只要改变这两个值 就会有动作的变化 和 速度的变化animator.SetFloat("VSpeed", Input.GetAxis("Vertical"));animator.SetFloat("HSpeed", Input.GetAxis("Horizontal"));//旋转this.transform.Rotate(Vector3.up, Input.GetAxis("Mouse X") * roundSpeed * Time.deltaTime);//下蹲if (Input.GetKeyDown(KeyCode.LeftShift)){//当按下 Shift键时 把编号为1的动画层级权重改为1animator.SetLayerWeight(1, 1);}else if (Input.GetKeyUp(KeyCode.LeftShift)){//当抬起 Shift键时 把编号为1的动画层级权重改为0animator.SetLayerWeight(1, 0);}//按下R 播放打滚动画if (Input.GetKeyDown(KeyCode.R))animator.SetTrigger("Roll");//鼠标左键 开火if (Input.GetMouseButtonDown(0))animator.SetTrigger("Fire");}//3.攻击动作的不同处理/// <summary>/// 专门用于处理刀武器攻击动作的伤害检测事件/// </summary>public void KnifeEvent(){//伤害检测 返回一个碰撞器数组Collider[] colliders = Physics.OverlapSphere(this.transform.position + this.transform.forward + this.transform.up, 1, 1 << LayerMask.NameToLayer("Monster"));//暂时无法继续写逻辑了 因为 我们没有怪物对应的脚本for (int i = 0; i < colliders.Length; i++){//得到碰撞到的对象上的怪物脚本 让其受伤}}public void ShootEvent(){//进行摄像机检测//前提是需要有开火点RaycastHit[] hits = Physics.RaycastAll(new Ray(gunPoint.position, gunPoint.forward), 1000, 1 << LayerMask.NameToLayer("Monster"));for (int i = 0; i < hits.Length; i++){//得到对象上的怪物脚本 让其受伤}}//4.金币变化的逻辑public void UpdateMoney(){//间接的更新界面上 钱的数量UIManager.Instance.GetPanel<GamePanel>().UpdateMoney(money);}/// <summary>/// 提供给外部加钱的方法/// </summary>/// <param name="money"></param>public void AddMoney(int money){//加金币this.money += money;UpdateMoney();}
}
保护区域逻辑
选择地图上一片区域为保护区
加入一个合适的特效
注意:因为一些草的贴图丢失,导致画面有面片感
处理:点击地图里的 地形——Terrain
点地形组件 Terrain 中页签中的 花的图标
按住 Shift 点击地图上的区域消除即可
给特效区域添加球形碰撞,并勾选触发器
MainTowerObject 逻辑(主保护区域相关逻辑) 挂载到保护区特效上
怪物逻辑
状态机准备
创建动画状态机
创建丧尸模型
数据准备
创建Excel表
再转成 Json 数据
然后申明 对应的数据结构
在 GameDataMgr 中去读取它
逻辑处理
创建 MonsterObject 脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;public class MonsterObject : MonoBehaviour
{//动画相关private Animator animator;//位移相关 寻路组件private NavMeshAgent agent;//一些不变的基础数据private MonsterInfo monsterInfo;//当前血量private int hp;//怪物是否死亡public bool isDead = false;//上一次攻击的时间private float frontTime;//出生过后再移动//移动————寻路组件//攻击————伤害检测//受伤//死亡//初始化// Start is called before the first frame updatevoid Awake(){//得到丧尸对象上挂载的动画组件和寻路组件animator = this.GetComponent<Animator>();agent = this.GetComponent<NavMeshAgent>();}//初始化public void InitInfo(MonsterInfo info){monsterInfo = info;//状态机加载animator.runtimeAnimatorController = Resources.Load<RuntimeAnimatorController>(monsterInfo.animator);//要改变的当前血量,一定要取出来用,不能改变数据里的数据hp = info.hp;//速度初始化//速度和加速度赋值 之所以赋值一样 是希望没有明显的加速度 而是一个匀速运动agent.speed = agent.acceleration = info.moveSpeed;//旋转速度agent.angularSpeed = info.roundSpeed;}//受伤public void Wound(int dmg){//减少血量hp -= dmg;//播放受伤动画animator.SetTrigger("Wound");if (hp <= 0){//死亡}else{//每死亡 是受伤 播放受伤音效}}//死亡public void Dead(){isDead = true;//停止移动agent.isStopped = true;//播放死亡动画animator.SetBool("Dead", true);//播放音效//加金币 ———— 我们之后通过关卡管理类 来管理游戏中的对象 通过它来让玩家加钱}//死亡动画播放完毕后 会调用的事件方法public void DeadEvent(){//死亡动画播放完毕移除对象//之后有了关卡管理器再来处理}//移动 ———— 寻路组件//出生过后再移动public void BornOver(){//出生结束后 再让怪物朝目标点移动agent.SetDestination(MainTowerObject.Instance.transform.position);//播放移动动画animator.SetBool("Run", true);}// 攻击void Update(){//检测什么时候停下来攻击if (isDead)return;//根据速度 来决定动画播放什么//agent.velocity 是指对象三个方向的速度animator.SetBool("Run", agent.velocity != Vector3.zero);//检测和目标点到达移动条件时 就攻击if (Vector3.Distance(this.transform.position, MainTowerObject.Instance.transform.position) < 5 && Time.time - frontTime >= monsterInfo.atkOffset){//记录这次攻击时的时间frontTime = Time.time;animator.SetTrigger("Atk");}}//伤害检测public void AtkEvent(){//范围检测 进行伤害判断 用圆形范围检测//Physics.OverlapSphere 第一个参数是: 位置 ,第二个参数是:半径,第三个参数是:能够攻击到的层级Collider[] colliders = Physics.OverlapSphere(this.transform.position + this.transform.forward + this.transform.up, 1, 1 << LayerMask.NameToLayer("MainTower"));for (int i = 0; i < colliders.Length; i++){if (colliders[i].gameObject == MainTowerObject.Instance.gameObject){//让保护区受到伤害MainTowerObject.Instance.Wound(monsterInfo.atk);}}}
}
补充:1.给所有丧尸模型添加 寻路组件—— Nav Mesh Agent
2.添加事件:丧尸死亡后移除模型(自行给每个死亡动画都添加上)
3.添加层级 MainTower 层
4.给每个丧尸模型都调整好预设体(调到适当的大小)和 添加碰撞盒——胶囊碰撞盒(后面玩家要攻击)
出怪点逻辑
选择适合的出怪点特效
创建 MonsterPoint (挂载到出怪特效上)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class MonsterPoint : MonoBehaviour
{//怪物有多少波public int maxWave;//每波怪物有多少只public int monsterNumOneWave;//用于记录 当前波的怪物还有多少只没有创建private int nowNum;//怪物ID 允许有多个 这样就可以随机创建不同的怪物 更具多样性public List<int> monsterIDs;//用于记录 当前波 要创建什么ID的怪物private int nowID;//单只怪物创建间隔时间public float createOffsetTime;//波与波之间的间隔时间public float delayTime;//第一波怪物创建的间隔时间public float firstDelayTime;// Start is called before the first frame updatevoid Start(){//利用延时函数 Invok//第一波 延时Invoke("CreateWave", firstDelayTime);}/// <summary>/// 开始创建一波的怪物/// </summary>private void CreateWave(){//得到当前波怪物的ID是什么 用到 Unity中的随机数nowID = monsterIDs[Random.Range(0, monsterIDs.Count)];//当前波怪物有多少只nowNum = monsterNumOneWave;//创建丧尸CreateMonster();//减少波数--maxWave;}/// <summary>/// 创建怪物/// </summary>private void CreateMonster(){//直接创建丧尸//取出怪物数据MonsterInfo info = GameDataMgr.Instance.monsterInfoList[nowID - 1];//创建怪物预设体GameObject obj = Instantiate(Resources.Load<GameObject>(info.res), this.transform.position, Quaternion.identity);//为我们创建出的怪物预设体 添加怪物脚本 进行初始化MonsterObject monsterObj = obj.AddComponent<MonsterObject>();//初始化monsterObj.InitInfo(info);//创建完一只怪物后 减去要创建的怪物数量--nowNum;if (nowNum == 0){//继续利用延时函数if (maxWave > 0)Invoke("CreateWave", delayTime);}else{//延时函数 间隔的创建丧尸Invoke("CreateMonster", createOffsetTime);}}/// <summary>/// 出怪点是否出怪结束/// </summary>/// <returns></returns>public bool CheckOver(){return nowNum == 0 && maxWave == 0;}
}
游戏关卡管理器
创建 GameLevelMgr 管理器
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class GameLevelMgr
{private static GameLevelMgr instance = new GameLevelMgr();public static GameLevelMgr Instance => instance;public PlayerObject player;//所有的出怪点private List<MonsterPoint> points = new List<MonsterPoint>();//记录当前 还有多少波怪物private int nowWaveNum = 0;//记录 一共有多少波怪物private int maxWaveNum = 0;//记录当前场景上的怪物数量private int nowMonsterNum = 0;private GameLevelMgr(){}//通过该关卡脚本来连接 开始场景 和 游戏场景//1.切换到游戏场景时 我们需要动态的创建玩家public void InitInfo(SceneInfo info){//显示游戏界面UIManager.Instance.ShowPanel<GamePanel>();//玩家的创建//获取之前记录的当前选中的玩家数据RoleInfo roleInfo = GameDataMgr.Instance.nowSelRole;//首先获取到场景当中 玩家的出生位置Transform heroPos = GameObject.Find("HeroBornPos").transform;//实例化玩家预设体 然后把它的位置角度 设置为 场景当中出生点的位置 GameObject heroObj = GameObject.Instantiate(Resources.Load<GameObject>(roleInfo.res), heroPos.position, heroPos.rotation);//对玩家对象进行初始化player = heroObj.GetComponent<PlayerObject>();player.InitPlayerInfo(roleInfo.atk, info.money);//让摄像机 看向动态创建出来的玩家Camera.main.GetComponent<CameraMove>().SetTarget(heroObj.transform);//初始化 中央 保护区的血量MainTowerObject.Instance.UpdateHp(info.towerHp, info.towerHp);}//2.我们需要通过游戏管理器 来判断 游戏是否胜利//要知道场景中 是否还有怪物没有出 以及 场景中 是否还有 没有死亡的怪物//用于记录出怪点的方法public void AddMonsterPoint(MonsterPoint point){points.Add(point);}/// <summary>/// 更新一共有多少波怪/// </summary>/// <param name="num"></param>public void UpdatgeMaxNum(int num){maxWaveNum += num;nowWaveNum = maxWaveNum;//更新界面UIManager.Instance.GetPanel<GamePanel>().UpdateWaveNum(nowWaveNum, maxWaveNum);}public void ChangeNowWaveNum(int num){nowWaveNum -= num;//更新界面UIManager.Instance.GetPanel<GamePanel>().UpdateWaveNum(nowWaveNum, maxWaveNum);}/// <summary>/// 检测 是否胜利/// </summary>/// <returns></returns>public bool CheckOver(){for (int i = 0; i < points.Count; i++){//只要有一个出怪点 还没有出完怪 那么就证明游戏还没有胜利if (!points[i].CheckOver())return false;}//要所有的怪都死亡了if (nowMonsterNum > 0)return false;Debug.Log("游戏胜利");return true;}/// <summary>/// 改变当前场景上怪物的数量/// </summary>/// <param name="num"></param>public void ChangeMonsterNum(int num){nowMonsterNum += num;}
}
补充:1.报错
2.创建一个玩家出生位置
3.给玩家角色都绑定好开火点
4.更改 SceneInfo 数据参数
4.将两个场景添加到 Build Settings 中
5.MonsterPoint 脚本 逻辑添加
5. MonsterObject 中 逻辑添加
6.PlayerObject 添加逻辑
游戏结束面板
1.拼面板
2. GameOverPanel 逻辑
补充:1.在游戏关卡管理器(GameLevelMgr)中 提供一个清除数据的方法
2.在MonsterObject 中检测是否胜利的逻辑,并给玩家获得金币奖励
3.在进入游戏后 锁定鼠标,显示 结束面板时 解除锁定
音效特效添加
1.在 GameDataMgr 中给外部提供一个播放音效的方法(因为GameDataMgr中数据多,所以在这里写这个方法方便些)
2.在 PlayerObject 中播放音效
3.怪物受伤音效
创建特效相关
1. PlayerObject 中创建打击特效
其他相关特效可以自行添加
防御塔逻辑
数据模型准备
1.创建模型
从资源包里取出炮台模型并重命名
图片资源也得准备(自己截图)
2.配置数据
先创 Excel 表 ——> 转Json 数据 ——> 创建对应结构体数据 ——> GameDataMgr 中读取出来
防御塔逻辑
创建 TowerObject 类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class TowerObject : MonoBehaviour
{//炮台头部 用于旋转 指向目标public Transform head;//开火点 用于释放攻击的位置public Transform gunPoint;//炮台头部旋转速度 可以写死 也可以配在表中private float roundSpeed = 20;//炮台关联的数据private TowerInfo info;//当前要攻击的目标private MonsterObject targetObj;//当前要攻击的群体目标private List<MonsterObject> targetObjs;//用于计时的 用于判断攻击间隔时间private float nowTime;//用于记录怪物位置private Vector3 monsterPos;//测试代码private void Start(){InitInfo(GameDataMgr.Instance.towerInfoList[10]);}/// <summary>/// 初始化炮台相关数据/// </summary>/// <param name="info"></param>public void InitInfo(TowerInfo info){this.info = info;}// Update is called once per framevoid Update(){//单体攻击逻辑if (info.atkType == 1){//没有目标 或者 目标死亡 或者 目标超出攻击距离 就继续寻找其他目标if (targetObj == null || targetObj.isDead || Vector3.Distance(this.transform.position, targetObj.transform.position) > info.atkRange){targetObj = GameLevelMgr.Instance.FindMonster(this.transform.position, info.atkRange);}//如果没有找到任何可以攻击的对象 那么炮台就不应该旋转if (targetObj == null)return;//得到怪物位置,偏移Y的目标位置是希望 炮台头部不要上下倾斜monsterPos = targetObj.transform.position;monsterPos.y = head.position.y;//让炮台头部旋转起来head.rotation = Quaternion.Slerp(head.rotation, Quaternion.LookRotation(monsterPos - head.position), roundSpeed * Time.deltaTime);//Vector3.Angle() 这个方法可以得到两个向量的夹角//判断 两个对象之间的夹角 小于一定范围时 才能让目标受伤 并且攻击间隔条件要满足if (Vector3.Angle(head.forward, monsterPos - head.position) < 5 && Time.time - nowTime >= info.offsetTime){//让目标受伤//提示:这里为什么不用射线检测?//因为当这些条件满足时,射线检测也一定是能够打中敌方的,所以就直接让敌方受伤就行targetObj.Wound(info.atk);//播放音效GameDataMgr.Instance.PlaySound("Music/Tower");//创建开火特效GameObject effObj = Instantiate(Resources.Load<GameObject>(info.eff), gunPoint.position, gunPoint.rotation);//延迟移除特效Destroy(effObj, 0.2f);//记录开火时间nowTime = Time.time;}}//群体攻击逻辑else{targetObjs = GameLevelMgr.Instance.FindMonsters(this.transform.position, info.atkRange);if (targetObjs.Count > 0 && Time.time - nowTime >= info.offsetTime){//创建开火特效GameObject effObj = Instantiate(Resources.Load<GameObject>(info.eff), gunPoint.position, gunPoint.rotation);//延迟移除特效Destroy(effObj, 0.2f);//让目标们受伤for (int i = 0; i < targetObjs.Count; i++){targetObjs[i].Wound(info.atk);}//记录开火时间nowTime = Time.time;}}}
}
补充:1.创建炮台预设体和图片资源
2.记录丧尸的数量,用于后面进行攻击
在 关卡管理器(GameLevelMgr)中去申明(要把原申明丧尸数量的数据替换了)
再添加两个方法:添加丧尸数量、减少丧尸数量
其他地方逻辑有错也要进行对应的修改
3.在 关卡管理器(GameLevelMgr)中添加 满足攻击条件的丧尸并传出去
4.创建一些开火、伤害特效
5.给所有炮台拖入 TowerObject 脚本,并添加开火点
最后把测试代码注释了
造塔点逻辑
1.创建 造塔点特效 (在资源包里找到合适的即可)
2.给造塔点 加上碰撞器(要勾选触发器)
3.创建 TowerPoint 类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class TowerPoint : MonoBehaviour
{//造塔点关联的 塔对象private GameObject towerObj = null;//造塔点关联的 塔的数据public TowerInfo nowTowerInfo = null;//可以建造的三个塔的ID是多少public List<int> chooseIDs;// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){}/// <summary>/// 建造一个塔/// </summary>/// <param name="id"></param>public void CreateTower(int id){TowerInfo info = GameDataMgr.Instance.towerInfoList[id - 1];//如果钱不够 就不用建造if (info.money > GameLevelMgr.Instance.player.money)return;//扣钱GameLevelMgr.Instance.player.AddMoney(-info.money);//创建塔//先判断之前是否有塔 如果有 就删除if(towerObj != null){Destroy(towerObj);towerObj = null;}//实例化塔对象towerObj = Instantiate(Resources.Load<GameObject>(info.res),this.transform.position, Quaternion.identity);towerObj.GetComponent<TowerObject>().InitInfo(info);//记录当前塔的数据nowTowerInfo = info;//塔建造完毕 更新游戏界面上的内容if (nowTowerInfo.nextLev != 0){//如果 不等于0 代表还能升级,界面就更新成要升级的炮台图标UIManager.Instance.GetPanel<GamePanel>().UpdateSelTower(this);}else{UIManager.Instance.GetPanel<GamePanel>().UpdateSelTower(null);}}//一定会用到触发器进入检测函数private void OnTriggerEnter(Collider other){//如果现在已经有塔了 就没有必要再显示升级界面 或者造塔界面了if (nowTowerInfo != null && nowTowerInfo.nextLev == 0)return;UIManager.Instance.GetPanel<GamePanel>().UpdateSelTower(this);}//触发器离开检测函数private void OnTriggerExit(Collider other){//如果不希望游戏界面下方的造塔界面显示 直接传空UIManager.Instance.GetPanel<GamePanel>().UpdateSelTower(null);}}
4.在 GamePanel 中添加更新炮塔图标类的界面方法
5.在 TowerBtn 中添加 初始化炮台的方法
6.记得把炮台的图片资源调为 Sprite图片
7.报错的一个要点:运行时为什么会不显示游戏界面
因为在 GamePanel 中我们重写了 Update() , 将 BasePanel 里的 Update()覆盖了,而BasePanel里的 Updata() 有界面的淡入淡出,覆盖后就没有了。
解决办法,将 BasePanel 里的 Update 写成虚函数,让GamePanel 去重写。
8.给每个人物都加上 角色碰撞器
9.在 GamePanel 中添加 检测输入造塔的逻辑
补充:这里要在 GamePanel 中添加一个 是否检测输入的标识
10.在 MonsterObject 中添加怪物死亡后加金币的逻辑
这里发现 丧尸会多次死亡,导致我们会多加好多钱,添加一些条件判断死亡加金币
PlayerObject 中的条件也得改一下
细节完善
1.丧尸死亡后还在向前移动
关闭寻路即可(这里是将寻路组件失活)
2.射线检测的改动
玩家、丧尸、炮台、保护区的参数数组都自己合理更改。
到这里,游戏的基本逻辑都已实现,接下来要自己把人物开枪、打击特效添加好,将三副地图设计好,还有各个数值设置合理。
完成展示
Unity核心实践项目
总结
相关文章:

Unity核心实践小项目
要源码包的私信我。 简介 衔接Unity核心学习后的实操小项目 需求分析 准备工作 面板基类 为了能够控制一画布整体的透明度,所以需要给每个面板都添加一个 CanvasGroup组件 UI管理器 UGUI方面的参数设置 开始场景 场景搭建 直接用资源包搭建好的场景:…...

Avaloia 实现国产麒麟系统中文显示界面
最近在搞一个国产麒麟系统的接口对接,因为,接口内含复杂的签名验证,而且还是离线环境,所以,postman不是很好用。 就想着哪个方式好一些,主要是有选择图片的操作,所以,在Electron和A…...

pytest 生成allure测试报告
allure的安装 github地址 allure资产列表 windows下载.zip,解压并配置环境变量PATH;linux下载tar.gz,解压配置; allure作为pytest插件 # 安装 pip install allure-pytest# 执行单元测试,生成allure测试数据&…...

查询GPU版本以及PyTorch中使用单GPU和多GPU
文章目录 多GPU介绍GPU可用性及版本检查使用单个GPU使用多个GPU多GPU介绍 多GPU是指使用多个显卡来同时进行计算,以加速深度学习模型的训练和推断。每个GPU都有自己的内存和计算能力,通过同时利用多个GPU可以并行地执行模型的计算,从而提高整体的计算效率。 GPU可用性及版…...

基于SpringBoot+Vue的线上考试系统
作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的线上考试…...

动手学深度学习(pytorch土堆)-02TensorBoard的使用
1.可视化 代码使用了 torch.utils.tensorboard 将数据记录到 TensorBoard 以便可视化。具体来说,它将标量数据记录到目录 logs 中,使用的是 SummaryWriter 类。 代码分解如下: SummaryWriter("logs"):初始化一个 Ten…...

STM3学习记录
一、串口 1.串口定义,将串口相关寄存器的首地址强制转化为串口结构体,方便通过结果体访问串口的寄存器 #define __IO volatile /*!< Defines read / write permissions */ typedef struct {__IO uint32_t SR; /*!< US…...

【网络】应用层协议-http协议
应用层协议-http协议 文章目录 1.Http协议1.1什么是http协议1.2认识URL1.3urlencode和urldecode1.4HTTP请求协议格式1.5HTTP响应协议格式1.6HTTP常见的Header1.7HTTP常见状态码1.8HTTP的方法1.8根据url调取对应的服务 2.cookie和session2.1cookie2.2session 3.HTTPS协议3.1对称…...

【python】OpenCV—Mask RCNN for Object Detection and Instance Segmentation
文章目录 1、任务描述2、MASR RCNN 网络结构3、方法实现4、结果展示5、涉及到的库getPerfProfile 6、参考 1、任务描述 利用 mask rcnn 网络,进行图片和视频的目标检测和实例分割 2、MASR RCNN 网络结构 3、方法实现 # Copyright (C) 2018-2019, BigVision LLC (L…...

通过 Python 使用 Pexels图片库 API 打造个性化壁纸应用
在数字时代,照片不仅仅是回忆的载体,它们还是我们生活的美丽装饰品。想象一下,如果你能轻松地将世界上最美的免费图片应用到你的应用程序中,岂不是让你的程序立刻闪亮起来?好消息是,这不仅仅是一个梦想。今…...

多线程篇(其它容器- CopyOnWriteArrayList)(持续更新迭代)
一、CopyOnWriteArrayList(一) 1. 简介 并发包中的并发List只有CopyOnWriteArrayList。 CopyOnWriteArrayList是一个线程安全的ArrayList,对其进行的修改操作都是在底层的一个复制的数 组(快照)上进行的࿰…...

OPENAIGC开发者大赛高校组金奖 | 知洞—基于大模型的智慧题库
在第二届拯救者杯OPENAIGC开发者大赛中,涌现出一批技术突出、创意卓越的作品。为了让这些优秀项目被更多人看到,我们特意开设了优秀作品报道专栏,旨在展示其独特之处和开发者的精彩故事。 无论您是技术专家还是爱好者,希望能带给您…...

java服务CPU使用率高排查
第一步 使用top命令进行查看 如果是死锁,cpu使用率不会很高,但不会响应,这时这样排查。 第二步 使用jps查看到进程id,再使用jstack pid查看线程堆栈信息 jstack pid会出现如图所示的信息,表示发生死锁,然后去排查这…...

聚焦:clicOH 借助 NVIDIA cuOpt 实现最后一英里交付速度 20 倍提升
受消费者行为转变和疫情影响,电子商务继续呈爆炸式增长和转型。因此,物流和运输公司发现自己处于包裹配送革命的前沿。这新的现实情况在最后一英里配送中尤为明显,而后者现在已经成为供应链物流中成本最高的要素,占从零售到制造等…...

从头开始嵌入式第三十八天(数据结构 双向链表)
目录 双向链表 一、结构特点 二、操作优势 三、应用场景 1.创建链表 2.头插数据 3.打印数据 4.查找数据 5.删除数据 6.更改数据 7.清空数据 8.尾插数据 9.按位插入 10.获取长度 11.是否为空 双向链表 双向链表是一种链表结构。 一、结构特点 1. 每个节点包含两个…...

chapter14-集合——(List-HashSet)——day18
目录 519-HashSet全面说明 520-数组链表模拟 521-HashSet扩容机制 重要 522-HashSet源码解读1 526-HashSet最佳实践 527-hashSet思考题 519-HashSet全面说明 题一、 两个tom都可以添加成功是因为这是两个对象 看源码做分析:不是直接指向常量池的吗?…...

企业会议室预约管理系统
基于springbootvuemysql实现的企业会议室预约管理系统(源码数据库部署视频) ### 主要技术 SpringBoot、Vue、MySQL ### 系统角色 员工、管理员 ### 系统功能 1)管理员:数据统计(会议室使用统计-柱状图、设备状态统计…...

安全API
提到安全,大部分在学习和调试编程语言时并不太在意,真正爆发问题是在用户端。真正的安全漏洞是很可怕的,获取系统最高权限,获得敏感资讯,伤心不已,泪流满面。 字符串 C语言简洁自由的风格,产生…...

【论文阅读】视觉分割新SOTA: Segment Anything(SAM)
导言 随着基于对比文本—图像对的预训练(CLIP)方法或者模型、聊天生成预训练转换器(ChatGPT)、生成预训练转换器-4(GPT-4)等基础大模型的出现,通用人工智能( AGI)的研究…...

redis之list核心命令演示与细节探索
redis之list核心命令演示与细节探索 BLMOVE source destination LEFT|RIGHT LEFT|RIGHT timeout summary: Pop an element from a list, push it to another list and return it; or block until one is available since: 6.2.0 BLPOP key [key …] timeout summary: Remove …...

[数据集][目标检测]智慧农业草莓叶子病虫害检测数据集VOC+YOLO格式4040张9类别
数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):4040 标注数量(xml文件个数):4040 标注数量(txt文件个数):4040 标注…...

Lua 与 C#交互
Lua 与C#交互 前提 Lua是一种嵌入式脚本语言,Lua的解释器是用C编写的,因此可以方便的与C/C进行相互调用。轻量级 Lua语言的官方版本只包括一个精简的核心和最基本的库,这使得Lua体积小、启动速度快,也适合嵌入在别的程序里。 交…...

【办公类】大组工会学习(文心一言+Python批量)
背景需求: 每学期要写一份工会的大组政治学习读后感(9月-1月,共5次) 学习内容 9月、10月、11月、12月、1月的学习内容文字稿 在班级里,我擅长电脑工作,所以这种写的工作都包了。 中2班三位老师一共写3篇&…...

项目管理工作流是什么?项目管理工作流管理实战技巧!
项目管理工作流是指在协作过程中通过限制任务状态的流转进行流程控制的一种方式。项目从启动到完成所经历的一系列有序、可控的步骤和流程,它详细描述了项目执行过程中各项任务和活动的顺序、依赖关系、责任人以及完成标准等,是项目成功执行的重要保障。…...

leveldb源码剖析(二)——LSM Tree
LSM Tree LSM Tree:Log-Structured Merge Tree,日志结构合并树。是一种频繁写性能很高的数据结构。 LSM Tree将写入操作与合并操作分离,数据首先写入磁盘中的日志文件(WAL),随后写入内存缓存,…...

三十六、Gin注册功能-检查账号是否存在
一、初始化 1、在cms.go中添加数据库连接方法 func connDB(app *CmsApp) {mysqlDB, err : gorm.Open(mysql.Open("root:rootroottcp(localhost:3306)/?charsetutf8mb4&parseTimeTrue&locLocal"))if err ! nil {panic(err)}db, err : mysqlDB.DB()if err !…...

什么是期权对冲?
今天期权懂带你了解什么是期权对冲?期权对冲的选择取决于投资者的市场预期和风险承受能力,通过合理使用期权对冲策略,可以有效减少风险并优化投资组合的表现。 期权对冲是什么? 期权是一种支持双向交易的投资产品,期…...

什么是数据库课程设计?
文章目录 前言一、课程设计目的二、课程设计流程三、设计要点四、示例项目总结 前言 数据库课程设计是一个综合性的实践过程,旨在通过实际项目的设计与实现,加深学生对数据库理论知识的理解和应用能力。 以下是一个关于数据库课程设计的基本框架和要点&…...

走进低代码报表开发(二):高效报表设计新利器
在前面的文章中,我们已经详细介绍了勤研低代码开发平台的报表数据源可视化设计,接下来,让我们一起来继续了解勤研低代码平台的报表设计,在当今数字化快速发展的时代,高效便捷的开发工具对于企业和开发者来说至关重要。…...

校园水电费管理|基于java的校园水电费管理小程序系统 (源码+数据库+文档)
校园水电费管理 目录 基于java的校园水电费管理小程序系统 一、前言 二、系统设计 三、系统功能设计 小程序端 后台功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取: 博主介绍:✌️大厂码农|毕…...