总目录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)在随机生成地图的脚本中加入随机生成各种平台(可忽略)
12.Unity2D 横版 TileMap随机生成简易横版瓦片地图+随机生成环境(花草树石)精灵图+2d-extras+协程的应用_ζั͡ ั͡雾 ั͡狼 ั͡✾的博客-CSDN博客_tilemap 随机Unity2D 横版 TileMap随机生成简易横版瓦片地图+随机生成环境(花草树石)精灵图+2d-extras+协程的应用。思路就是先生成瓦片,再进行创建能通过的通道。生成瓦片的时候先随机生成瓦片起始点,再通过一个协程从瓦片起始点开始延长不等长度(因为是横版游戏,在横向上的延伸要求高)注意需要修改环境瓦片地图的平铺轴和精灵图片的锚点位置,确保随机生成的精灵图是接触与地面而不是悬空的。(由于随机生成的环境精灵图大小不一样,但锚点都在中间,所以需要修改锚点) ...https://wulang.blog.csdn.net/article/details/125777090?spm=1001.2014.3001.5502
首先新建一个空物体用来统一管理生成平台预制体,及其图层
地图生成脚本更新(在调试阶段为了快速生成地图,我将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);
}
}
}
为了防止平台到地面时按下键取消碰撞盒导致穿过地面,和由于离散检测敌人和角色有时会穿过地面,我们可以在角色共有脚中加入防本卡脱离机制,也是通过射线检测完成的文章来源:https://www.toymoban.com/news/detail-444383.html
CharacterPanel脚步更新(如果不管这个bug可以忽略此内容)文章来源地址https://www.toymoban.com/news/detail-444383.html
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);
}
}
到了这里,关于13.Unity2D 横版 可上下左右移动的双向平台(双向行走+可移动+单独判定)+随机平台生成的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!