提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、制作一个完整的小骑士跳跃落地行为
- 1.制作动画以及UNITY编辑器编辑
- 2.使用代码实现完整的跳跃落地行为控制
- 3.更多要考虑到的点
- 总结
前言
大家好久不见(虽然就过了半天),经过慎重考虑后我决定这期继续完善小骑士的行为,这其中涉及到素材的导入,创建tk2dSprite和tk2dSpriteAnimation,以及代码控制行为等等,在本篇最后还会考虑到更多的代码设计。
一、制作一个完整的小骑士跳跃落地行为
1.制作动画以及UNITY编辑器编辑
我们先把素材导入后,开始回到tk2dspriteEditor中,由于我们第二期就已经制作了小骑士的spritecollection和spriteanimation,所以我们直接把图片拖进去即可。‘’
我们用一个动画片段Airborne来实现跳跃到降落的全部动画,所以要根据实际情况设置好Clip time。
然后我们再做一个turn动画,这样转身不会只是更改一个transform.localScale.x而已还会播放一个很短暂的动画
2.使用代码实现完整的跳跃落地行为控制
首先我们先处理玩家音效管理问题,你问我为什么先做这个?肯定是因为它简单啊,给小骑士创建一个脚本,在这个脚本中我们这么做:
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 jump;
public AudioSource footStepsRun;
public AudioSource footStepsWalk;
public AudioSource falling;
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.JUMP:
RandomizePitch(jump, 0.9f, 1.1f);
jump.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();
jump.Stop();
falling.Stop();
footStepsRun.Stop();
footStepsWalk.Stop();
}
public void PauseAllSounds()
{
softLanding.Pause();
jump.Pause();
falling.Pause();
footStepsRun.Pause();
footStepsWalk.Pause();
}
public void UnPauseAllSounds()
{
softLanding.UnPause();
jump.UnPause();
falling.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;
}
}
然后回到Unity编辑器中,像我一样给小骑士子对象sounds,然后拖曳音频文件上去
下面几个都同理,最后都拖到这里
然后到了跳跃部分,首先我们要到GlobalEnums中创建HeroSounds和Collsionside的数组
using System;
namespace GlobalEnums
{
public enum ActorStates
{
grounded,
idle,
running,
airborne,
wall_sliding,
hard_landing,
dash_landing,
no_input,
previous
}
public enum CollisionSide
{
top,
left,
right,
bottom,
other
}
public enum HeroSounds
{
FOOTSETP_RUN,
FOOTSTEP_WALK,
SOFT_LANDING,
JUMP,
FALLING
}
public enum PhysLayers
{
DEFAULT,
IGNORE_RAYCAST = 2,
WATER = 4,
UI,
TERRAIN = 8,
PLAYER,
TRANSITION_GATES,
ENEMIES,
PROJECTILES,
HERO_DETECTOR,
TERRAIN_DETECTOR,
ENEMY_DETECTOR,
BOUNCER = 24,
SOFT_TERRAIN = 25
}
}
然后我们要到HeroActions创建一个新的PlayerAction叫jump:
using System;
using InControl;
public class HeroActions : PlayerActionSet
{
public PlayerAction left;
public PlayerAction right;
public PlayerAction up;
public PlayerAction down;
public PlayerTwoAxisAction moveVector;
public PlayerAction jump;
public HeroActions()
{
left = CreatePlayerAction("Left");
left.StateThreshold = 0.3f;
right = CreatePlayerAction("Right");
right.StateThreshold = 0.3f;
up = CreatePlayerAction("Up");
up.StateThreshold = 0.3f;
down = CreatePlayerAction("Down");
down.StateThreshold = 0.3f;
moveVector = CreateTwoAxisPlayerAction(left, right, up, down);
moveVector.LowerDeadZone = 0.15f;
moveVector.UpperDeadZone = 0.95f;
jump = CreatePlayerAction("Jump");
}
}
回到InputHandler.cs中我们为它创建一个新的:
using System;
using System.Collections;
using System.Collections.Generic;
using GlobalEnums;
using InControl;
using UnityEngine;
public class InputHandler : MonoBehaviour
{
public InputDevice gameController;
public HeroActions inputActions;
public void Awake()
{
inputActions = new HeroActions();
}
public void Start()
{
MapKeyboardLayoutFromGameSettings();
if(InputManager.ActiveDevice != null && InputManager.ActiveDevice.IsAttached)
{
}
else
{
gameController = InputDevice.Null;
}
Debug.LogFormat("Input Device set to {0}.", new object[]
{
gameController.Name
});
}
private void MapKeyboardLayoutFromGameSettings()
{
AddKeyBinding(inputActions.up, "W");
AddKeyBinding(inputActions.down, "S");
AddKeyBinding(inputActions.left, "A");
AddKeyBinding(inputActions.right, "D");
AddKeyBinding(inputActions.jump, "X");
}
private static void AddKeyBinding(PlayerAction action, string savedBinding)
{
Mouse mouse = Mouse.None;
Key key;
if (!Enum.TryParse(savedBinding, out key) && !Enum.TryParse(savedBinding, out mouse))
{
return;
}
if (mouse != Mouse.None)
{
action.AddBinding(new MouseBindingSource(mouse));
return;
}
action.AddBinding(new KeyBindingSource(new Key[]
{
key
}));
}
}
通过代码来实现完整的跳跃落地行为控制:
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; //是否进入跳跃队列中
public float MAX_FALL_VELOCITY; //最大下落速度(防止速度太快了)
public int JUMP_STEPS; //最大跳跃的步
public int JUMP_STEPS_MIN; //最小跳跃的步
public int JUMP_QUEUE_STEPS; //最大跳跃队列的步
public bool touchingWall; //是否接触到墙
public bool touchingWallL; //是否接触到的墙左边
public bool touchingWallR; //是否接触到的墙右边
private Rigidbody2D rb2d;
private BoxCollider2D col2d;
private GameManager gm;
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;
}
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;
inputHandler = gm.GetComponent<InputHandler>();
}
void Start()
{
}
void Update()
{
orig_Update();
}
private void orig_Update()
{
current_velocity = rb2d.velocity;
FallCheck();
if(hero_state == ActorStates.running)
{
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.no_input)
{
}
else if(hero_state != ActorStates.no_input)
{
LookForInput();
}
LookForQueueInput();
}
private void FixedUpdate()
{
if (hero_state != ActorStates.no_input)
{
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(rb2d.velocity.y < -MAX_FALL_VELOCITY)
{
rb2d.velocity = new Vector2(rb2d.velocity.x, -MAX_FALL_VELOCITY);
}
if (jumpQueuing)
{
jumpQueueSteps++;
}
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;
jump_steps = 0;
}
/// <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);
}
}
}
else
{
cState.falling = false;
}
}
/// <summary>
/// 翻转小骑士的localScale.x
/// </summary>
public void FlipSprite()
{
cState.facingRight = !cState.facingRight;
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();//规整化
}
}
private void LookForQueueInput()
{
if (acceptingInput)
{
if (inputHandler.inputActions.jump.WasPressed)
{
if (CanJump())
{
HeroJump();
}
else
{
jumpQueueSteps = 0;
jumpQueuing = true;
}
}
if (inputHandler.inputActions.jump.IsPressed)
{
if(jumpQueueSteps <= JUMP_QUEUE_STEPS && CanJump() && jumpQueuing)
{
Debug.LogFormat("Execute Hero Jump");
HeroJump();
}
}
}
}
/// <summary>
/// 可以跳跃吗
/// </summary>
/// <returns></returns>
private bool CanJump()
{
if(hero_state == ActorStates.no_input || hero_state == ActorStates.hard_landing || hero_state == ActorStates.dash_landing || 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);
}
}
}
/// <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()
{
cState.falling = false;
jump_steps = 0;
SetState(ActorStates.grounded);
cState.onGround = true;
}
/// <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(collision.gameObject.GetComponent<SteepSlope>() == null && hero_state != ActorStates.hard_landing)
{
BackOnGround();
}
}
}
}
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(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;
}
/// <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 touchingWall;
public bool isPaused;
public HeroControllerStates()
{
facingRight = false;
onGround = false;
wasOnGround = false;
inWalkZone = false;
jumping = false;
falling = false;
touchingWall = false;
isPaused = false;
}
}
其中我们需要创建两个空脚本,日后要用:
using UnityEngine;
public class NonSlider : MonoBehaviour
{
}
using UnityEngine;
public class SteepSlope : MonoBehaviour
{
}
还有碰撞检测中自己创建的GetSafeContact():
using System;
using UnityEngine;
public static class Collision2DUtils
{
private static ContactPoint2D[] contactsBuffer;
static Collision2DUtils()
{
contactsBuffer = new ContactPoint2D[1];
}
public static Collision2DSafeContact GetSafeContact(this Collision2D collision)
{
if(collision.GetContacts(contactsBuffer) >= 1)
{
ContactPoint2D contactPoint2D = contactsBuffer[0];
return new Collision2DSafeContact
{
Point = contactPoint2D.point,
Normal = contactPoint2D.normal,
IsLegitimate = true
};
}
Vector2 b = collision.collider.transform.TransformPoint(collision.collider.offset);
Vector2 a = collision.otherCollider.transform.TransformPoint(collision.otherCollider.offset);
return new Collision2DSafeContact
{
Point = (a + b) * 0.5f,
Normal = (a - b).normalized,
IsLegitimate = false
};
}
public struct Collision2DSafeContact
{
public Vector2 Point;
public Vector2 Normal;
public bool IsLegitimate;
}
}
回到Unity编辑器中设置好参数,我们就会发现小骑士能正常跳跃了。
3.更多要考虑到的点
更多要考虑的点指的是无论你按跳跃键按的再用力或再小力,jumpSpeed决定了小骑士的跳跃高度,这往往是我们不想看到的,我们想根据按下的力度来决定玩家跳跃的高度,因此我们要制作一个jumpRelease的完整行为来控制玩家跳跃的高度。
所以完整的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; //最小跳跃的步
[SerializeField] private int JUMP_QUEUE_STEPS; //最大跳跃队列的步
[SerializeField] private int JUMP_RELEASE_QUEUE_STEPS;
public bool touchingWall; //是否接触到墙
public bool touchingWallL; //是否接触到的墙左边
public bool touchingWallR; //是否接触到的墙右边
private Rigidbody2D rb2d;
private BoxCollider2D col2d;
private GameManager gm;
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;
}
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;
inputHandler = gm.GetComponent<InputHandler>();
}
void Start()
{
}
void Update()
{
orig_Update();
}
private void orig_Update()
{
current_velocity = rb2d.velocity;
FallCheck();
if(hero_state == ActorStates.running)
{
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.no_input)
{
}
else if(hero_state != ActorStates.no_input)
{
LookForInput();
}
LookForQueueInput();
}
private void FixedUpdate()
{
if (hero_state != ActorStates.no_input)
{
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(rb2d.velocity.y < -MAX_FALL_VELOCITY)
{
rb2d.velocity = new Vector2(rb2d.velocity.x, -MAX_FALL_VELOCITY);
}
if (jumpQueuing)
{
jumpQueueSteps++;
}
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 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);
}
}
}
else
{
cState.falling = false;
}
}
/// <summary>
/// 翻转小骑士的localScale.x
/// </summary>
public void FlipSprite()
{
cState.facingRight = !cState.facingRight;
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();
}
}
}
private void LookForQueueInput()
{
if (acceptingInput)
{
if (inputHandler.inputActions.jump.WasPressed)
{
if (CanJump())
{
HeroJump();
}
else
{
jumpQueueSteps = 0;
jumpQueuing = true;
}
}
if (inputHandler.inputActions.jump.IsPressed)
{
if(jumpQueueSteps <= JUMP_QUEUE_STEPS && CanJump() && jumpQueuing)
{
Debug.LogFormat("Execute Hero Jump");
HeroJump();
}
}
}
}
/// <summary>
/// 可以跳跃吗
/// </summary>
/// <returns></returns>
private bool CanJump()
{
if(hero_state == ActorStates.no_input || hero_state == ActorStates.hard_landing || hero_state == ActorStates.dash_landing || 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()
{
cState.falling = false;
jump_steps = 0;
SetState(ActorStates.grounded);
cState.onGround = true;
}
/// <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(collision.gameObject.GetComponent<SteepSlope>() == null && hero_state != ActorStates.hard_landing)
{
BackOnGround();
}
}
}
}
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(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;
}
/// <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 touchingWall;
public bool isPaused;
public HeroControllerStates()
{
facingRight = false;
onGround = false;
wasOnGround = false;
inWalkZone = false;
jumping = false;
falling = false;
touchingWall = false;
isPaused = false;
}
}
回到Unity编辑器中设置好参数,就可以看到跳跃高度随着按跳跃键的强度而改变了
总结
跳跃高度随着按跳跃键的强度而改变:
下一期我们来做冲刺动画吧。