赞
踩
总目录https://blog.csdn.net/qq_54263076/category_11900070.html?spm=1001.2014.3001.5482
大多数平台教程全是碰撞体的可移动平台,并没有可跳上的平台,并且要求按下键会从平台上跳下来。有也只是通过控制平台的“2D平台效果碰撞体”组件的单向平台的旋转偏移来做,但是这样会出现一个问题,当有多个人或者多个NPC在同一个平台上,如果有一个人按下键会导致所有人都跳下来,虽然其他人并没有按下键。如同下面:(看不懂原理没关系,就看个示范结果)
所以为了解决这个问题,我们需要对能进行跳跃平台的人物自己碰撞体短时间消失,射线检测是必要的。如果不懂射线检测,欢迎看我下面的博客
11.Unity2D 横版 简单AI 之背后受击转身+寻路跟随敌人+模块化+射线检测_ζั͡ ั͡雾 ั͡狼 ั͡✾的博客-CSDN博客Unity2D 横版 简单AI 之背后受击转身+寻路跟随敌人+模块化+射线检测。回顾上节课,我们已经完成了范围内检索敌人自动攻击,随机移动功能。1.敌人背后受击转身+背部攻击伤害翻倍2.寻路跟随敌人随机移动的坏处就是看到敌人(主角)会略过去,按照自己的行为行走,所以我们要再写一个行为脚本,用来看到敌人后的行为,当然如果没有看到敌人还是随机移动的函数,其中黄线为视线线,变红色为找到主角,并记录此时主角的位置......https://blog.csdn.net/qq_54263076/article/details/125756448?spm=1001.2014.3001.5501并且能进行跳跃平台的人物(主角+NPC)都有CharacterPanel脚本,所以将平台检测写入这个脚本就行。
1.可上下左右移的可跳上去的碰撞体平台
(1)创建平台预制体。精灵渲染组件+碰撞+平台效果碰撞器(可以单方面跳上去,但不能跳下来)。
(2)创建platformMove脚本,用来控制平台上下或左右循环移动,加入这个预制体
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- public class platformMove : MonoBehaviour
- {
- public float waitTime = 0.5f;//定值等待时间
- private float _waitTime ;//等待时间
- public float Speed = 1;//速度
- public Vector3 StartPoi;//起始位置
- public Vector3 EndPoi;//需要移动目标位置
- private bool IsStoE=true;//是否是起始点到终止点的状态
- platformMove(Vector3 EndPoi)//构造函数,赋值终点
- {
- this.EndPoi = EndPoi;
- }
- // Start is called before the first frame update
- void Start()
- {
- _waitTime = waitTime;
- StartPoi = transform.position;//初始化起始位置
- /* EndPoi = transform.position + 2 * Vector3.right;//用来测试,实际的目标点是通过赋值*/
-
- }
-
- // Update is called once per frame
- void Update()
- {
- Vector2 StoE = (EndPoi - StartPoi).normalized;//起始点指向终止点的单位方向向量
-
- if (_waitTime > 0)
- {
- _waitTime -= Time.deltaTime;
- }
- else
- {
- if (IsStoE)//是起始点到终止点的状态
- {
- gameObject.transform.Translate(Speed * Time.deltaTime * StoE);
- if (Vector2.Dot((EndPoi- transform.position),StoE)<0)//如果到达终点 点乘夹角是180
- {
- _waitTime = waitTime;//等待时间重置
- IsStoE = false;//改变状态
- }
-
- }
- else {
-
- gameObject.transform.Translate(-Speed * Time.deltaTime * StoE);
- if (Vector2.Dot((StartPoi - transform.position), -StoE) < 0)//如果到达起点 点乘夹角是180
- {
-
- _waitTime = waitTime;//等待时间重置
- IsStoE = true;//改变状态
- }
-
-
- }
- }
- }
-
-
-
- }
(3)在随机生成地图的脚本中加入随机生成各种平台(可忽略)
首先新建一个空物体用来统一管理生成平台预制体,及其图层
地图生成脚本更新(在调试阶段为了快速生成地图,我将yeid return从for循环外移出去了,以后写加载条的时候可以再放回去减少性能消耗)
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.Tilemaps;
- public class GroundTIleMap : MonoBehaviour
- {
- public Tilemap ground_tilemap;//拖动获取地面瓦片地图
- public Tilemap environ_tilemap;
- public TileBase groundTile;//拖动获取地面规则瓦片Rule Tile,-
- public TileBase environTile;//拖动获取环境规则瓦片
- public Vector2Int lowerLeftCoo = new Vector2Int(-18, -7);//地图起始左下角位置
- public int width = 20;//地图宽度
- public int length = 100;//地图长度
- public float groundStartPro = 0.10f;//生成地面起始点的概率
- public Vector2Int groundLenRan = new Vector2Int(3, 10);//起始地面点生成的长度范围
- public float environmentRich = 0.5f;//环境丰富度
- public GameObject[] itemsRandom;//随机物品组
- public float itemsRich = 0.05f;//随机物品丰富度
- public GameObject[] platformRandom;//随机平台组
- public float platformRich = 0.05f;//平台丰富度
- private List<Vector2Int> canEndPlat=new List<Vector2Int>();//可能的平台终点落点
- // Start is called before the first frame update
- void Start()
- {
-
- StartCoroutine(GroundStartPoi());
- }
-
- // Update is called once per frame
- IEnumerator GroundStartPoi()//生成地面起始点 用协程可以缓慢一步步生成地图,性能消耗少
- {
- yield return null;
- ground_tilemap.ClearAllTiles();
- Debug.Log("开始生成地面");
- for (int i = lowerLeftCoo.x; i < (this.length + lowerLeftCoo.x); i++)
- {
- for (int j = lowerLeftCoo.y; j < (this.width + lowerLeftCoo.y); j++)
- {
- /* yield return null;*/
- bool IsGround = j < (this.width + 3) ? (Random.value <= groundStartPro) : (Random.value <= groundStartPro + 0.05);//三元表达式,地面三行更容易生成地面起始点
- if (IsGround) StartCoroutine(GroundExtension(i, j));
-
- }
- }
- Debug.Log("结束生成地面");
- StartCoroutine(ClearChannel());//执行完GroundStartPoi(),GroundExtension()生成地面,开始清除产生能走的通道
-
- }
- IEnumerator GroundExtension(int i, int j)//从地面起始点开始延长
- {
- yield return null;
- int groundLen = Random.Range(groundLenRan.x, groundLenRan.y);
- for (int m = i; m <= i + groundLen; m++)
- {
- /* yield return null;*/
- ground_tilemap.SetTile(new Vector3Int(m, j, 0), groundTile);
- }
-
- }
- //清除,产生通道,思路就是从底层有方块的地方,开始判断上面。如果没有方块的话,就清除俩层/三层的通道
- IEnumerator ClearChannel()
- {
- yield return null;
- Debug.Log("开始产生通道");
- for (int i = lowerLeftCoo.x; i < (this.length + lowerLeftCoo.x); i++)
- {
- for (int j = lowerLeftCoo.y; j < (this.width + lowerLeftCoo.y - 1); j++)//最高层上面必然没有方块,不需要判断,-1
- {
- if (ground_tilemap.GetTile(new Vector3Int(i, j, 0)) != null&& ground_tilemap.GetTile(new Vector3Int(i, j + 1, 0)) == null)//如果此处不为空方块,如果此处上方为空方块
- {
-
- ground_tilemap.SetTilesBlock(new BoundsInt(i - 2, j + 1, 0, 3, 3, 1), new TileBase[] { null, null, null, null, null, null, null, null, null });//将上方3x3格子置空
-
- }
- /* yield return null;*/
-
- }
- }
- Debug.Log("产生通道完成");
- StartCoroutine(GeneratePlatform());
-
- }
-
- IEnumerator GeneratePlatform()
- {
- yield return null;
- Debug.Log("开始生成平台");
- for (int i = lowerLeftCoo.x; i < (this.length + lowerLeftCoo.x); i++)
- {
- for (int j = lowerLeftCoo.y; j < (this.width + lowerLeftCoo.y); j++)
- {//Y平台
- if (ground_tilemap.GetTile(new Vector3Int(i, j, 0)) != null && ground_tilemap.GetTile(new Vector3Int(i, j + 1, 0)) == null)//起点 如果此处不为空方块,且上方为空
- {
- if (Random.value < platformRich*0.5)//随机
- {
- Vector2Int EndCellPoi = CanYEndPlat(i, j);
- if (EndCellPoi != Vector2Int.zero)//当y轴落点不为0,即列表返回了一个数据
- {
- //清空目标点与生成点的地面瓦片
- for (int n = j + 1; n < EndCellPoi.y; n++)
- {
- ground_tilemap.SetTile(new Vector3Int(i, n, 0), null);
-
- }
- //随机选取平台生成
- int r = Random.Range(0, platformRandom.Length);
- GameObject plat = platformRandom[r];
- plat.GetComponent<platformMove>().EndPoi = ground_tilemap.CellToWorld(new Vector3Int(EndCellPoi.x, EndCellPoi.y, 0));//平台赋值终点 转世界坐标
- //生成平台在PlatForm下统一图层管理
- Instantiate(plat, ground_tilemap.CellToWorld(new Vector3Int(i, j + 1, 0)), Quaternion.Euler(0, 0, 0), GameObject.Find("PlatForm").transform);
- };
- }
-
- }
- //X平台
- if (ground_tilemap.GetTile(new Vector3Int(i, j, 0)) == null && ground_tilemap.GetTile(new Vector3Int(i-1, j , 0)) == null)//起点 如果此处为空方块,且左边方为空
- {
- Debug.Log( " " + new Vector3Int(i, j, 0));
- if (Random.value < platformRich * 0.5)//随机
- {
- Vector2Int EndCellPoi = CanXEndPlat(i, j);
- if (EndCellPoi != Vector2Int.zero)//当x轴落点不为0,即列表返回了一个数据
- {
- //清空目标点与生成点的地面瓦片
- for (int m = i + 1; m < EndCellPoi.x; m++)
- {
- ground_tilemap.SetTile(new Vector3Int(m, j, 0), null);
- }
- //随机选取平台生成
- Debug.Log(EndCellPoi + " " + new Vector3Int(i, j, 0));
- int r = Random.Range(0, platformRandom.Length);
- GameObject plat = platformRandom[r];
- plat.GetComponent<platformMove>().EndPoi = ground_tilemap.CellToWorld(new Vector3Int(EndCellPoi.x, EndCellPoi.y, 0));//平台赋值终点 转世界坐标
- //生成平台在PlatForm下统一图层管理
- Instantiate(plat, ground_tilemap.CellToWorld(new Vector3Int(i, j, 0)), Quaternion.Euler(0, 0, 0), GameObject.Find("PlatForm").transform);
- };
- }
- }
- }
- }
- Debug.Log("结束生成平台");
- StartCoroutine(GenerateEnviron());
-
- }
- //可能Y轴上平台终点落点
- private Vector2Int CanYEndPlat(int i, int j)
- {
- canEndPlat.Clear();//清空可能终点落点列表
- for (int y = i + 1; y < (this.width + lowerLeftCoo.y); y++)
- {
- if (((ground_tilemap.GetTile(new Vector3Int(i, y, 0)) != null && ground_tilemap.GetTile(new Vector3Int(i, y + 1, 0)) == null)
- || (ground_tilemap.GetTile(new Vector3Int(i, y, 0)) == null && (ground_tilemap.GetTile(new Vector3Int(i - 1, y, 0)) != null || ground_tilemap.GetTile(new Vector3Int(i + 1, y, 0)) != null))
- )&& Mathf.Abs(y - j) > 3)//找到起点头上的方块为可能落点(起码要高度大于三)(有方块且头顶为空方块的地方,没方块的地方隔壁却有方块的地方)
- {
- canEndPlat.Add(new Vector2Int(i, y+1));
- }
- }
- if (canEndPlat.Count != 0)
- { //随机返回一个可能落点
- int r = Random.Range(0, canEndPlat.Count);
- return canEndPlat[r];
- }
- else { return Vector2Int.zero; }
-
- }
- //可能X轴上平台终点落点
- private Vector2Int CanXEndPlat(int i,int j) {
- canEndPlat.Clear();//清空可能终点落点列表
- for(int x=i+1;x< (this.length + lowerLeftCoo.x); x++)
- {
- if ((ground_tilemap.GetTile(new Vector3Int(x, j - 2, 0)) == null && ground_tilemap.GetTile(new Vector3Int(x, j +2, 0)) == null
- && ground_tilemap.GetTile(new Vector3Int(x, j -1, 0)) == null && ground_tilemap.GetTile(new Vector3Int(x, j+1, 0)) == null
- && ground_tilemap.GetTile(new Vector3Int(x, j, 0)) == null && ground_tilemap.GetTile(new Vector3Int(x+1,j , 0)) != null)
- &&(Mathf.Abs(x - i) > 2&& Mathf.Abs(x - i)< 6))//可能落点上下各俩块都为空,且右边有方块
- {
- canEndPlat.Add(new Vector2Int(x, j));
- }
- }
- if(canEndPlat.Count!=0)
- { //随机返回一个可能落点
- int r = Random.Range(0, canEndPlat.Count);
- return canEndPlat[r];
- }
- else { return Vector2Int.zero; }
-
- }
-
- //生成环境
- IEnumerator GenerateEnviron()
- {
- yield return null;
- Debug.Log("开始生成花草和随机物品");
- for (int i = lowerLeftCoo.x; i < (this.length + lowerLeftCoo.x); i++)
- {
- for (int j = lowerLeftCoo.y; j < (this.width + lowerLeftCoo.y); j++)
- {
- if (ground_tilemap.GetTile(new Vector3Int(i, j, 0)) == groundTile && ground_tilemap.GetTile(new Vector3Int(i, j + 1, 0)) == null)//如果此处为地面,上面为空方块
- {
- //花草
- if (Random.value < environmentRich)//随机
- { environ_tilemap.SetTile(new Vector3Int(i, j + 1, 0), environTile); }
- //随机物品
- if (Random.value < itemsRich)//随机
- {
- int index = Random.Range(0, itemsRandom.Length );
- Instantiate(itemsRandom[index], environ_tilemap.CellToWorld(new Vector3Int(i, j + 2, 0)), Quaternion.Euler(0, 0, 0),GameObject.Find("ItemsManager").transform); //坐标转换}
- }
- /* yield return null;*/
-
- }
- }
- }
- Debug.Log("结束生成花草和随机物品");
- }
-
- }
-
2.双向平台(实际是在按住下键瞬间取消了角色的碰撞体)
给平台预制体添加标签platform
角色的控制脚本更新
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- public class playerControl : MonoBehaviour
- {
-
- private Rigidbody2D rig;//2D刚体
- private CharacterPanel characterPanel;//获取角色面板
- private Animator ani;//动画控制器
- private AnimatorStateInfo state;//动画状态
- // Start is called before the first frame update
- void Start()
- {
- rig = GetComponent<Rigidbody2D>();//获取刚体
- ani = GetComponent<Animator>();//获取动画控制器
- characterPanel = GetComponent<CharacterPanel>();//获取角色面板
-
-
- }
-
- // Update is called once per frame
- void Update()
- {
- move();//移动函数
- attack();//攻击函数-四连击
-
- }
- private void move()
- { //水平,垂直俩个轴系
- float h = Input.GetAxis("Horizontal");
- float v = Input.GetAxis("Vertical");
- float dir = Input.GetAxisRaw("Horizontal");//方向-1 0 1
-
- //跳跃
- if (v > 0 && transform.GetComponent<CharacterPanel>().Iscanjump == true)
- {
- characterPanel.jump();
- }
- //长按高跳
- if (rig.velocity.y > 0 && Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow) && transform.GetComponent<CharacterPanel>().Iscanjump == false)
- {
- rig.velocity += Vector2.up * 0.2f;//长按高跳额外得到向上速度
- }
-
- //方向改变
- if (dir != 0)
- {
- transform.localScale = new Vector3(dir * Mathf.Abs(transform.localScale.x), transform.localScale.y, transform.localScale.z);//通过改变scale改变方向
- }
- //按键左右移动
- Vector3 vt = new Vector3(h, 0, 0).normalized;//vt为俩个轴系合成的方向向量,normalized单位化
- //移动动画
- if (dir != 0)
- {
- ani.SetBool("Ismove", true);
- }
- else { ani.SetBool("Ismove", false); }
-
- //空中左右移动,为地面jumpcharacterPanel.speedVertiacal倍
- if (h != 0 && transform.GetComponent<CharacterPanel>().Iscanjump == false)
- {
- gameObject.transform.Translate(vt * characterPanel.speed * characterPanel.jumpspeedVertiacal * Time.deltaTime);//通过这个函数来使用vt使得左右移动
- }
- //地面左右移动
- else { gameObject.transform.Translate(vt * characterPanel.speed * Time.deltaTime); }
-
- //按下键从平台上跳下来
- if (Input.GetKeyDown(KeyCode.S))
- {
- RaycastHit2D hit = Physics2D.Raycast(transform.position, new Vector2(0, -1),
- Mathf.Infinity, LayerMask.GetMask("tilemap"));
- if (hit.collider.CompareTag("platform"))//检测到平台
- {
- transform.GetComponent<Collider2D>().enabled = false;
- StartCoroutine(EnableCollider());
-
- }
- Debug.DrawRay(transform.position, new Vector2(0, -1), Color.blue);
- }
-
-
-
- }
- IEnumerator EnableCollider()
- {
- yield return new WaitForSeconds(0.15f);
- transform.GetComponent<Collider2D>().enabled = true;
-
- }
- private void attack() {
-
-
- state = ani.GetCurrentAnimatorStateInfo(0);
- //判断播放完
- if ((state.IsName("attack1") || state.IsName("attack2") || state.IsName("attack3") || state.IsName("attack4")) && state.normalizedTime >= 1.0f)
- {
-
- ani.SetInteger("attack", 0);
-
- }
-
- if (Input.GetKey(KeyCode.J))
- {
-
- if (state.IsName("idle")|| state.IsName("move") && ani.GetInteger("attack")==0 )
- {
-
- ani.SetInteger("attack", 1);
- } else if (state.IsName("attack1")&& ani.GetInteger("attack") == 1 )
- {
- ani.SetInteger("attack", 2);
- }
- else if (state.IsName("attack2") && ani.GetInteger("attack") == 2)
- {
- ani.SetInteger("attack", 3);
- }
- else if (state.IsName("attack3") && ani.GetInteger("attack") == 3)
- {
- ani.SetInteger("attack", 4);
- }
- }
- if (state.normalizedTime >=1.0f)
- {
- ani.SetFloat("normalizedTime", state.normalizedTime);
- }
-
-
- }
- }
为了防止平台到地面时按下键取消碰撞盒导致穿过地面,和由于离散检测敌人和角色有时会穿过地面,我们可以在角色共有脚中加入防本卡脱离机制,也是通过射线检测完成的
CharacterPanel脚步更新(如果不管这个bug可以忽略此内容)
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.UI;
- public class CharacterPanel : MonoBehaviour
- {
- public bool Iscanjump = false;//是否能跳跃,默认不能
- public float Hpmax = 100;//最大生命
- public float Hp = 100;//生命
- public float Atk = 10;//攻击力
- public float AtkRan = 1;//攻击浮动
- public float Def = 10;//防御力
- public float lookdir;//获取初始Scale.x,用于转向
- public float dropConst = 15;//下坠常数
- public float speed = 10;//地面移动速度
- public float jumpspeedUp = 20;//上升速度
- public float jumpspeedVertiacal = 0.5f;//空中左右移动速度
- private Rigidbody2D rig;//2D刚体
- private Animator ani;
- private Transform Canvas;//获取角色个人UI面板
- // Start is called before the first frame update
- void Start()
- {
- Canvas = transform.Find("Canvas");
- ani = transform.GetComponent<Animator>();
-
- rig = GetComponent<Rigidbody2D>();//获取刚体
- }
-
- // Update is called once per frame
- void FixedUpdate()
- {
- lookdir = transform.localScale.x;
- check();
- //跳跃优化手感
- Quickjump();
- //检测卡地面脱离
- OutLockGround();
- }
- //标准化检查
- private void check()
- {
- if (Hp > Hpmax) Hp = Hpmax;//血量不超过上限
- if (Hp <= 0) { Hp = 0;death(); };//血量不超过下限,且死亡
- }
- //受伤,其他脚本调用
- public void hurt(float atk)
- {
- float hurtnum= (20 * atk / (20 + Def)) + Random.Range(-AtkRan, AtkRan);
- //受伤动画 为什么要将触发器中的受伤动画移动在角色面板的hurt函数下?因为受伤不仅有角色攻击受伤,还有陷阱,掉落等伤害,所以直接血量减少时播放受伤动画更加好
- transform.GetComponent<Animator>().SetBool("Ishurt", true);
- StartCoroutine(endHurt());//开启协程结束受伤动画
- //伤害数值显示
- Canvas.GetComponent<CharaCanvas>().ShowHurtText(hurtnum);
- Hp -= hurtnum;
-
-
- }
- IEnumerator endHurt()
- {
- yield return 0;//此处暂停,下一帧执行
- transform.GetComponent<Animator>().SetBool("Ishurt", false);
- }
- //死亡
- private void death()
- {
- ani.SetBool("Isdeath",true);
- }
- //跳跃
- public void jump()
- {
- if (Iscanjump == true)
- {
- rig.velocity = new Vector2(0, jumpspeedUp);//设置刚体速度,给予向量
- }
- }
- //优化跳跃手感,迅速下落,放入帧频率更新函数里面
- public void Quickjump()
- {
- float a = dropConst * 5 - Mathf.Abs(rig.velocity.y);//通过下坠常数,空中速度快为0时,下坠常数a越大,即越快速 度过这个状态
- rig.velocity -= Vector2.up * a * Time.deltaTime;
- }
- //卡住地面脱离
- public void OutLockGround()
- {
- RaycastHit2D hit = Physics2D.Raycast(transform.position, new Vector2(0, -1),
- 0.5f, LayerMask.GetMask("tilemap"));
- if (hit.collider.CompareTag("ground"))//检测到地面
- {
- transform.Translate(new Vector3(0,1,0));
- }
- Debug.DrawRay(transform.position, new Vector2(0, -1), Color.blue);
-
- }
-
-
-
-
-
-
-
-
-
- //以下是敌人的调运行为函数,主角有自己的控制脚本
- //转向
- public void Turndir()
- {
- transform.localScale = new Vector3(-lookdir, transform.localScale.y, transform.localScale.z);//通过改变scale改变方向
- }
- //移动
- public void move()
- {
- if (lookdir == 0) lookdir =0.001f;//预防除零问题弹错
- Vector3 vt = new Vector3(lookdir/Mathf.Abs(lookdir), 0, 0);
- //空中左右移动,为地面jumpcharacterPanel.speedVertiacal倍
- if (Iscanjump == false)
- {
- gameObject.transform.Translate(jumpspeedVertiacal * speed * Time.deltaTime * vt);//通过这个函数来使用vt使得左右移动
- }
- //地面左右移动
- else { gameObject.transform.Translate(speed * Time.deltaTime * vt); }
- ani.SetBool("Ismove", true);
-
- }
- //等待
- public void idle()
- {
- ani.SetBool("Ismove", false);
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。