[Unity Demo]从零开始制作空洞骑士Hollow Knight第六集:制作小骑士完整的跳跃落地行为

news2024/9/21 9:08:48

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、制作一个完整的小骑士跳跃落地行为
    • 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编辑器中设置好参数,就可以看到跳跃高度随着按跳跃键的强度而改变了


总结

跳跃高度随着按跳跃键的强度而改变:

下一期我们来做冲刺动画吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2151911.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【CSS Tricks】如何做一个粒子效果的logo

效果展示 代码展示 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>粒子效果Logo</title>…

VUE面试题(单页应用及其首屏加载速度慢的问题)

目录 一、单页应用 1.概念 2.单页面应用的优缺点 二、多页面应用&#xff1a; 1.概念 2.区别 三、SPA的实现 1.原理 2.方式&#xff1a; 3.Hash与History模式有什么区别 四、首屏加载速度慢如何优化 1.什么是首屏加载&#xff1f; 2.首屏加载慢的原因 3.如何解决…

OpenCV特征检测(2)边缘检测函数Canny()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 使用 Canny 算法 48在图像中查找边缘。 该函数使用 Canny 算法在输入图像中查找边缘&#xff0c;并在输出地图 edges 中标记它们。在 threshold1…

【0332】Postgres内核 start background worker(s)

0. 相关联文件 postmaster.cilist.h1. 启动 background worker(s) Postgres内核在 PostmasterMain() 函数中初始化 postmaster 守护进程时候,通过 maybe_start_bgworkers() 函数开始尝试启动 background worker(s)。 若时机恰当,则启动 background worker(s)。 作为一种附带…

基于云的补丁管理

什么是云补丁 云补丁或基于云的补丁管理是指扫描和检测缺失补丁、测试补丁并将它们部署到所需系统的过程&#xff0c;所有这些都通过基于云的控制台或软件完成。虽然补丁管理工作流程通常保持不变&#xff0c;但基于云的补丁管理的主要区别在于&#xff0c;整个过程仅通过基于…

iOS 18 适配 Xcode 16 问题

在适配 iOS 18 xcode 16时遇到的问题&#xff0c;记录一下。 1. 使用xcode 16 iOS 18 运行App时遇到&#xff0c;APP 的icon 出现空白现象。 原先APP icon 设置方案。 暂时解决方案&#xff1a; 2、

Python 低层多线程接口_thread的用法

_thread是python标准库中的一个低层多线程API&#xff0c;可以在进程中启动线程来处理任务&#xff0c;并且提供了简单的锁机制来控制共享资源的同步访问。本文就_thread模块的用法和特性做个简单的演示。 文章目录 一、进程和线程的区别二、_thread模块的用法2.1 派生线程2.2…

Percona发布开源DBaaS平台;阿里云RDS发布全球多活数据库(GAD);Redshift支持自然语言生成SQL

重要更新 1. 云栖大会于本周四/五在杭州举行&#xff0c;周五上午云栖主论坛阿里云数据库负责人李飞飞将发表《从数据到智能&#xff1a;DataAI驱动的云原生数据库》演讲&#xff0c;另外&#xff0c;还有多场次的数据库专场&#xff0c;感兴趣的可以现场或在线观看&#xff1a…

个人小结(2.0)

离谱&#xff0c;困扰着几周的问题今天偶然发现了解决方法。 问题如下&#xff1a;就是对应的模块引入爆红&#xff0c;但是单击进入引入的文件没有问题 然后它的提示是&#xff1a; 无法找到模块“../views/screen/index.vue”的声明文件。“c:/Users/10834/Desktop/0716_pro…

vue-使用refs取值,打印出来是个数组??

背景&#xff1a; 经常使用$refs去获取组件实例&#xff0c;一般都是拿到实例对象&#xff0c;这次去取值的时候发现&#xff0c;拿到的竟然是个数组。 原因&#xff1a; 这是vue的特性,自动把v-for里面的ref展开成数组的形式&#xff0c;哪怕你的ref名字是唯一的&#xff01…

DataX--Web:图形化界面简化大数据任务管理

在处理大数据任务时&#xff0c;频繁地修改配置文件或编写脚本可能会变得繁琐且容易出错。DataX Web提供了一个图形化界面&#xff0c;旨在简化这些操作&#xff0c;让用户通过直观的界面管理数据同步任务。 DataX Web简介 DataX Web是一个开源项目&#xff0c;它允许用户通过…

帧率和丢帧分析理论

一、丢帧问题概述 应用丢帧通常指的是在应用程序的界面绘制过程中&#xff0c;由于某些原因导致界面绘制的帧率下降&#xff0c;从而造成界面卡顿、动画不流畅等问题。以60Hz刷新率为例子&#xff0c;想要达到每秒60帧&#xff08;即60fps&#xff09;的流畅体验&#xff0c;每…

Python 序列( 列表 字典 元组 集合)

列表简介&#xff1a; 1.列表&#xff1a;用于存储任意数目、任意类型的数据集合。 2.列表是内置可变序列&#xff0c;是包含多个元素的有序连续的内存空间。列表的标准语法格式&#xff1a;a[10,20,30,40]其中&#xff0c;10,20,30,40这些称为&#xff1a;列表a的元素。 3.…

海外云市场分析

海外云市场数据洞察 2024 H1 季度数据 H1季度,全球云基础设施服务指数同比增长21%,达到798亿美元 (相比去年增加134亿美元),三大云服务提供商— AWS,微软Azure 和GCP 营收总增长率为24%,占总市场66%。 其中三大云厂商同比营收增长排序(2024 H1):微软 31%,G…

用户态缓存:环形缓冲区(Ring Buffer)

目录 环形缓冲区&#xff08;Ring Buffer&#xff09;简介 为什么选择环形缓冲区&#xff1f; 代码解析 1. 头文件与类型定义 1.1 头文件保护符 1.2 包含必要的标准库 1.3 类型定义 2. 环形缓冲区结构体 2.1 结构体成员解释 3. 辅助宏与内联函数 3.1 min 宏 3.2 is…

【Python报错已解决】xlrd.biffh.XLRDError: Excel xlsx file; not supported

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 专栏介绍 在软件开发和日常使用中&#xff0c;BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

最新LinPay码支付 免签支付系统源码 免授权版本(含搭建教程)

最新LinPay码支付 免签支付系统源码 免授权版本 服务集成商兼容市面所有易支付&#xff0c;兼容所有商城LinPay是专为个人站长打造的聚合免签系统&#xff0c;拥有卓越的性能和丰富的功能。它采用全新轻量化的界面UI&#xff0c;让您能更方便快捷地解决知识付费和运营赞助的难…

中间件知识点-消息中间件(Rabbitmq)一

消息中间件介绍 MQ的作用(优点)主要有以下三个方面&#xff1a; a.异步 b.解耦 c.削峰 MQ的作用(缺点)主要有以下三个方面&#xff1a; a.系统可用性降低 b.系统复杂度提高 c.存在消息一致性问题需要解决 备注&#xff1a; 引入MQ后系统的复杂度会大大提高。 以前服务之间可以…

移动开发(三):使用.NET MAUI打包第一个安卓APK完整过程

目录 一、修改AndroidManifest.xml 配置APP基本信息权限 二、修改项目属性调整输出Android包格式为APK 三、项目发布 四、APP分发 五、总结 之前给大家介绍过使用使用.NET MAUI开发第一个安卓APP,今天给大家介绍如何打包成APK,然后安装到安卓手机正常运行。这里还是沿用…

如何下载ComfyUI开发版

看B站视频&#xff0c;见用绘世可以下载ComfyUI开发版&#xff0c;而我又不想在电脑里放太多东西&#xff0c;于是研究了一下&#xff0c;如何直接从GitHub网站下载。具体步骤看图示。 看压缩包内容&#xff0c;应该直接解压覆盖就可以了&#xff0c;暂未有时间测试。