提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、扩展小骑士落地Land和重落地HardLand行为
- 1.制作动画以及使用UNITY编辑器编辑
- 2.使用代码实现扩展新的落地行为和重落地行为
- 二、扩展小骑士冲刺Dash行为
- 1.制作动画以及使用UNITY编辑器编辑
- 2.使用代码实现扩展新的冲刺行为
- 总结
前言
不知不觉已经出了八期从零开始制作空洞骑士的教学,没想到我还能坚持下去,同时我的阅读量已经到达了20w之多,也是感谢大家的支持吧。闲言少叙,这期我们就来扩展小骑士落地Land和重落地HardLand行为以及冲刺Dash行为。这其中涉及到素材的导入,创建tk2dSprite和tk2dSpriteAnimation,以及代码控制行为等等,难度适中,希望你能够耐心阅读并理解我的意思。
一、扩展小骑士落地Land和重落地HardLand行为
1.制作动画以及使用UNITY编辑器编辑
在做之前你可能会好奇,我上一期不是做了跳跃落地行为以及冲刺行为吗,怎么还要做,这其实还是你要玩游戏才能知道的,小骑士的落地分为两种行为,通过高度或者下降时间来判断,下降时间 小于设定的值就会是轻落地softLand,大于则是重落地HardLand,对应的行为也不一样,dash也是同理,当你装备了冲刺大师这个护符,你就可以按出向下冲刺的DownDash行为,因此这期的目标就是来实现它们的。
我们先把素材导入后,开始回到tk2dspriteEditor中,由于我们第二期就已经制作了小骑士的spritecollection和spriteanimation,所以我们直接把图片拖进去即可。
2.使用代码实现扩展新的落地行为和重落地行为
回到代码中,对于HeroAudioController.cs添加几个新的音效管理AudioSource即可,相信看过前几期的已经知道怎么操作了:
using System.Collections;
using System.Collections.Generic;
using GlobalEnums;
using UnityEngine;
public class HeroAudioController : MonoBehaviour
{
private HeroController heroCtrl;
private void Awake()
{
heroCtrl = GetComponent<HeroController>();
}
[Header("Sound Effects")]
public AudioSource softLanding;
public AudioSource hardLanding;
public AudioSource jump;
public AudioSource footStepsRun;
public AudioSource footStepsWalk;
public AudioSource falling;
public AudioSource backDash;
public AudioSource dash;
private Coroutine fallingCo;
public void PlaySound(HeroSounds soundEffect)
{
if(!heroCtrl.cState.isPaused)
{
switch (soundEffect)
{
case HeroSounds.FOOTSETP_RUN:
if(!footStepsRun.isPlaying && !softLanding.isPlaying)
{
footStepsRun.Play();
return;
}
break;
case HeroSounds.FOOTSTEP_WALK:
if (!footStepsWalk.isPlaying && !softLanding.isPlaying)
{
footStepsWalk.Play();
return;
}
break;
case HeroSounds.SOFT_LANDING:
RandomizePitch(softLanding, 0.9f, 1.1f);
softLanding.Play();
break;
case HeroSounds.HARD_LANDING:
hardLanding.Play();
break;
case HeroSounds.JUMP:
RandomizePitch(jump, 0.9f, 1.1f);
jump.Play();
break;
case HeroSounds.BACK_DASH:
backDash.Play();
break;
case HeroSounds.DASH:
dash.Play();
break;
case HeroSounds.FALLING:
fallingCo = StartCoroutine(FadeInVolume(falling, 0.7f));
falling.Play();
break;
default:
break;
}
}
}
public void StopSound(HeroSounds soundEffect)
{
if(soundEffect == HeroSounds.FOOTSETP_RUN)
{
footStepsRun.Stop();
return;
}
if (soundEffect == HeroSounds.FOOTSTEP_WALK)
{
footStepsWalk.Stop();
return;
}
switch (soundEffect)
{
case HeroSounds.FALLING:
falling.Stop();
if(fallingCo != null)
{
StopCoroutine(fallingCo);
}
return;
default:
return;
}
}
public void StopAllSounds()
{
softLanding.Stop();
hardLanding.Stop();
jump.Stop();
falling.Stop();
backDash.Stop();
dash.Stop();
footStepsRun.Stop();
footStepsWalk.Stop();
}
public void PauseAllSounds()
{
softLanding.Pause();
hardLanding.Pause();
jump.Pause();
falling.Pause();
backDash.Pause();
dash.Pause();
footStepsRun.Pause();
footStepsWalk.Pause();
}
public void UnPauseAllSounds()
{
softLanding.UnPause();
hardLanding.UnPause();
jump.UnPause();
falling.UnPause();
backDash.UnPause();
dash.UnPause();
footStepsRun.UnPause();
footStepsWalk.UnPause();
}
/// <summary>
/// 音量淡入线性插值的从0到1
/// </summary>
/// <param name="src"></param>
/// <param name="duration"></param>
/// <returns></returns>
private IEnumerator FadeInVolume(AudioSource src, float duration)
{
float elapsedTime = 0f;
src.volume = 0f;
while (elapsedTime < duration)
{
elapsedTime += Time.deltaTime;
float t = elapsedTime / duration;
src.volume = Mathf.Lerp(0f, 1f, t);
yield return null;
}
}
/// <summary>
/// 随机旋转一个在和之间的pitch的值返回给audiosource
/// </summary>
/// <param name="src"></param>
/// <param name="minPitch"></param>
/// <param name="maxPitch"></param>
private void RandomizePitch(AudioSource src, float minPitch, float maxPitch)
{
float pitch = Random.Range(minPitch, maxPitch);
src.pitch = pitch;
}
/// <summary>
/// 重置audiosource的pitch
/// </summary>
/// <param name="src"></param>
private void ResetPitch(AudioSource src)
{
src.pitch = 1f;
}
}
然后到HeroController.cs我们需要
记录下落时间的属性fallTimer,
正在hardLanding的计时器,大于就将状态改为grounded并BackOnGround()的private float hardLandingTimer;
private float hardLandFailSafeTimer; 进入hardLand后玩家失去输入的一段时间
private bool hardLanded; //是否已经hardLand了
public float HARD_LANDING_TIME; //正在hardLanding花费的时间。
public float BIG_FALL_TIME; //判断是否是hardLanding所需要的事件,大于它就是
public GameObject hardLandingEffectPrefab; //重落地生成的特效游戏对象
cstate添加新的状态cState.willHardLand。
首先我们来完善在Update中的FallCheck()函数:判断是否切入到willHardLand的状态
private void FallCheck()
{
//如果y轴上的速度小于-1E-06F判断是否到地面上了
if (rb2d.velocity.y < -1E-06F)
{
if (!CheckTouchingGround())
{
cState.falling = true;
cState.onGround = false;
if(hero_state != ActorStates.no_input)
{
SetState(ActorStates.airborne);
}
fallTimer += Time.deltaTime;
if(fallTimer > BIG_FALL_TIME)
{
if (!cState.willHardLand)
{
cState.willHardLand = true;
}
if (!fallRumble)
{
StartFallRumble();
}
}
}
}
else
{
cState.falling = false;
fallTimer = 0f;
if (fallRumble)
{
CancelFallEffects();
}
}
}
在Upate中增加计时器,等到达HARD_LANDING_TIME时间后,才能进入新的状态:
if (hero_state == ActorStates.hard_landing)
{
hardLandingTimer += Time.deltaTime;
if (hardLandingTimer > HARD_LANDING_TIME)
{
SetState(ActorStates.grounded);
BackOnGround();
}
}
回到BackOnGround()我们来完善这个函数主要是重新初始化Land相关的参数。
public void BackOnGround()
{
if(landingBufferSteps <= 0)
{
landingBufferSteps = LANDING_BUFFER_STEPS;
if(!cState.onGround && !hardLanded)
{
Instantiate(softLandingEffectPrefab, transform.position,Quaternion.identity); //TODO:
}
}
cState.falling = false;
fallTimer = 0f;
dashLandingTimer = 0f;
cState.willHardLand = false;
hardLandingTimer = 0f;
hardLanded = false;
jump_steps = 0;
SetState(ActorStates.grounded);
cState.onGround = true;
airDashed = false;
}
在Update中还有一个函数要完善:
private void FailSafeCheck()
{
if(hero_state == ActorStates.hard_landing)
{
hardLandFailSafeTimer += Time.deltaTime;
if(hardLandFailSafeTimer > HARD_LANDING_TIME + 0.3f)
{
SetState(ActorStates.grounded);
BackOnGround();
hardLandFailSafeTimer = 0f;
}
}
else
{
hardLandFailSafeTimer = 0f;
}
}
private void DoHardLanding()
{
AffectedByGravity(true);
ResetInput();
SetState(ActorStates.hard_landing);
hardLanded = true;
audioCtrl.PlaySound(HeroSounds.HARD_LANDING);
Instantiate(hardLandingEffectPrefab, transform.position,Quaternion.identity);
}
public void ResetHardLandingTimer()
{
cState.willHardLand = false;
hardLandingTimer = 0f;
fallTimer = 0f;
hardLanded = false;
}
最终重落地的行为在OnCollisionEnter2D和OnCollisionStay2D函数中实现,是底部碰到地面后执行DoHardLanding()函数
private void OnCollisionEnter2D(Collision2D collision)
{
if(collision.gameObject.layer == LayerMask.NameToLayer("Terrain") && collision.gameObject.CompareTag("HeroWalkable") && CheckTouchingGround())
{
}
if(hero_state != ActorStates.no_input)
{
if(collision.gameObject.layer == LayerMask.NameToLayer("Terrain") || collision.gameObject.CompareTag("HeroWalkable"))
{
CollisionSide collisionSide = FindCollisionSide(collision);
//如果头顶顶到了
if (collisionSide == CollisionSide.top)
{
if (cState.jumping)
{
CancelJump();
}
}
//如果底下碰到了
if (collisionSide == CollisionSide.bottom)
{
if(ShouldHardLand(collision))
{
DoHardLanding();
}
else if(collision.gameObject.GetComponent<SteepSlope>() == null && hero_state != ActorStates.hard_landing)
{
BackOnGround();
}
if(cState.dashing && dashingDown)
{
AffectedByGravity(true);
SetState(ActorStates.dash_landing);
hardLanded = true;
return;
}
}
}
}
else if(hero_state == ActorStates.no_input)
{
}
}
private void OnCollisionStay2D(Collision2D collision)
{
if(hero_state != ActorStates.no_input && collision.gameObject.layer == LayerMask.NameToLayer("Terrain"))
{
if (collision.gameObject.GetComponent<NonSlider>() == null)
{
if (CheckStillTouchingWall(CollisionSide.left, false))
{
cState.touchingWall = true;
touchingWallL = true;
touchingWallR = false;
}
else if (CheckStillTouchingWall(CollisionSide.right, false))
{
cState.touchingWall = true;
touchingWallL = false;
touchingWallR = true;
}
else
{
cState.touchingWall = false;
touchingWallL = false;
touchingWallR = false;
}
if (CheckTouchingGround())
{
if (ShouldHardLand(collision))
{
DoHardLanding();
}
if(hero_state != ActorStates.hard_landing && hero_state != ActorStates.dash_landing && cState.falling)
{
BackOnGround();
return;
}
}
else if(cState.jumping || cState.falling)
{
cState.onGround = false;
SetState(ActorStates.airborne);
return;
}
}
else
{
}
}
}
完整的HeroController.cs如下所示:
using System;
using System.Collections;
using System.Collections.Generic;
using HutongGames.PlayMaker;
using GlobalEnums;
using UnityEngine;
public class HeroController : MonoBehaviour
{
public ActorStates hero_state;
public ActorStates prev_hero_state;
public bool acceptingInput = true;
public float move_input;
public float vertical_input;
private Vector2 current_velocity;
public float WALK_SPEED = 3.1f;//走路速度
public float RUN_SPEED = 5f;//跑步速度
public float JUMP_SPEED = 5f;//跳跃的食欲
private int jump_steps; //跳跃的步
private int jumped_steps; //已经跳跃的步
private int jumpQueueSteps; //跳跃队列的步
private bool jumpQueuing; //是否进入跳跃队列中
private int jumpReleaseQueueSteps; //释放跳跃后的步
private bool jumpReleaseQueuing; //是否进入释放跳跃队列中
private bool jumpReleaseQueueingEnabled; //是否允许进入释放跳跃队列中
public float MAX_FALL_VELOCITY; //最大下落速度(防止速度太快了)
public int JUMP_STEPS; //最大跳跃的步
public int JUMP_STEPS_MIN; //最小跳跃的步
private int JUMP_QUEUE_STEPS; //最大跳跃队列的步
private int JUMP_RELEASE_QUEUE_STEPS;//最大跳跃释放队列的步
private int dashQueueSteps;
private bool dashQueuing;
private float dashCooldownTimer; //冲刺冷却时间
private float dash_timer; //正在冲刺计数器
private bool airDashed;//是否是在空中冲刺
public PlayMakerFSM dashBurst;
public GameObject dashParticlesPrefab;//冲刺粒子效果预制体
public float DASH_SPEED; //冲刺时的速度
public float DASH_TIME; //冲刺时间
public float DASH_COOLDOWN; //冲刺冷却时间
public int DASH_QUEUE_STEPS; //最大冲刺队列的步
public float fallTimer { get; private set; }
private float hardLandingTimer; //正在hardLanding的计时器,大于就将状态改为grounded并BackOnGround()
private float hardLandFailSafeTimer; //进入hardLand后玩家失去输入的一段时间
private bool hardLanded; //是否已经hardLand了
public float HARD_LANDING_TIME; //正在hardLanding花费的时间。
public float BIG_FALL_TIME; //判断是否是hardLanding所需要的事件,大于它就是
public GameObject hardLandingEffectPrefab;
private float prevGravityScale;
private int landingBufferSteps;
private int LANDING_BUFFER_STEPS = 5;
private bool fallRumble; //是否开启掉落时相机抖动
public GameObject softLandingEffectPrefab;
public bool touchingWall; //是否接触到墙
public bool touchingWallL; //是否接触到的墙左边
public bool touchingWallR; //是否接触到的墙右边
private Rigidbody2D rb2d;
private BoxCollider2D col2d;
private GameManager gm;
public PlayerData playerData;
private InputHandler inputHandler;
public HeroControllerStates cState;
private HeroAnimationController animCtrl;
private HeroAudioController audioCtrl;
private static HeroController _instance;
public static HeroController instance
{
get
{
if (_instance == null)
_instance = FindObjectOfType<HeroController>();
if(_instance && Application.isPlaying)
{
DontDestroyOnLoad(_instance.gameObject);
}
return _instance;
}
}
public HeroController()
{
JUMP_QUEUE_STEPS = 2;
JUMP_RELEASE_QUEUE_STEPS = 2;
LANDING_BUFFER_STEPS = 5;
}
private void Awake()
{
if(_instance == null)
{
_instance = this;
DontDestroyOnLoad(this);
}
else if(this != _instance)
{
Destroy(gameObject);
return;
}
SetupGameRefs();
}
private void SetupGameRefs()
{
if (cState == null)
cState = new HeroControllerStates();
rb2d = GetComponent<Rigidbody2D>();
col2d = GetComponent<BoxCollider2D>();
animCtrl = GetComponent<HeroAnimationController>();
audioCtrl = GetComponent<HeroAudioController>();
gm = GameManager.instance;
playerData = PlayerData.instance;
inputHandler = gm.GetComponent<InputHandler>();
}
void Start()
{
playerData = PlayerData.instance;
if (dashBurst == null)
{
Debug.Log("DashBurst came up null, locating manually");
dashBurst = FSMUtility.GetFSM(transform.Find("Effects").Find("Dash Burst").gameObject);
}
}
void Update()
{
current_velocity = rb2d.velocity;
FallCheck();
FailSafeCheck();
if (hero_state == ActorStates.running && !cState.dashing && !cState.backDashing)
{
if (cState.inWalkZone)
{
audioCtrl.StopSound(HeroSounds.FOOTSETP_RUN);
audioCtrl.PlaySound(HeroSounds.FOOTSTEP_WALK);
}
else
{
audioCtrl.StopSound(HeroSounds.FOOTSTEP_WALK);
audioCtrl.PlaySound(HeroSounds.FOOTSETP_RUN);
}
}
else
{
audioCtrl.StopSound(HeroSounds.FOOTSETP_RUN);
audioCtrl.StopSound(HeroSounds.FOOTSTEP_WALK);
}
if (hero_state == ActorStates.hard_landing)
{
hardLandingTimer += Time.deltaTime;
if (hardLandingTimer > HARD_LANDING_TIME)
{
SetState(ActorStates.grounded);
BackOnGround();
}
}
if (hero_state == ActorStates.no_input)
{
}
else if (hero_state != ActorStates.no_input)
{
LookForInput();
}
LookForQueueInput();
if (dashCooldownTimer > 0f) //计时器在Update中-= Time.deltaTime
{
dashCooldownTimer -= Time.deltaTime;
}
}
private void FixedUpdate()
{
if(hero_state == ActorStates.hard_landing)
{
ResetMotion();
}
else if(hero_state == ActorStates.no_input)
{
}
else if (hero_state != ActorStates.no_input)
{
if(hero_state == ActorStates.running)
{
if(move_input > 0f)
{
if (CheckForBump(CollisionSide.right))
{
//rb2d.velocity = new Vector2(rb2d.velocity.x, BUMP_VELOCITY);
}
}
else if (CheckForBump(CollisionSide.left))
{
//rb2d.velocity = new Vector2(rb2d.velocity.x, -BUMP_VELOCITY);
}
}
}
if (cState.jumping) //如果cState.jumping就Jump
{
Jump();
}
if (cState.dashing)//如果cState.dashing就Dash
{
Dash();
}
//限制速度
if(rb2d.velocity.y < -MAX_FALL_VELOCITY)
{
rb2d.velocity = new Vector2(rb2d.velocity.x, -MAX_FALL_VELOCITY);
}
if (jumpQueuing)
{
jumpQueueSteps++;
}
if (dashQueuing) //跳跃队列开始
{
dashQueueSteps++;
}
if (landingBufferSteps > 0)
{
landingBufferSteps--;
}
if (jumpReleaseQueueSteps > 0)
{
jumpReleaseQueueSteps--;
}
cState.wasOnGround = cState.onGround;
}
/// <summary>
/// 小骑士移动的函数
/// </summary>
/// <param name="move_direction"></param>
private void Move(float move_direction)
{
if (cState.onGround)
{
SetState(ActorStates.grounded);
}
if(acceptingInput)
{
if (cState.inWalkZone)
{
rb2d.velocity = new Vector2(move_direction * WALK_SPEED, rb2d.velocity.y);
return;
}
rb2d.velocity = new Vector2(move_direction * RUN_SPEED, rb2d.velocity.y);
}
}
/// <summary>
/// 小骑士跳跃的函数
/// </summary>
private void Jump()
{
if (jump_steps <= JUMP_STEPS)
{
rb2d.velocity = new Vector2(rb2d.velocity.x, JUMP_SPEED);
jump_steps++;
jumped_steps++;
return;
}
CancelJump();
}
/// <summary>
/// 取消跳跃,这个在释放跳跃键时有用
/// </summary>
private void CancelJump()
{
cState.jumping = false;
jumpReleaseQueuing = false;
jump_steps = 0;
}
/// <summary>
/// 标注:此函数暂且不具备任何内容待后续开发
/// </summary>
private void BackDash()
{
}
/// <summary>
/// 冲刺时执行的函数
/// </summary>
private void Dash()
{
AffectedByGravity(false); //不受到重力影响
ResetHardLandingTimer();
if(dash_timer > DASH_TIME)
{
FinishedDashing();//大于则结束冲刺
return;
}
float num;
num = DASH_SPEED;
if (dashingDown)
{
rb2d.velocity = new Vector2(0f, -num);
}
else if (cState.facingRight)
{
if (CheckForBump(CollisionSide.right))
{
//rb2d.velocity = new Vector2(num, cState.onGround ? BUMP_VELOCITY : BUMP_VELOCITY_DASH);
}
else
{
rb2d.velocity = new Vector2(num, 0f); //为人物的velocity赋值DASH_SPEED
}
}
else if (CheckForBump(CollisionSide.left))
{
//rb2d.velocity = new Vector2(-num, cState.onGround ? BUMP_VELOCITY : BUMP_VELOCITY_DASH);
}
else
{
rb2d.velocity = new Vector2(-num, 0f);
}
dash_timer += Time.deltaTime;
}
private void HeroDash()
{
if (!cState.onGround)
{
airDashed = true;
}
audioCtrl.StopSound(HeroSounds.FOOTSETP_RUN);
audioCtrl.StopSound(HeroSounds.FOOTSTEP_WALK);
audioCtrl.PlaySound(HeroSounds.DASH);
if (inputHandler.inputActions.right.IsPressed)
{
FaceRight();
}
else if (inputHandler.inputActions.left.IsPressed)
{
FaceLeft();
}
cState.dashing = true;
dashQueueSteps = 0;
HeroActions inputActions = inputHandler.inputActions;
dashBurst.transform.localPosition = new Vector3(4.11f, -0.55f, 0.001f); //生成dashBurst后设置位置和旋转角
dashBurst.transform.localEulerAngles = new Vector3(0f, 0f, 0f);
dashingDown = false;
dashCooldownTimer = DASH_COOLDOWN;
dashBurst.SendEvent("PLAY"); //发送dashBurst的FSM的事件PLAY
dashParticlesPrefab.GetComponent<ParticleSystem>().enableEmission = true;
if (cState.onGround)
{
}
}
/// <summary>
/// 判断是否可以冲刺
/// </summary>
/// <returns></returns>
public bool CanDash()
{
return hero_state != ActorStates.no_input && hero_state != ActorStates.hard_landing && hero_state != ActorStates.dash_landing &&
dashCooldownTimer <= 0f && !cState.dashing && !cState.preventDash && (cState.onGround || !airDashed) && playerData.canDash;
}
/// <summary>
/// 结束冲刺
/// </summary>
private void FinishedDashing()
{
CancelDash();
AffectedByGravity(true);//物体重新受到重力的影响
animCtrl.FinishedDash(); //该播放Dash To Idle动画片段了
if (cState.touchingWall && !cState.onGround)
{
if (touchingWallL)
{
}
if (touchingWallR)
{
}
}
}
/// <summary>
/// 取消冲刺,将cState.dashing设置为false后动画将不再播放
/// </summary>
public void CancelDash()
{
cState.dashing = false;
dash_timer = 0f; //重置冲刺时的计时器
AffectedByGravity(true); //物体重新受到重力的影响
if (dashParticlesPrefab.GetComponent<ParticleSystem>().enableEmission)
{
dashParticlesPrefab.GetComponent<ParticleSystem>().enableEmission = false;
}
}
/// <summary>
/// 物体是否受到重力的影响
/// </summary>
/// <param name="gravityApplies"></param>
private void AffectedByGravity(bool gravityApplies)
{
float gravityScale = rb2d.gravityScale;
if(rb2d.gravityScale > Mathf.Epsilon && !gravityApplies)
{
prevGravityScale = rb2d.gravityScale;
rb2d.gravityScale = 0f;
return;
}
if(rb2d.gravityScale <= Mathf.Epsilon && gravityApplies)
{
rb2d.gravityScale = prevGravityScale;
prevGravityScale = 0f;
}
}
private void FailSafeCheck()
{
if(hero_state == ActorStates.hard_landing)
{
hardLandFailSafeTimer += Time.deltaTime;
if(hardLandFailSafeTimer > HARD_LANDING_TIME + 0.3f)
{
SetState(ActorStates.grounded);
BackOnGround();
hardLandFailSafeTimer = 0f;
}
}
else
{
hardLandFailSafeTimer = 0f;
}
}
/// <summary>
/// 进入降落状态的检查
/// </summary>
private void FallCheck()
{
//如果y轴上的速度小于-1E-06F判断是否到地面上了
if (rb2d.velocity.y < -1E-06F)
{
if (!CheckTouchingGround())
{
cState.falling = true;
cState.onGround = false;
if(hero_state != ActorStates.no_input)
{
SetState(ActorStates.airborne);
}
fallTimer += Time.deltaTime;
if(fallTimer > BIG_FALL_TIME)
{
if (!cState.willHardLand)
{
cState.willHardLand = true;
}
if (!fallRumble)
{
StartFallRumble();
}
}
}
}
else
{
cState.falling = false;
fallTimer = 0f;
if (fallRumble)
{
CancelFallEffects();
}
}
}
private void DoHardLanding()
{
AffectedByGravity(true);
ResetInput();
SetState(ActorStates.hard_landing);
hardLanded = true;
audioCtrl.PlaySound(HeroSounds.HARD_LANDING);
Instantiate(hardLandingEffectPrefab, transform.position,Quaternion.identity);
}
public void ResetHardLandingTimer()
{
cState.willHardLand = false;
hardLandingTimer = 0f;
fallTimer = 0f;
hardLanded = false;
}
private bool ShouldHardLand(Collision2D collision)
{
return !collision.gameObject.GetComponent<NoHardLanding>() && cState.willHardLand && hero_state != ActorStates.hard_landing;
}
private void ResetInput()
{
move_input = 0f;
vertical_input = 0f;
}
/// <summary>
/// 翻转小骑士的localScale.x
/// </summary>
public void FlipSprite()
{
cState.facingRight = !cState.facingRight;
Vector3 localScale = transform.localScale;
localScale.x *= -1f;
transform.localScale = localScale;
}
public void FaceRight()
{
cState.facingRight = true;
Vector3 localScale = transform.localScale;
localScale.x = -1f;
transform.localScale = localScale;
}
public void FaceLeft()
{
cState.facingRight = false;
Vector3 localScale = transform.localScale;
localScale.x = 1f;
transform.localScale = localScale;
}
private void LookForInput()
{
if (acceptingInput)
{
move_input = inputHandler.inputActions.moveVector.Vector.x; //获取X方向的键盘输入
vertical_input = inputHandler.inputActions.moveVector.Vector.y;//获取Y方向的键盘输入
FilterInput();//规整化
if (inputHandler.inputActions.jump.WasReleased && jumpReleaseQueueingEnabled)
{
jumpReleaseQueueSteps = JUMP_RELEASE_QUEUE_STEPS;
jumpReleaseQueuing = true;
}
if (!inputHandler.inputActions.jump.IsPressed)
{
JumpReleased();
}
if (!inputHandler.inputActions.dash.IsPressed)
{
if(cState.preventDash && !cState.dashCooldown)
{
cState.preventDash = false;
}
dashQueuing = false;
}
}
}
private void LookForQueueInput()
{
if (acceptingInput)
{
if (inputHandler.inputActions.jump.WasPressed)
{
if (CanJump())
{
HeroJump();
}
else
{
jumpQueueSteps = 0;
jumpQueuing = true;
}
}
if (inputHandler.inputActions.dash.WasPressed)
{
if (CanDash())
{
HeroDash();
}
else
{
dashQueueSteps = 0;
dashQueuing = true;
}
}
if (inputHandler.inputActions.jump.IsPressed)
{
if(jumpQueueSteps <= JUMP_QUEUE_STEPS && CanJump() && jumpQueuing)
{
Debug.LogFormat("Execute Hero Jump");
HeroJump();
}
}
if(inputHandler.inputActions.dash.IsPressed && dashQueueSteps <= DASH_QUEUE_STEPS && CanDash() && dashQueuing)
{
Debug.LogFormat("Start Hero Dash");
HeroDash();
}
}
}
/// <summary>
/// 可以跳跃吗
/// </summary>
/// <returns></returns>
private bool CanJump()
{
if(hero_state == ActorStates.no_input || hero_state == ActorStates.hard_landing || hero_state == ActorStates.dash_landing || cState.dashing || cState.backDashing || cState.jumping)
{
return false;
}
if (cState.onGround)
{
return true; //如果在地面上就return true
}
return false;
}
/// <summary>
/// 小骑士跳跃行为播放声音以及设置cstate.jumping
/// </summary>
private void HeroJump()
{
audioCtrl.PlaySound(HeroSounds.JUMP);
cState.jumping = true;
jumpQueueSteps = 0;
jumped_steps = 0;
}
private void HeroJumpNoEffect()
{
audioCtrl.PlaySound(HeroSounds.JUMP);
cState.jumping = true;
jumpQueueSteps = 0;
jumped_steps = 0;
}
/// <summary>
/// 取消跳跃
/// </summary>
public void CancelHeroJump()
{
if (cState.jumping)
{
CancelJump();
if(rb2d.velocity.y > 0f)
{
rb2d.velocity = new Vector2(rb2d.velocity.x, 0f);
}
}
}
private void JumpReleased()
{
if(rb2d.velocity.y > 0f &&jumped_steps >= JUMP_STEPS_MIN)
{
if (jumpReleaseQueueingEnabled)
{
if(jumpReleaseQueuing && jumpReleaseQueueSteps <= 0)
{
rb2d.velocity = new Vector2(rb2d.velocity.x, 0f); //取消跳跃并且设置y轴速度为0
CancelJump();
}
}
else
{
rb2d.velocity = new Vector2(rb2d.velocity.x, 0f);
CancelJump();
}
}
jumpQueuing = false;
}
/// <summary>
/// 设置玩家的ActorState的新类型
/// </summary>
/// <param name="newState"></param>
private void SetState(ActorStates newState)
{
if(newState == ActorStates.grounded)
{
if(Mathf.Abs(move_input) > Mathf.Epsilon)
{
newState = ActorStates.running;
}
else
{
newState = ActorStates.idle;
}
}
else if(newState == ActorStates.previous)
{
newState = prev_hero_state;
}
if(newState != hero_state)
{
prev_hero_state = hero_state;
hero_state = newState;
animCtrl.UpdateState(newState);
}
}
/// <summary>
/// 回到地面上时执行的函数
/// </summary>
public void BackOnGround()
{
if(landingBufferSteps <= 0)
{
landingBufferSteps = LANDING_BUFFER_STEPS;
if(!cState.onGround && !hardLanded)
{
Instantiate(softLandingEffectPrefab, transform.position,Quaternion.identity); //TODO:
}
}
cState.falling = false;
fallTimer = 0f;
dashLandingTimer = 0f;
cState.willHardLand = false;
hardLandingTimer = 0f;
hardLanded = false;
jump_steps = 0;
SetState(ActorStates.grounded);
cState.onGround = true;
airDashed = false;
}
/// <summary>
/// 开启在下落时晃动
/// </summary>
public void StartFallRumble()
{
fallRumble = true;
audioCtrl.PlaySound(HeroSounds.FALLING);
}
public void CancelFallEffects()
{
fallRumble = false;
audioCtrl.StopSound(HeroSounds.FALLING);
}
/// <summary>
/// 规整化输入
/// </summary>
private void FilterInput()
{
if (move_input > 0.3f)
{
move_input = 1f;
}
else if (move_input < -0.3f)
{
move_input = -1f;
}
else
{
move_input = 0f;
}
if (vertical_input > 0.5f)
{
vertical_input = 1f;
return;
}
if (vertical_input < -0.5f)
{
vertical_input = -1f;
return;
}
vertical_input = 0f;
}
private void OnCollisionEnter2D(Collision2D collision)
{
if(collision.gameObject.layer == LayerMask.NameToLayer("Terrain") && collision.gameObject.CompareTag("HeroWalkable") && CheckTouchingGround())
{
}
if(hero_state != ActorStates.no_input)
{
if(collision.gameObject.layer == LayerMask.NameToLayer("Terrain") || collision.gameObject.CompareTag("HeroWalkable"))
{
CollisionSide collisionSide = FindCollisionSide(collision);
//如果头顶顶到了
if (collisionSide == CollisionSide.top)
{
if (cState.jumping)
{
CancelJump();
}
}
//如果底下碰到了
if (collisionSide == CollisionSide.bottom)
{
if(ShouldHardLand(collision))
{
DoHardLanding();
}
else if(collision.gameObject.GetComponent<SteepSlope>() == null && hero_state != ActorStates.hard_landing)
{
BackOnGround();
}
if(cState.dashing && dashingDown)
{
AffectedByGravity(true);
SetState(ActorStates.dash_landing);
hardLanded = true;
return;
}
}
}
}
else if(hero_state == ActorStates.no_input)
{
}
}
private void OnCollisionStay2D(Collision2D collision)
{
if(hero_state != ActorStates.no_input && collision.gameObject.layer == LayerMask.NameToLayer("Terrain"))
{
if (collision.gameObject.GetComponent<NonSlider>() == null)
{
if (CheckStillTouchingWall(CollisionSide.left, false))
{
cState.touchingWall = true;
touchingWallL = true;
touchingWallR = false;
}
else if (CheckStillTouchingWall(CollisionSide.right, false))
{
cState.touchingWall = true;
touchingWallL = false;
touchingWallR = true;
}
else
{
cState.touchingWall = false;
touchingWallL = false;
touchingWallR = false;
}
if (CheckTouchingGround())
{
if (ShouldHardLand(collision))
{
DoHardLanding();
}
if(hero_state != ActorStates.hard_landing && hero_state != ActorStates.dash_landing && cState.falling)
{
BackOnGround();
return;
}
}
else if(cState.jumping || cState.falling)
{
cState.onGround = false;
SetState(ActorStates.airborne);
return;
}
}
else
{
}
}
}
private void OnCollisionExit2D(Collision2D collision)
{
if(touchingWallL && !CheckStillTouchingWall(CollisionSide.left, false))
{
cState.touchingWall = false;
touchingWallL = false;
}
if (touchingWallR && !CheckStillTouchingWall(CollisionSide.left, false))
{
cState.touchingWall = false;
touchingWallR = false;
}
if(hero_state != ActorStates.no_input && collision.gameObject.layer == LayerMask.NameToLayer("Terrain") && !CheckTouchingGround())
{
cState.onGround = false;
SetState(ActorStates.airborne);
}
}
/// <summary>
/// 检查是否接触到地面
/// </summary>
/// <returns></returns>
public bool CheckTouchingGround()
{
Vector2 vector = new Vector2(col2d.bounds.min.x, col2d.bounds.center.y);
Vector2 vector2 = col2d.bounds.center;
Vector2 vector3 = new Vector2(col2d.bounds.max.x, col2d.bounds.center.y);
float distance = col2d.bounds.extents.y + 0.16f;
Debug.DrawRay(vector, Vector2.down, Color.yellow);
Debug.DrawRay(vector2, Vector2.down, Color.yellow);
Debug.DrawRay(vector3, Vector2.down, Color.yellow);
RaycastHit2D raycastHit2D = Physics2D.Raycast(vector, Vector2.down, distance, LayerMask.GetMask("Terrain"));
RaycastHit2D raycastHit2D2 = Physics2D.Raycast(vector2, Vector2.down, distance, LayerMask.GetMask("Terrain"));
RaycastHit2D raycastHit2D3 = Physics2D.Raycast(vector3, Vector2.down, distance, LayerMask.GetMask("Terrain"));
return raycastHit2D.collider != null || raycastHit2D2.collider != null || raycastHit2D3.collider != null;
}
/// <summary>
/// 检查是否保持着接触着墙
/// </summary>
/// <param name="side"></param>
/// <param name="checkTop"></param>
/// <returns></returns>
private bool CheckStillTouchingWall(CollisionSide side,bool checkTop = false)
{
Vector2 origin = new Vector2(col2d.bounds.min.x, col2d.bounds.max.y);
Vector2 origin2 = new Vector2(col2d.bounds.min.x, col2d.bounds.center.y);
Vector2 origin3 = new Vector2(col2d.bounds.min.x, col2d.bounds.min.y);
Vector2 origin4 = new Vector2(col2d.bounds.max.x, col2d.bounds.max.y);
Vector2 origin5 = new Vector2(col2d.bounds.max.x, col2d.bounds.center.y);
Vector2 origin6 = new Vector2(col2d.bounds.max.x, col2d.bounds.min.y);
float distance = 0.1f;
RaycastHit2D raycastHit2D = default(RaycastHit2D);
RaycastHit2D raycastHit2D2 = default(RaycastHit2D);
RaycastHit2D raycastHit2D3 = default(RaycastHit2D);
if(side == CollisionSide.left)
{
if (checkTop)
{
raycastHit2D = Physics2D.Raycast(origin, Vector2.left, distance, LayerMask.GetMask("Terrain"));
}
raycastHit2D2 = Physics2D.Raycast(origin2, Vector2.left, distance, LayerMask.GetMask("Terrain"));
raycastHit2D3 = Physics2D.Raycast(origin3, Vector2.left, distance, LayerMask.GetMask("Terrain"));
}
else
{
if(side != CollisionSide.right)
{
Debug.LogError("Invalid CollisionSide specified.");
return false;
}
if (checkTop)
{
raycastHit2D = Physics2D.Raycast(origin4, Vector2.right, distance, LayerMask.GetMask("Terrain"));
}
raycastHit2D2 = Physics2D.Raycast(origin5, Vector2.right, distance, LayerMask.GetMask("Terrain"));
raycastHit2D3 = Physics2D.Raycast(origin6, Vector2.right, distance, LayerMask.GetMask("Terrain"));
}
if(raycastHit2D2.collider != null)
{
bool flag = true;
if (raycastHit2D2.collider.isTrigger)
{
flag = false;
}
if(raycastHit2D2.collider.GetComponent<SteepSlope>() != null)
{
flag = false;
}
if (raycastHit2D2.collider.GetComponent<NonSlider>() != null)
{
flag = false;
}
if (flag)
{
return true;
}
}
if (raycastHit2D3.collider != null)
{
bool flag2 = true;
if (raycastHit2D3.collider.isTrigger)
{
flag2 = false;
}
if (raycastHit2D3.collider.GetComponent<SteepSlope>() != null)
{
flag2 = false;
}
if (raycastHit2D3.collider.GetComponent<NonSlider>() != null)
{
flag2 = false;
}
if (flag2)
{
return true;
}
}
if (checkTop && raycastHit2D.collider != null)
{
bool flag3 = true;
if (raycastHit2D.collider.isTrigger)
{
flag3 = false;
}
if (raycastHit2D.collider.GetComponent<SteepSlope>() != null)
{
flag3 = false;
}
if (raycastHit2D.collider.GetComponent<NonSlider>() != null)
{
flag3 = false;
}
if (flag3)
{
return true;
}
}
return false;
}
public bool CheckForBump(CollisionSide side)
{
float num = 0.025f;
float num2 = 0.2f;
Vector2 vector = new Vector2(col2d.bounds.min.x + num2, col2d.bounds.min.y + 0.2f);
Vector2 vector2 = new Vector2(col2d.bounds.min.x + num2, col2d.bounds.min.y - num);
Vector2 vector3 = new Vector2(col2d.bounds.max.x - num2, col2d.bounds.min.y + 0.2f);
Vector2 vector4 = new Vector2(col2d.bounds.max.x - num2, col2d.bounds.min.y - num);
float num3 = 0.32f + num2;
RaycastHit2D raycastHit2D = default(RaycastHit2D);
RaycastHit2D raycastHit2D2 = default(RaycastHit2D);
if(side == CollisionSide.left)
{
Debug.DrawLine(vector2, vector2 + Vector2.left * num3, Color.cyan, 0.15f);
Debug.DrawLine(vector, vector + Vector2.left * num3, Color.cyan, 0.15f);
raycastHit2D = Physics2D.Raycast(vector2, Vector2.left, num3, LayerMask.GetMask("Terrain"));
raycastHit2D2 = Physics2D.Raycast(vector, Vector2.left, num3, LayerMask.GetMask("Terrain"));
}
else if (side == CollisionSide.right)
{
Debug.DrawLine(vector4, vector4 + Vector2.right * num3, Color.cyan, 0.15f);
Debug.DrawLine(vector3, vector3 + Vector2.right * num3, Color.cyan, 0.15f);
raycastHit2D = Physics2D.Raycast(vector4, Vector2.right, num3, LayerMask.GetMask("Terrain"));
raycastHit2D2 = Physics2D.Raycast(vector3, Vector2.right, num3, LayerMask.GetMask("Terrain"));
}
else
{
Debug.LogError("Invalid CollisionSide specified.");
}
if(raycastHit2D2.collider != null && raycastHit2D.collider == null)
{
Vector2 vector5 = raycastHit2D2.point + new Vector2((side == CollisionSide.right) ? 0.1f : -0.1f, 1f);
RaycastHit2D raycastHit2D3 = Physics2D.Raycast(vector5, Vector2.down, 1.5f, LayerMask.GetMask("Terrain"));
Vector2 vector6 = raycastHit2D2.point + new Vector2((side == CollisionSide.right) ? -0.1f : 0.1f, 1f);
RaycastHit2D raycastHit2D4 = Physics2D.Raycast(vector6, Vector2.down, 1.5f, LayerMask.GetMask("Terrain"));
if(raycastHit2D3.collider != null)
{
Debug.DrawLine(vector5, raycastHit2D3.point, Color.cyan, 0.15f);
if (!(raycastHit2D4.collider != null))
{
return true;
}
Debug.DrawLine(vector6, raycastHit2D4.point, Color.cyan, 0.15f);
float num4 = raycastHit2D3.point.y - raycastHit2D4.point.y;
if(num4 > 0f)
{
Debug.Log("Bump Height: " + num4.ToString());
return true;
}
}
}
return false;
}
/// <summary>
/// 找到碰撞点的方向也就是上下左右
/// </summary>
/// <param name="collision"></param>
/// <returns></returns>
private CollisionSide FindCollisionSide(Collision2D collision)
{
Vector2 normal = collision.GetSafeContact().Normal ;
float x = normal.x;
float y = normal.y;
if(y >= 0.5f)
{
return CollisionSide.bottom;
}
if (y <= -0.5f)
{
return CollisionSide.top;
}
if (x < 0)
{
return CollisionSide.right;
}
if (x > 0)
{
return CollisionSide.left;
}
Debug.LogError(string.Concat(new string[]
{
"ERROR: unable to determine direction of collision - contact points at (",
normal.x.ToString(),
",",
normal.y.ToString(),
")"
}));
return CollisionSide.bottom;
}
}
[Serializable]
public class HeroControllerStates
{
public bool facingRight;
public bool onGround;
public bool wasOnGround;
public bool inWalkZone;
public bool jumping;
public bool falling;
public bool dashing;
public bool backDashing;
public bool touchingWall;
public bool willHardLand;
public bool preventDash;
public bool preventBackDash;
public bool dashCooldown;
public bool backDashCooldown;
public bool isPaused;
public HeroControllerStates()
{
facingRight = false;
onGround = false;
wasOnGround = false;
inWalkZone = false;
jumping = false;
falling = false;
dashing = false;
backDashing = false;
touchingWall = false;
willHardLand = false;
preventDash = false;
preventBackDash = false;
dashCooldown = false;
backDashCooldown = false;
isPaused = false;
}
}
还有就是HeroAnimationController .cs我们的条件是
if(actorStates == ActorStates.hard_landing)
{
animator.Play("HardLand");
}
using System;
using GlobalEnums;
using UnityEngine;
public class HeroAnimationController : MonoBehaviour
{
private HeroController heroCtrl;
private HeroControllerStates cState;
private tk2dSpriteAnimator animator;
private PlayerData pd;
private bool wasFacingRight;
private bool playLanding;
private bool playRunToIdle;//播放"Run To Idle"动画片段
private bool playDashToIdle; //播放"Dash To Idle"动画片段
private bool playBackDashToIdleEnd; //播放"Back Dash To Idle"动画片段(其实并不会播放)
private bool changedClipFromLastFrame;
public ActorStates actorStates { get; private set; }
public ActorStates prevActorStates { get; private set; }
private void Awake()
{
heroCtrl = HeroController.instance;
cState = heroCtrl.cState;
animator = GetComponent<tk2dSpriteAnimator>();
}
private void Start()
{
pd = PlayerData.instance;
ResetAll();
actorStates = heroCtrl.hero_state;
if(heroCtrl.hero_state == ActorStates.airborne)
{
animator.PlayFromFrame("Airborne", 7);
return;
}
PlayIdle();
}
private void Update()
{
UpdateAnimation();
if (cState.facingRight)
{
wasFacingRight = true;
return;
}
wasFacingRight = false;
}
private void UpdateAnimation()
{
changedClipFromLastFrame = false;
if (playLanding)
{
Play("Land");
animator.AnimationCompleted = new Action<tk2dSpriteAnimator, tk2dSpriteAnimationClip>(AnimationCompleteDelegate);
playLanding = false;
}
if (playRunToIdle)
{
Play("Run To Idle");
animator.AnimationCompleted = new Action<tk2dSpriteAnimator, tk2dSpriteAnimationClip>(AnimationCompleteDelegate);
playRunToIdle = false;
}
if (playBackDashToIdleEnd)
{
Play("Backdash Land 2");
//处理animation播放完成后的事件(其实并不会播放)
animator.AnimationCompleted = new Action<tk2dSpriteAnimator, tk2dSpriteAnimationClip>(AnimationCompleteDelegate);
playDashToIdle = false;
}
if (playDashToIdle)
{
Play("Dash To Idle");
//处理animation播放完成后的事件
animator.AnimationCompleted = new Action<tk2dSpriteAnimator, tk2dSpriteAnimationClip>(AnimationCompleteDelegate);
playDashToIdle = false;
}
if (actorStates == ActorStates.no_input)
{
//TODO:
}
else if (cState.dashing)
{
if (heroCtrl.dashingDown)
{
Play("Dash Down");
}
else
{
Play("Dash"); //通过cState.dashing判断是否播放Dash动画片段
}
}
else if (actorStates == ActorStates.idle)
{
//TODO:
if (CanPlayIdle())
{
PlayIdle();
}
}
else if (actorStates == ActorStates.running)
{
if (!animator.IsPlaying("Turn"))
{
if (cState.inWalkZone)
{
if (!animator.IsPlaying("Walk"))
{
Play("Walk");
}
}
else
{
PlayRun();
}
}
}
else if (actorStates == ActorStates.airborne)
{
if (cState.jumping)
{
if (!animator.IsPlaying("Airborne"))
{
animator.PlayFromFrame("Airborne", 0);
}
}
else if (cState.falling)
{
if (!animator.IsPlaying("Airborne"))
{
animator.PlayFromFrame("Airborne", 7);
}
}
else if (!animator.IsPlaying("Airborne"))
{
animator.PlayFromFrame("Airborne", 3);
}
}
//(其实并不会播放)
else if (actorStates == ActorStates.dash_landing)
{
animator.Play("Dash Down Land");
}
else if(actorStates == ActorStates.hard_landing)
{
animator.Play("HardLand");
}
if (cState.facingRight)
{
if(!wasFacingRight && cState.onGround && CanPlayTurn())
{
Play("Turn");
}
wasFacingRight = true;
}
else
{
if (wasFacingRight && cState.onGround && CanPlayTurn())
{
Play("Turn");
}
wasFacingRight = false;
}
ResetPlays();
}
private void AnimationCompleteDelegate(tk2dSpriteAnimator anim, tk2dSpriteAnimationClip clip)
{
if(clip.name == "Land")
{
PlayIdle();
}
if(clip.name == "Run To Idle")
{
PlayIdle();
}
if(clip.name == "Backdash To Idle")//(其实并不会播放)
{
PlayIdle();
}
if(clip.name == "Dash To Idle")
{
PlayIdle();
}
}
private void Play(string clipName)
{
if(clipName != animator.CurrentClip.name)
{
changedClipFromLastFrame = true;
}
animator.Play(clipName);
}
private void PlayRun()
{
animator.Play("Run");
}
public void PlayIdle()
{
animator.Play("Idle");
}
public void FinishedDash()
{
playDashToIdle = true;
}
private void ResetAll()
{
playLanding = false;
playRunToIdle = false;
playDashToIdle = false;
wasFacingRight = false;
}
private void ResetPlays()
{
playLanding = false;
playRunToIdle = false;
playDashToIdle = false;
}
public void UpdateState(ActorStates newState)
{
if(newState != actorStates)
{
if(actorStates == ActorStates.airborne && newState == ActorStates.idle && !playLanding)
{
playLanding = true;
}
if(actorStates == ActorStates.running && newState == ActorStates.idle && !playRunToIdle && !cState.inWalkZone)
{
playRunToIdle = true;
}
prevActorStates = actorStates;
actorStates = newState;
}
}
private bool CanPlayIdle()
{
return !animator.IsPlaying("Land") && !animator.IsPlaying("Run To Idle") && !animator.IsPlaying("Dash To Idle") && !animator.IsPlaying("Backdash Land") && !animator.IsPlaying("Backdash Land 2") && !animator.IsPlaying("LookUpEnd") && !animator.IsPlaying("LookDownEnd") && !animator.IsPlaying("Exit Door To Idle") && !animator.IsPlaying("Wake Up Ground") && !animator.IsPlaying("Hazard Respawn");
}
private bool CanPlayTurn()
{
return !animator.IsPlaying("Wake Up Ground") && !animator.IsPlaying("Hazard Respawn"); ;
}
}
最后我们还要制作好预制体加上去:
先做代码:
using System;
using UnityEngine;
public class SoftLandEffect : MonoBehaviour
{
public GameObject dustEffects;
public AudioClip softLandClip;
private PlayerData pd;
private GameObject heroObject;
private AudioSource audioSource;
private Rigidbody2D heroRigibody;
private tk2dSpriteAnimator jumpPuffAnimator;
private float recycleTimer;
private void OnEnable()
{
if (pd == null)
pd = PlayerData.instance;
if(audioSource == null)
{
audioSource = GetComponent<AudioSource>();
}
foreach (object obj in transform)
{
((Transform)obj).gameObject.SetActive(false);
}
recycleTimer = 1f;
HeroController instance = HeroController.instance;
if(instance != null)
{
dustEffects.SetActive(true);
audioSource.PlayOneShot(softLandClip);
}
}
private void Update()
{
if(recycleTimer <= 0f)
{
Destroy(gameObject); //TODO:
return;
}
recycleTimer -= Time.deltaTime;
}
}
然后做粒子系统:
再做HardLandingEffect:
using System;
using UnityEngine;
public class HardLandEffect : MonoBehaviour
{
public GameObject dustObj;
[Space]
public GameObject particleRockPrefab;
private float recycleTime;
private void OnEnable()
{
//TODO:
dustObj.SetActive(true);
dustObj.SetActiveChildren(true);
if (particleRockPrefab)
{
FlingUtils.SpawnAndFling(new FlingUtils.Config
{
Prefab = particleRockPrefab,
AmountMin = 2,
AmountMax = 3,
SpeedMin = 12f,
SpeedMax = 15f,
AngleMin = 95f,
AngleMax = 140f
}, transform, new Vector3(0f, -0.9f, 0f));
FlingUtils.SpawnAndFling(new FlingUtils.Config
{
Prefab = particleRockPrefab,
AmountMin = 2,
AmountMax = 3,
SpeedMin = 12f,
SpeedMax = 15f,
AngleMin = 40f,
AngleMax = 85f
}, transform, new Vector3(0f, -0.9f, 0f));
}
recycleTime = Time.time + 1.5f;
}
private void Update()
{
if(Time.time > recycleTime)
{
Destroy(gameObject); //TODO:
}
}
}
首先来到Extension.cs中我们创建一个新的静态方法:
public static class Extensions
{
public static void SetActiveChildren(this GameObject self, bool value)
{
int childCount = self.transform.childCount;
for (int i = 0; i < childCount; i++)
{
self.transform.GetChild(i).gameObject.SetActive(value);
}
}
}
再来到这个FlingUtils.cs: (其实这个还有待研究,我要想想怎么在静态方法中使用Instaniate()方法生成一个游戏对象)
using System;
using UnityEngine;
public static class FlingUtils
{
public static GameObject[] SpawnAndFling(Config config,Transform spawnPoint,Vector3 positionOffset)
{
if(config.Prefab == null)
{
return null;
}
int num = UnityEngine.Random.Range(config.AmountMin, config.AmountMax + 1);
Vector3 a = (spawnPoint != null) ? spawnPoint.TransformPoint(positionOffset) : positionOffset;
GameObject[] array = new GameObject[num];
for (int i = 0; i < num; i++)
{
Vector3 position = new Vector3(UnityEngine.Random.Range(-config.OriginVariationX, config.OriginVariationX), UnityEngine.Random.Range(-config.OriginVariationY, config.OriginVariationY));
GameObject gameObject = config.Prefab; //TODO:
gameObject.transform.position = position;
Rigidbody2D component = gameObject.GetComponent<Rigidbody2D>();
if(component != null)
{
float d = UnityEngine.Random.Range(config.SpeedMin, config.SpeedMax);
float num2 = UnityEngine.Random.Range(config.AngleMin, config.AngleMax);
component.velocity = new Vector2(Mathf.Cos(num2 * 0.017453292f), Mathf.Sin(num2 * 0.017453292f)) * d;
}
array[i] = gameObject;
}
return array;
}
public struct Config
{
public GameObject Prefab;
public float SpeedMin;
public float SpeedMax;
public float AngleMin;
public float AngleMax;
public float OriginVariationX;
public float OriginVariationY;
public int AmountMin;
public int AmountMax;
}
public struct ChildrenConfig
{
public GameObject Parent;
public int AmountMin;
public int AmountMax;
public float SpeedMin;
public float SpeedMax;
public float AngleMin;
public float AngleMax;
public float OriginVariationX;
public float OriginVariationY;
}
public struct SelfConfig
{
public GameObject Object;
public float SpeedMin;
public float SpeedMax;
public float AngleMin;
public float AngleMax;
}
}
二、扩展小骑士冲刺Dash行为
1.制作动画以及使用UNITY编辑器编辑
老规矩我们先把素材导入后,开始回到tk2dspriteEditor中,由于我们第二期就已经制作了小骑士的spritecollection和spriteanimation,所以我们直接把图片拖进去即可。
可能你会注意到我的动画多了三个叫Back Dash、Backdash Land 2、Backdash To Idle的,这是被空洞骑士制作组弃用的行为后撤步BackDash,弃用的原因据说是与其这样还不如按下反方向键并按冲刺呢,而且范围又小有时候根本躲不了技能,没有Dash冲的爽。但我们还是先加上去吧:
2.使用代码实现扩展新的落地行为和重落地行为
回到HeroAnimationController.cs脚本中,我们需要更改一下条件了:
else if (cState.dashing)
{
if (heroCtrl.dashingDown)
{
Play("Dash Down");
}
else
{
Play("Dash"); //通过cState.dashing判断是否播放Dash动画片段
}
}
设置一下PlayerData.cs,添加那个护符的编号:
using System;
using System.Collections.Generic;
using System.Reflection;
using GlobalEnums;
using UnityEngine;
[Serializable]
public class PlayerData
{
private static PlayerData _instance;
public static PlayerData instance
{
get
{
if(_instance == null)
{
_instance = new PlayerData();
}
return _instance;
}
set
{
_instance = value;
}
}
public bool hasDash;
public bool canDash;
public bool hasBackDash;
public bool canBackDash;
public bool gotCharm_31;
public bool equippedCharm_31;
protected PlayerData()
{
SetupNewPlayerData();
}
public void Reset()
{
SetupNewPlayerData();
}
private void SetupNewPlayerData()
{
hasDash = true; //测试阶段先设置为true方便测试
canDash = true;
hasBackDash = false;
canBackDash = false;
gotCharm_31 = true;
equippedCharm_31 = true;
}
}
再来到HeroController.cs中:
public bool dashingDown;//是否正在执行向下冲刺
更改Dash()函数:
private void Dash()
{
AffectedByGravity(false); //不受到重力影响
ResetHardLandingTimer();
if(dash_timer > DASH_TIME)
{
FinishedDashing();//大于则结束冲刺
return;
}
float num;
num = DASH_SPEED;
if (dashingDown)
{
rb2d.velocity = new Vector2(0f, -num);
}
else if (cState.facingRight)
{
if (CheckForBump(CollisionSide.right))
{
}
else
{
rb2d.velocity = new Vector2(num, 0f); //为人物的velocity赋值DASH_SPEED
}
}
else if (CheckForBump(CollisionSide.left))
{
}
else
{
rb2d.velocity = new Vector2(-num, 0f);
}
dash_timer += Time.deltaTime;
}
更改HeroDash()函数:
private void HeroDash()
{
if (!cState.onGround)
{
airDashed = true;
}
audioCtrl.StopSound(HeroSounds.FOOTSETP_RUN);
audioCtrl.StopSound(HeroSounds.FOOTSTEP_WALK);
audioCtrl.PlaySound(HeroSounds.DASH);
if (inputHandler.inputActions.right.IsPressed)
{
FaceRight();
}
else if (inputHandler.inputActions.left.IsPressed)
{
FaceLeft();
}
cState.dashing = true;
dashQueueSteps = 0;
HeroActions inputActions = inputHandler.inputActions;
if(inputActions.down.IsPressed && !cState.onGround && playerData.equippedCharm_31 && !inputActions.left.IsPressed && !inputActions.right.IsPressed)
{
dashBurst.transform.localPosition = new Vector3(-0.07f, 3.74f, 0.01f); //生成dashBurst后设置位置和旋转角
dashBurst.transform.localEulerAngles = new Vector3(0f, 0f, 90f);
dashingDown = true;
}
else
{
dashBurst.transform.localPosition = new Vector3(4.11f, -0.55f, 0.001f); //生成dashBurst后设置位置和旋转角
dashBurst.transform.localEulerAngles = new Vector3(0f, 0f, 0f);
dashingDown = false;
}
dashCooldownTimer = DASH_COOLDOWN;
dashBurst.SendEvent("PLAY"); //发送dashBurst的FSM的事件PLAY
dashParticlesPrefab.GetComponent<ParticleSystem>().enableEmission = true;
if (cState.onGround)
{
dashEffect = Instantiate(backDashPrefab, transform.position, Quaternion.identity);
dashEffect.transform.localScale = new Vector3(transform.localScale.x * -1f, transform.localScale.y, transform.localScale.z);
}
}
在OnCollisionEnter2D碰撞检测函数中添加
//如果底下碰到了
if (collisionSide == CollisionSide.bottom)
{
if(ShouldHardLand(collision))
{
DoHardLanding();
}
else if(collision.gameObject.GetComponent<SteepSlope>() == null && hero_state != ActorStates.hard_landing)
{
BackOnGround();
}
if(cState.dashing && dashingDown)
{
AffectedByGravity(true);
SetState(ActorStates.dash_landing);
hardLanded = true;
return;
}
}
完整的代码如下所示:
using System;
using System.Collections;
using System.Collections.Generic;
using HutongGames.PlayMaker;
using GlobalEnums;
using UnityEngine;
public class HeroController : MonoBehaviour
{
public ActorStates hero_state;
public ActorStates prev_hero_state;
public bool acceptingInput = true;
public float move_input;
public float vertical_input;
private Vector2 current_velocity;
public float WALK_SPEED = 3.1f;//走路速度
public float RUN_SPEED = 5f;//跑步速度
public float JUMP_SPEED = 5f;//跳跃的食欲
private int jump_steps; //跳跃的步
private int jumped_steps; //已经跳跃的步
private int jumpQueueSteps; //跳跃队列的步
private bool jumpQueuing; //是否进入跳跃队列中
private int jumpReleaseQueueSteps; //释放跳跃后的步
private bool jumpReleaseQueuing; //是否进入释放跳跃队列中
private bool jumpReleaseQueueingEnabled; //是否允许进入释放跳跃队列中
public float MAX_FALL_VELOCITY; //最大下落速度(防止速度太快了)
public int JUMP_STEPS; //最大跳跃的步
public int JUMP_STEPS_MIN; //最小跳跃的步
private int JUMP_QUEUE_STEPS; //最大跳跃队列的步
private int JUMP_RELEASE_QUEUE_STEPS;//最大跳跃释放队列的步
private int dashQueueSteps;
private bool dashQueuing;
private float dashCooldownTimer; //冲刺冷却时间
private float dash_timer; //正在冲刺计数器
private float back_dash_timer; 正在后撤冲刺计数器 (标注:此行代码无用待后续开发)
private float dashLandingTimer;
private bool airDashed;//是否是在空中冲刺
public bool dashingDown;//是否正在执行向下冲刺
public PlayMakerFSM dashBurst;
public GameObject dashParticlesPrefab;//冲刺粒子效果预制体
public GameObject backDashPrefab; //后撤冲刺特效预制体 标注:此行代码无用待后续开发
private GameObject backDash;//后撤冲刺 (标注:此行代码无用待后续开发)
private GameObject dashEffect;//后撤冲刺特效生成 (标注:此行代码无用待后续开发)
public float DASH_SPEED; //冲刺时的速度
public float DASH_TIME; //冲刺时间
public float DASH_COOLDOWN; //冲刺冷却时间
public float BACK_DASH_SPEED;//后撤冲刺时的速度 (标注:此行代码无用待后续开发)
public float BACK_DASH_TIME;//后撤冲刺时间 (标注:此行代码无用待后续开发)
public float BACK_DASH_COOLDOWN; //后撤冲刺冷却时间 (标注:此行代码无用待后续开发)
public float DASH_LANDING_TIME;
public int DASH_QUEUE_STEPS; //最大冲刺队列的步
public float fallTimer { get; private set; }
private float hardLandingTimer; //正在hardLanding的计时器,大于就将状态改为grounded并BackOnGround()
private float hardLandFailSafeTimer; //进入hardLand后玩家失去输入的一段时间
private bool hardLanded; //是否已经hardLand了
public float HARD_LANDING_TIME; //正在hardLanding花费的时间。
public float BIG_FALL_TIME; //判断是否是hardLanding所需要的事件,大于它就是
public GameObject hardLandingEffectPrefab;
private float prevGravityScale;
private int landingBufferSteps;
private int LANDING_BUFFER_STEPS = 5;
private bool fallRumble; //是否开启掉落时相机抖动
public GameObject softLandingEffectPrefab;
public bool touchingWall; //是否接触到墙
public bool touchingWallL; //是否接触到的墙左边
public bool touchingWallR; //是否接触到的墙右边
private Rigidbody2D rb2d;
private BoxCollider2D col2d;
private GameManager gm;
public PlayerData playerData;
private InputHandler inputHandler;
public HeroControllerStates cState;
private HeroAnimationController animCtrl;
private HeroAudioController audioCtrl;
private static HeroController _instance;
public static HeroController instance
{
get
{
if (_instance == null)
_instance = FindObjectOfType<HeroController>();
if(_instance && Application.isPlaying)
{
DontDestroyOnLoad(_instance.gameObject);
}
return _instance;
}
}
public HeroController()
{
JUMP_QUEUE_STEPS = 2;
JUMP_RELEASE_QUEUE_STEPS = 2;
LANDING_BUFFER_STEPS = 5;
}
private void Awake()
{
if(_instance == null)
{
_instance = this;
DontDestroyOnLoad(this);
}
else if(this != _instance)
{
Destroy(gameObject);
return;
}
SetupGameRefs();
}
private void SetupGameRefs()
{
if (cState == null)
cState = new HeroControllerStates();
rb2d = GetComponent<Rigidbody2D>();
col2d = GetComponent<BoxCollider2D>();
animCtrl = GetComponent<HeroAnimationController>();
audioCtrl = GetComponent<HeroAudioController>();
gm = GameManager.instance;
playerData = PlayerData.instance;
inputHandler = gm.GetComponent<InputHandler>();
}
void Start()
{
playerData = PlayerData.instance;
if (dashBurst == null)
{
Debug.Log("DashBurst came up null, locating manually");
dashBurst = FSMUtility.GetFSM(transform.Find("Effects").Find("Dash Burst").gameObject);
}
}
void Update()
{
current_velocity = rb2d.velocity;
FallCheck();
FailSafeCheck();
if (hero_state == ActorStates.running && !cState.dashing && !cState.backDashing)
{
if (cState.inWalkZone)
{
audioCtrl.StopSound(HeroSounds.FOOTSETP_RUN);
audioCtrl.PlaySound(HeroSounds.FOOTSTEP_WALK);
}
else
{
audioCtrl.StopSound(HeroSounds.FOOTSTEP_WALK);
audioCtrl.PlaySound(HeroSounds.FOOTSETP_RUN);
}
}
else
{
audioCtrl.StopSound(HeroSounds.FOOTSETP_RUN);
audioCtrl.StopSound(HeroSounds.FOOTSTEP_WALK);
}
if(hero_state == ActorStates.dash_landing)
{
dashLandingTimer += Time.deltaTime;
if(dashLandingTimer > DASH_LANDING_TIME)
{
BackOnGround();
}
}
if (hero_state == ActorStates.hard_landing)
{
hardLandingTimer += Time.deltaTime;
if (hardLandingTimer > HARD_LANDING_TIME)
{
SetState(ActorStates.grounded);
BackOnGround();
}
}
if (hero_state == ActorStates.no_input)
{
}
else if (hero_state != ActorStates.no_input)
{
LookForInput();
}
LookForQueueInput();
if (dashCooldownTimer > 0f) //计时器在Update中-= Time.deltaTime
{
dashCooldownTimer -= Time.deltaTime;
}
}
private void FixedUpdate()
{
if(hero_state == ActorStates.hard_landing || hero_state == ActorStates.dash_landing)
{
ResetMotion();
}
else if(hero_state == ActorStates.no_input)
{
}
else if (hero_state != ActorStates.no_input)
{
if(hero_state == ActorStates.running)
{
if(move_input > 0f)
{
if (CheckForBump(CollisionSide.right))
{
//rb2d.velocity = new Vector2(rb2d.velocity.x, BUMP_VELOCITY);
}
}
else if (CheckForBump(CollisionSide.left))
{
//rb2d.velocity = new Vector2(rb2d.velocity.x, -BUMP_VELOCITY);
}
}
if (!cState.dashing && !cState.backDashing)
{
Move(move_input);
if (move_input > 0f && !cState.facingRight)
{
FlipSprite();
}
else if (move_input < 0f && cState.facingRight)
{
FlipSprite();
}
}
}
if (cState.jumping) //如果cState.jumping就Jump
{
Jump();
}
if (cState.dashing)//如果cState.dashing就Dash
{
Dash();
}
//限制速度
if(rb2d.velocity.y < -MAX_FALL_VELOCITY)
{
rb2d.velocity = new Vector2(rb2d.velocity.x, -MAX_FALL_VELOCITY);
}
if (jumpQueuing)
{
jumpQueueSteps++;
}
if (dashQueuing) //跳跃队列开始
{
dashQueueSteps++;
}
if (landingBufferSteps > 0)
{
landingBufferSteps--;
}
if (jumpReleaseQueueSteps > 0)
{
jumpReleaseQueueSteps--;
}
cState.wasOnGround = cState.onGround;
}
/// <summary>
/// 小骑士移动的函数
/// </summary>
/// <param name="move_direction"></param>
private void Move(float move_direction)
{
if (cState.onGround)
{
SetState(ActorStates.grounded);
}
if(acceptingInput)
{
if (cState.inWalkZone)
{
rb2d.velocity = new Vector2(move_direction * WALK_SPEED, rb2d.velocity.y);
return;
}
rb2d.velocity = new Vector2(move_direction * RUN_SPEED, rb2d.velocity.y);
}
}
/// <summary>
/// 小骑士跳跃的函数
/// </summary>
private void Jump()
{
if (jump_steps <= JUMP_STEPS)
{
rb2d.velocity = new Vector2(rb2d.velocity.x, JUMP_SPEED);
jump_steps++;
jumped_steps++;
return;
}
CancelJump();
}
/// <summary>
/// 取消跳跃,这个在释放跳跃键时有用
/// </summary>
private void CancelJump()
{
cState.jumping = false;
jumpReleaseQueuing = false;
jump_steps = 0;
}
/// <summary>
/// 标注:此函数暂且不具备任何内容待后续开发
/// </summary>
private void BackDash()
{
}
/// <summary>
/// 冲刺时执行的函数
/// </summary>
private void Dash()
{
AffectedByGravity(false); //不受到重力影响
ResetHardLandingTimer();
if(dash_timer > DASH_TIME)
{
FinishedDashing();//大于则结束冲刺
return;
}
float num;
num = DASH_SPEED;
if (dashingDown)
{
rb2d.velocity = new Vector2(0f, -num);
}
else if (cState.facingRight)
{
if (CheckForBump(CollisionSide.right))
{
//rb2d.velocity = new Vector2(num, cState.onGround ? BUMP_VELOCITY : BUMP_VELOCITY_DASH);
}
else
{
rb2d.velocity = new Vector2(num, 0f); //为人物的velocity赋值DASH_SPEED
}
}
else if (CheckForBump(CollisionSide.left))
{
//rb2d.velocity = new Vector2(-num, cState.onGround ? BUMP_VELOCITY : BUMP_VELOCITY_DASH);
}
else
{
rb2d.velocity = new Vector2(-num, 0f);
}
dash_timer += Time.deltaTime;
}
private void HeroDash()
{
if (!cState.onGround)
{
airDashed = true;
}
audioCtrl.StopSound(HeroSounds.FOOTSETP_RUN);
audioCtrl.StopSound(HeroSounds.FOOTSTEP_WALK);
audioCtrl.PlaySound(HeroSounds.DASH);
if (inputHandler.inputActions.right.IsPressed)
{
FaceRight();
}
else if (inputHandler.inputActions.left.IsPressed)
{
FaceLeft();
}
cState.dashing = true;
dashQueueSteps = 0;
HeroActions inputActions = inputHandler.inputActions;
if(inputActions.down.IsPressed && !cState.onGround && playerData.equippedCharm_31 && !inputActions.left.IsPressed && !inputActions.right.IsPressed)
{
dashBurst.transform.localPosition = new Vector3(-0.07f, 3.74f, 0.01f); //生成dashBurst后设置位置和旋转角
dashBurst.transform.localEulerAngles = new Vector3(0f, 0f, 90f);
dashingDown = true;
}
else
{
dashBurst.transform.localPosition = new Vector3(4.11f, -0.55f, 0.001f); //生成dashBurst后设置位置和旋转角
dashBurst.transform.localEulerAngles = new Vector3(0f, 0f, 0f);
dashingDown = false;
}
dashCooldownTimer = DASH_COOLDOWN;
dashBurst.SendEvent("PLAY"); //发送dashBurst的FSM的事件PLAY
dashParticlesPrefab.GetComponent<ParticleSystem>().enableEmission = true;
if (cState.onGround)
{
dashEffect = Instantiate(backDashPrefab, transform.position, Quaternion.identity);
dashEffect.transform.localScale = new Vector3(transform.localScale.x * -1f, transform.localScale.y, transform.localScale.z);
}
}
/// <summary>
/// 判断是否可以后撤冲刺
/// </summary>
/// <returns></returns>
public bool CanBackDash()
{
return !cState.dashing && hero_state != ActorStates.no_input && !cState.backDashing && !cState.preventBackDash && !cState.backDashCooldown && cState.onGround && playerData.canBackDash;
}
/// <summary>
/// 判断是否可以冲刺
/// </summary>
/// <returns></returns>
public bool CanDash()
{
return hero_state != ActorStates.no_input && hero_state != ActorStates.hard_landing && hero_state != ActorStates.dash_landing &&
dashCooldownTimer <= 0f && !cState.dashing && !cState.backDashing && !cState.preventDash && (cState.onGround || !airDashed) && playerData.canDash;
}
/// <summary>
/// 结束冲刺
/// </summary>
private void FinishedDashing()
{
CancelDash();
AffectedByGravity(true);//物体重新受到重力的影响
animCtrl.FinishedDash(); //该播放Dash To Idle动画片段了
if (cState.touchingWall && !cState.onGround)
{
if (touchingWallL)
{
}
if (touchingWallR)
{
}
}
}
/// <summary>
/// 取消冲刺,将cState.dashing设置为false后动画将不再播放
/// </summary>
public void CancelDash()
{
cState.dashing = false;
dash_timer = 0f; //重置冲刺时的计时器
AffectedByGravity(true); //物体重新受到重力的影响
if (dashParticlesPrefab.GetComponent<ParticleSystem>().enableEmission)
{
dashParticlesPrefab.GetComponent<ParticleSystem>().enableEmission = false;
}
}
private void CancelBackDash()
{
cState.backDashing = false;
back_dash_timer = 0f;
}
/// <summary>
/// 物体是否受到重力的影响
/// </summary>
/// <param name="gravityApplies"></param>
private void AffectedByGravity(bool gravityApplies)
{
float gravityScale = rb2d.gravityScale;
if(rb2d.gravityScale > Mathf.Epsilon && !gravityApplies)
{
prevGravityScale = rb2d.gravityScale;
rb2d.gravityScale = 0f;
return;
}
if(rb2d.gravityScale <= Mathf.Epsilon && gravityApplies)
{
rb2d.gravityScale = prevGravityScale;
prevGravityScale = 0f;
}
}
private void FailSafeCheck()
{
if(hero_state == ActorStates.hard_landing)
{
hardLandFailSafeTimer += Time.deltaTime;
if(hardLandFailSafeTimer > HARD_LANDING_TIME + 0.3f)
{
SetState(ActorStates.grounded);
BackOnGround();
hardLandFailSafeTimer = 0f;
}
}
else
{
hardLandFailSafeTimer = 0f;
}
}
/// <summary>
/// 进入降落状态的检查
/// </summary>
private void FallCheck()
{
//如果y轴上的速度小于-1E-06F判断是否到地面上了
if (rb2d.velocity.y < -1E-06F)
{
if (!CheckTouchingGround())
{
cState.falling = true;
cState.onGround = false;
if(hero_state != ActorStates.no_input)
{
SetState(ActorStates.airborne);
}
fallTimer += Time.deltaTime;
if(fallTimer > BIG_FALL_TIME)
{
if (!cState.willHardLand)
{
cState.willHardLand = true;
}
if (!fallRumble)
{
StartFallRumble();
}
}
}
}
else
{
cState.falling = false;
fallTimer = 0f;
if (fallRumble)
{
CancelFallEffects();
}
}
}
private void DoHardLanding()
{
AffectedByGravity(true);
ResetInput();
SetState(ActorStates.hard_landing);
hardLanded = true;
audioCtrl.PlaySound(HeroSounds.HARD_LANDING);
Instantiate(hardLandingEffectPrefab, transform.position,Quaternion.identity);
}
public void ResetHardLandingTimer()
{
cState.willHardLand = false;
hardLandingTimer = 0f;
fallTimer = 0f;
hardLanded = false;
}
private bool ShouldHardLand(Collision2D collision)
{
return !collision.gameObject.GetComponent<NoHardLanding>() && cState.willHardLand && hero_state != ActorStates.hard_landing;
}
private void ResetInput()
{
move_input = 0f;
vertical_input = 0f;
}
private void ResetMotion()
{
CancelJump();
CancelDash();
CancelBackDash();
rb2d.velocity = Vector2.zero;
}
/// <summary>
/// 翻转小骑士的localScale.x
/// </summary>
public void FlipSprite()
{
cState.facingRight = !cState.facingRight;
Vector3 localScale = transform.localScale;
localScale.x *= -1f;
transform.localScale = localScale;
}
public void FaceRight()
{
cState.facingRight = true;
Vector3 localScale = transform.localScale;
localScale.x = -1f;
transform.localScale = localScale;
}
public void FaceLeft()
{
cState.facingRight = false;
Vector3 localScale = transform.localScale;
localScale.x = 1f;
transform.localScale = localScale;
}
private void LookForInput()
{
if (acceptingInput)
{
move_input = inputHandler.inputActions.moveVector.Vector.x; //获取X方向的键盘输入
vertical_input = inputHandler.inputActions.moveVector.Vector.y;//获取Y方向的键盘输入
FilterInput();//规整化
if (inputHandler.inputActions.jump.WasReleased && jumpReleaseQueueingEnabled)
{
jumpReleaseQueueSteps = JUMP_RELEASE_QUEUE_STEPS;
jumpReleaseQueuing = true;
}
if (!inputHandler.inputActions.jump.IsPressed)
{
JumpReleased();
}
if (!inputHandler.inputActions.dash.IsPressed)
{
if(cState.preventDash && !cState.dashCooldown)
{
cState.preventDash = false;
}
dashQueuing = false;
}
}
}
private void LookForQueueInput()
{
if (acceptingInput)
{
if (inputHandler.inputActions.jump.WasPressed)
{
if (CanJump())
{
HeroJump();
}
else
{
jumpQueueSteps = 0;
jumpQueuing = true;
}
}
if (inputHandler.inputActions.dash.WasPressed)
{
if (CanDash())
{
HeroDash();
}
else
{
dashQueueSteps = 0;
dashQueuing = true;
}
}
if (inputHandler.inputActions.jump.IsPressed)
{
if(jumpQueueSteps <= JUMP_QUEUE_STEPS && CanJump() && jumpQueuing)
{
Debug.LogFormat("Execute Hero Jump");
HeroJump();
}
}
if(inputHandler.inputActions.dash.IsPressed && dashQueueSteps <= DASH_QUEUE_STEPS && CanDash() && dashQueuing)
{
Debug.LogFormat("Start Hero Dash");
HeroDash();
}
}
}
/// <summary>
/// 可以跳跃吗
/// </summary>
/// <returns></returns>
private bool CanJump()
{
if(hero_state == ActorStates.no_input || hero_state == ActorStates.hard_landing || hero_state == ActorStates.dash_landing || cState.dashing || cState.backDashing || cState.jumping)
{
return false;
}
if (cState.onGround)
{
return true; //如果在地面上就return true
}
return false;
}
/// <summary>
/// 小骑士跳跃行为播放声音以及设置cstate.jumping
/// </summary>
private void HeroJump()
{
audioCtrl.PlaySound(HeroSounds.JUMP);
cState.jumping = true;
jumpQueueSteps = 0;
jumped_steps = 0;
}
private void HeroJumpNoEffect()
{
audioCtrl.PlaySound(HeroSounds.JUMP);
cState.jumping = true;
jumpQueueSteps = 0;
jumped_steps = 0;
}
/// <summary>
/// 取消跳跃
/// </summary>
public void CancelHeroJump()
{
if (cState.jumping)
{
CancelJump();
if(rb2d.velocity.y > 0f)
{
rb2d.velocity = new Vector2(rb2d.velocity.x, 0f);
}
}
}
private void JumpReleased()
{
if(rb2d.velocity.y > 0f &&jumped_steps >= JUMP_STEPS_MIN)
{
if (jumpReleaseQueueingEnabled)
{
if(jumpReleaseQueuing && jumpReleaseQueueSteps <= 0)
{
rb2d.velocity = new Vector2(rb2d.velocity.x, 0f); //取消跳跃并且设置y轴速度为0
CancelJump();
}
}
else
{
rb2d.velocity = new Vector2(rb2d.velocity.x, 0f);
CancelJump();
}
}
jumpQueuing = false;
}
/// <summary>
/// 设置玩家的ActorState的新类型
/// </summary>
/// <param name="newState"></param>
private void SetState(ActorStates newState)
{
if(newState == ActorStates.grounded)
{
if(Mathf.Abs(move_input) > Mathf.Epsilon)
{
newState = ActorStates.running;
}
else
{
newState = ActorStates.idle;
}
}
else if(newState == ActorStates.previous)
{
newState = prev_hero_state;
}
if(newState != hero_state)
{
prev_hero_state = hero_state;
hero_state = newState;
animCtrl.UpdateState(newState);
}
}
/// <summary>
/// 回到地面上时执行的函数
/// </summary>
public void BackOnGround()
{
if(landingBufferSteps <= 0)
{
landingBufferSteps = LANDING_BUFFER_STEPS;
if(!cState.onGround && !hardLanded)
{
Instantiate(softLandingEffectPrefab, transform.position,Quaternion.identity); //TODO:
}
}
cState.falling = false;
fallTimer = 0f;
dashLandingTimer = 0f;
cState.willHardLand = false;
hardLandingTimer = 0f;
hardLanded = false;
jump_steps = 0;
SetState(ActorStates.grounded);
cState.onGround = true;
airDashed = false;
}
/// <summary>
/// 开启在下落时晃动
/// </summary>
public void StartFallRumble()
{
fallRumble = true;
audioCtrl.PlaySound(HeroSounds.FALLING);
}
public void CancelFallEffects()
{
fallRumble = false;
audioCtrl.StopSound(HeroSounds.FALLING);
}
/// <summary>
/// 规整化输入
/// </summary>
private void FilterInput()
{
if (move_input > 0.3f)
{
move_input = 1f;
}
else if (move_input < -0.3f)
{
move_input = -1f;
}
else
{
move_input = 0f;
}
if (vertical_input > 0.5f)
{
vertical_input = 1f;
return;
}
if (vertical_input < -0.5f)
{
vertical_input = -1f;
return;
}
vertical_input = 0f;
}
private void OnCollisionEnter2D(Collision2D collision)
{
if(collision.gameObject.layer == LayerMask.NameToLayer("Terrain") && collision.gameObject.CompareTag("HeroWalkable") && CheckTouchingGround())
{
}
if(hero_state != ActorStates.no_input)
{
if(collision.gameObject.layer == LayerMask.NameToLayer("Terrain") || collision.gameObject.CompareTag("HeroWalkable"))
{
CollisionSide collisionSide = FindCollisionSide(collision);
//如果头顶顶到了
if (collisionSide == CollisionSide.top)
{
if (cState.jumping)
{
CancelJump();
}
}
//如果底下碰到了
if (collisionSide == CollisionSide.bottom)
{
if(ShouldHardLand(collision))
{
DoHardLanding();
}
else if(collision.gameObject.GetComponent<SteepSlope>() == null && hero_state != ActorStates.hard_landing)
{
BackOnGround();
}
if(cState.dashing && dashingDown)
{
AffectedByGravity(true);
SetState(ActorStates.dash_landing);
hardLanded = true;
return;
}
}
}
}
else if(hero_state == ActorStates.no_input)
{
}
}
private void OnCollisionStay2D(Collision2D collision)
{
if(hero_state != ActorStates.no_input && collision.gameObject.layer == LayerMask.NameToLayer("Terrain"))
{
if (collision.gameObject.GetComponent<NonSlider>() == null)
{
if (CheckStillTouchingWall(CollisionSide.left, false))
{
cState.touchingWall = true;
touchingWallL = true;
touchingWallR = false;
}
else if (CheckStillTouchingWall(CollisionSide.right, false))
{
cState.touchingWall = true;
touchingWallL = false;
touchingWallR = true;
}
else
{
cState.touchingWall = false;
touchingWallL = false;
touchingWallR = false;
}
if (CheckTouchingGround())
{
if (ShouldHardLand(collision))
{
DoHardLanding();
}
if(hero_state != ActorStates.hard_landing && hero_state != ActorStates.dash_landing && cState.falling)
{
BackOnGround();
return;
}
}
else if(cState.jumping || cState.falling)
{
cState.onGround = false;
SetState(ActorStates.airborne);
return;
}
}
else
{
}
}
}
private void OnCollisionExit2D(Collision2D collision)
{
if(touchingWallL && !CheckStillTouchingWall(CollisionSide.left, false))
{
cState.touchingWall = false;
touchingWallL = false;
}
if (touchingWallR && !CheckStillTouchingWall(CollisionSide.left, false))
{
cState.touchingWall = false;
touchingWallR = false;
}
if(hero_state != ActorStates.no_input && collision.gameObject.layer == LayerMask.NameToLayer("Terrain") && !CheckTouchingGround())
{
cState.onGround = false;
SetState(ActorStates.airborne);
}
}
/// <summary>
/// 检查是否接触到地面
/// </summary>
/// <returns></returns>
public bool CheckTouchingGround()
{
Vector2 vector = new Vector2(col2d.bounds.min.x, col2d.bounds.center.y);
Vector2 vector2 = col2d.bounds.center;
Vector2 vector3 = new Vector2(col2d.bounds.max.x, col2d.bounds.center.y);
float distance = col2d.bounds.extents.y + 0.16f;
Debug.DrawRay(vector, Vector2.down, Color.yellow);
Debug.DrawRay(vector2, Vector2.down, Color.yellow);
Debug.DrawRay(vector3, Vector2.down, Color.yellow);
RaycastHit2D raycastHit2D = Physics2D.Raycast(vector, Vector2.down, distance, LayerMask.GetMask("Terrain"));
RaycastHit2D raycastHit2D2 = Physics2D.Raycast(vector2, Vector2.down, distance, LayerMask.GetMask("Terrain"));
RaycastHit2D raycastHit2D3 = Physics2D.Raycast(vector3, Vector2.down, distance, LayerMask.GetMask("Terrain"));
return raycastHit2D.collider != null || raycastHit2D2.collider != null || raycastHit2D3.collider != null;
}
/// <summary>
/// 检查是否保持着接触着墙
/// </summary>
/// <param name="side"></param>
/// <param name="checkTop"></param>
/// <returns></returns>
private bool CheckStillTouchingWall(CollisionSide side,bool checkTop = false)
{
Vector2 origin = new Vector2(col2d.bounds.min.x, col2d.bounds.max.y);
Vector2 origin2 = new Vector2(col2d.bounds.min.x, col2d.bounds.center.y);
Vector2 origin3 = new Vector2(col2d.bounds.min.x, col2d.bounds.min.y);
Vector2 origin4 = new Vector2(col2d.bounds.max.x, col2d.bounds.max.y);
Vector2 origin5 = new Vector2(col2d.bounds.max.x, col2d.bounds.center.y);
Vector2 origin6 = new Vector2(col2d.bounds.max.x, col2d.bounds.min.y);
float distance = 0.1f;
RaycastHit2D raycastHit2D = default(RaycastHit2D);
RaycastHit2D raycastHit2D2 = default(RaycastHit2D);
RaycastHit2D raycastHit2D3 = default(RaycastHit2D);
if(side == CollisionSide.left)
{
if (checkTop)
{
raycastHit2D = Physics2D.Raycast(origin, Vector2.left, distance, LayerMask.GetMask("Terrain"));
}
raycastHit2D2 = Physics2D.Raycast(origin2, Vector2.left, distance, LayerMask.GetMask("Terrain"));
raycastHit2D3 = Physics2D.Raycast(origin3, Vector2.left, distance, LayerMask.GetMask("Terrain"));
}
else
{
if(side != CollisionSide.right)
{
Debug.LogError("Invalid CollisionSide specified.");
return false;
}
if (checkTop)
{
raycastHit2D = Physics2D.Raycast(origin4, Vector2.right, distance, LayerMask.GetMask("Terrain"));
}
raycastHit2D2 = Physics2D.Raycast(origin5, Vector2.right, distance, LayerMask.GetMask("Terrain"));
raycastHit2D3 = Physics2D.Raycast(origin6, Vector2.right, distance, LayerMask.GetMask("Terrain"));
}
if(raycastHit2D2.collider != null)
{
bool flag = true;
if (raycastHit2D2.collider.isTrigger)
{
flag = false;
}
if(raycastHit2D2.collider.GetComponent<SteepSlope>() != null)
{
flag = false;
}
if (raycastHit2D2.collider.GetComponent<NonSlider>() != null)
{
flag = false;
}
if (flag)
{
return true;
}
}
if (raycastHit2D3.collider != null)
{
bool flag2 = true;
if (raycastHit2D3.collider.isTrigger)
{
flag2 = false;
}
if (raycastHit2D3.collider.GetComponent<SteepSlope>() != null)
{
flag2 = false;
}
if (raycastHit2D3.collider.GetComponent<NonSlider>() != null)
{
flag2 = false;
}
if (flag2)
{
return true;
}
}
if (checkTop && raycastHit2D.collider != null)
{
bool flag3 = true;
if (raycastHit2D.collider.isTrigger)
{
flag3 = false;
}
if (raycastHit2D.collider.GetComponent<SteepSlope>() != null)
{
flag3 = false;
}
if (raycastHit2D.collider.GetComponent<NonSlider>() != null)
{
flag3 = false;
}
if (flag3)
{
return true;
}
}
return false;
}
public bool CheckForBump(CollisionSide side)
{
float num = 0.025f;
float num2 = 0.2f;
Vector2 vector = new Vector2(col2d.bounds.min.x + num2, col2d.bounds.min.y + 0.2f);
Vector2 vector2 = new Vector2(col2d.bounds.min.x + num2, col2d.bounds.min.y - num);
Vector2 vector3 = new Vector2(col2d.bounds.max.x - num2, col2d.bounds.min.y + 0.2f);
Vector2 vector4 = new Vector2(col2d.bounds.max.x - num2, col2d.bounds.min.y - num);
float num3 = 0.32f + num2;
RaycastHit2D raycastHit2D = default(RaycastHit2D);
RaycastHit2D raycastHit2D2 = default(RaycastHit2D);
if(side == CollisionSide.left)
{
Debug.DrawLine(vector2, vector2 + Vector2.left * num3, Color.cyan, 0.15f);
Debug.DrawLine(vector, vector + Vector2.left * num3, Color.cyan, 0.15f);
raycastHit2D = Physics2D.Raycast(vector2, Vector2.left, num3, LayerMask.GetMask("Terrain"));
raycastHit2D2 = Physics2D.Raycast(vector, Vector2.left, num3, LayerMask.GetMask("Terrain"));
}
else if (side == CollisionSide.right)
{
Debug.DrawLine(vector4, vector4 + Vector2.right * num3, Color.cyan, 0.15f);
Debug.DrawLine(vector3, vector3 + Vector2.right * num3, Color.cyan, 0.15f);
raycastHit2D = Physics2D.Raycast(vector4, Vector2.right, num3, LayerMask.GetMask("Terrain"));
raycastHit2D2 = Physics2D.Raycast(vector3, Vector2.right, num3, LayerMask.GetMask("Terrain"));
}
else
{
Debug.LogError("Invalid CollisionSide specified.");
}
if(raycastHit2D2.collider != null && raycastHit2D.collider == null)
{
Vector2 vector5 = raycastHit2D2.point + new Vector2((side == CollisionSide.right) ? 0.1f : -0.1f, 1f);
RaycastHit2D raycastHit2D3 = Physics2D.Raycast(vector5, Vector2.down, 1.5f, LayerMask.GetMask("Terrain"));
Vector2 vector6 = raycastHit2D2.point + new Vector2((side == CollisionSide.right) ? -0.1f : 0.1f, 1f);
RaycastHit2D raycastHit2D4 = Physics2D.Raycast(vector6, Vector2.down, 1.5f, LayerMask.GetMask("Terrain"));
if(raycastHit2D3.collider != null)
{
Debug.DrawLine(vector5, raycastHit2D3.point, Color.cyan, 0.15f);
if (!(raycastHit2D4.collider != null))
{
return true;
}
Debug.DrawLine(vector6, raycastHit2D4.point, Color.cyan, 0.15f);
float num4 = raycastHit2D3.point.y - raycastHit2D4.point.y;
if(num4 > 0f)
{
Debug.Log("Bump Height: " + num4.ToString());
return true;
}
}
}
return false;
}
/// <summary>
/// 找到碰撞点的方向也就是上下左右
/// </summary>
/// <param name="collision"></param>
/// <returns></returns>
private CollisionSide FindCollisionSide(Collision2D collision)
{
Vector2 normal = collision.GetSafeContact().Normal ;
float x = normal.x;
float y = normal.y;
if(y >= 0.5f)
{
return CollisionSide.bottom;
}
if (y <= -0.5f)
{
return CollisionSide.top;
}
if (x < 0)
{
return CollisionSide.right;
}
if (x > 0)
{
return CollisionSide.left;
}
Debug.LogError(string.Concat(new string[]
{
"ERROR: unable to determine direction of collision - contact points at (",
normal.x.ToString(),
",",
normal.y.ToString(),
")"
}));
return CollisionSide.bottom;
}
}
[Serializable]
public class HeroControllerStates
{
public bool facingRight;
public bool onGround;
public bool wasOnGround;
public bool inWalkZone;
public bool jumping;
public bool falling;
public bool dashing;
public bool backDashing;
public bool touchingWall;
public bool willHardLand;
public bool preventDash;
public bool preventBackDash;
public bool dashCooldown;
public bool backDashCooldown;
public bool isPaused;
public HeroControllerStates()
{
facingRight = false;
onGround = false;
wasOnGround = false;
inWalkZone = false;
jumping = false;
falling = false;
dashing = false;
backDashing = false;
touchingWall = false;
willHardLand = false;
preventDash = false;
preventBackDash = false;
dashCooldown = false;
backDashCooldown = false;
isPaused = false;
}
}
总结
回到Unity编辑器中,设置好该设置的脚本:
最后播放看看效果,我们先把小骑士拉高点:
运行游戏后生成的没有问题:
再看看轻落地:
再看看向下冲:
OK下一期我们来完善一下生命系统并制作玩家的攻击状态行为,那我们下期再见。