提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、扩展小骑士落地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++;