Unity教程(九)角色攻击的改进

news2024/11/25 12:25:33

Unity开发2D类银河恶魔城游戏学习笔记

Unity教程(零)Unity和VS的使用相关内容
Unity教程(一)开始学习状态机
Unity教程(二)角色移动的实现
Unity教程(三)角色跳跃的实现
Unity教程(四)碰撞检测
Unity教程(五)角色冲刺的实现
Unity教程(六)角色滑墙的实现
Unity教程(七)角色蹬墙跳的实现
Unity教程(八)角色攻击的基本实现
Unity教程(九)角色攻击的改进

Unity教程(十)Tile Palette搭建平台关卡


如果你更习惯用知乎
Unity开发2D类银河恶魔城游戏学习笔记目录


文章目录

  • Unity开发2D类银河恶魔城游戏学习笔记
  • 前言
  • 一、概述
  • 二、基础攻击的改进
    • (1)调整动画提升流畅度
    • (2)解决攻击时滑动的问题
    • (3)解决两次攻击间角色移动的问题
    • (4)攻击间移动
    • (5)添加攻击方向
    • (6)加速操作(不需要做)
  • 三、代码整理
  • 总结 完整代码
    • Player.cs
    • PlayerPrimaryAttackState.cs
    • PlayerIdleState.cs


前言

本文为Udemy课程The Ultimate Guide to Creating an RPG Game in Unity学习笔记,如有错误,欢迎指正。

本节进行角色基本攻击的改进。
对应b站视频:
【Unity教程】从0编程制作类银河恶魔城游戏P40
【Unity教程】从0编程制作类银河恶魔城游戏P41


一、概述

本节主要进行角色基本攻击的改进。
空闲状态到移动状态之间的转换添加条件 ! isBusy
在这里插入图片描述
本节改进了基本攻击的小问题,添加了攻击移动和攻击方向,提升了动画流畅度。在最后对代码进行了一些整理。整体结构如下:
在这里插入图片描述


二、基础攻击的改进

(1)调整动画提升流畅度

调整三个攻击动画的采样率,并且将最后一个攻击动画事件提前。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(2)解决攻击时滑动的问题

现在我们攻击时角色会来回滑动,因为我们的角色还保持着移动的速度但已经播放攻击动画了。
在这里插入图片描述
这里教程中的解决方式是在攻击时将角色速度置为0。
但我个人感觉这种处理方式会让攻击缺少一些灵活性。但如果想实现边移动边攻击就需要有另外的动画和另外的逻辑来实现,需要另行设计,所以在此先按教程中的实现。
此外,我们可以在进入时给stateTimer赋一个值,让角色停顿一下再攻击,做出惯性的效果。
在PlayerPrimaryAttack中修改:

    //进入
    public override void Enter()
    {
        base.Enter();
        if (comboCounter > 2 || Time.time > lastTimeAttacked + comboWindow)
            comboCounter = 0;

        player.anim.SetInteger("comboCounter",comboCounter);


        stateTimer = 0.1f;
        
    }

    // 更新
    public override void Update()
    {
        base.Update();

        if (stateTimer < 0)
            rb.velocity = new Vector2(0, 0);

        if(triggerCalled)
            stateMachine.ChangeState(player.idleState);
    }

在这里插入图片描述

(3)解决两次攻击间角色移动的问题

我依然认为这种处理方法缺乏灵活性,但可以先学习一下他的处理方式。
如果需要解决这个问题,我们可以使用协程。我参照了以下文章的讲解:
进程、线程和协程之间的区别和联系
Unity 协程(Coroutine)原理与用法详解
Unity官方手册

协程是通过迭代器来实现功能的,通过关键字IEnumerator来定义。

启动协程:
StartCoroutine(IEnumerator routine:通过方法形式调用
StartCoroutine(string methodName,object values):带参数的通过方法名进行调用

停止携程:
StopCoroutine(string methodName:通过方法名(字符串)来进行
StopCoroutine(IEnumerator routine:通过方法形式来调用
StopCoroutine(Coroutine routine):通过指定的协程来关闭

yield方法:
yield return null; 暂停协程等待下一帧继续执行
yield return 0或其他数字; 暂停协程等待下一帧继续执行
yield return new WairForSeconds(时间); 等待规定时间后继续执行
yield return StartCoroutine(“协程方法名”); 开启一个协程(嵌套协程)

在Player中创建一个参数isBusy,并定义BusyFor函数

    public bool isBusy { get; private set; }

    public IEnumerator BusyFor(float _seconds)
    {
        isBusy = true;

        yield return new WaitForSeconds(_seconds);

        isBusy = false;
    }

在每次攻击结束时调用,在PlayerPriamaryAttack

    //退出
    public override void Exit()
    {
        base.Exit();

        player.StartCoroutine("BusyFor", 0.15f);

        comboCounter++;
        lastTimeAttacked = Time.time;
    }

然后给空闲状态转到移动状态添加一个 ! Busy 的条件:

    //更新
    public override void Update()
    {
        base.Update();

        //切换到移动状态
        if(xInput!=0 && !player.isBusy)
            stateMachine.ChangeState(player.moveState);
    }

在攻击期间即使我一直按着移动键,角色也不能移动了,效果如下:
在这里插入图片描述

(4)攻击间移动

给连击中的每一段设置一个位移。
在Player中添加变量攻击位移数组

    [Header("Attack details")]
    public float[] attackMovement;

在开始攻击时设置位移

    //进入
    public override void Enter()
    {
        base.Enter();
        if (comboCounter > 2 || Time.time > lastTimeAttacked + comboWindow)
            comboCounter = 0;

        player.anim.SetInteger("comboCounter",comboCounter);

        player.SetVelocity(player.attackMovement[comboCounter] * player.facingDir, rb.velocity.y);

        stateTimer = 0.1f;
        
    }

给数列赋值,调整数值直到你想要的效果
在这里插入图片描述
在这里插入图片描述

(5)添加攻击方向

现在的攻击还有一个小问题,在攻击后马上按相反方向键向角色身后攻击,我们会发现角色完全没有转向。
因为我们在攻击时始终是向着角色面向的方向,没有翻转。
我们在PlayerPrimaryAttack中添加这个功能:

    //进入
    public override void Enter()
    {
        base.Enter();
        if (comboCounter > 2 || Time.time > lastTimeAttacked + comboWindow)
            comboCounter = 0;

        player.anim.SetInteger("comboCounter",comboCounter);


        float attackDir = player.facingDir;

        if (xInput != 0)
            attackDir = xInput;

        player.SetVelocity(player.attackMovement[comboCounter].x * attackDir, player.attackMovement[comboCounter].y);

        stateTimer = 0.1f;
        
    }

当没有输入时,向角色面向方向攻击;当有输入时,向输入方向攻击。现在角色可以迅速回身攻击了。
在这里插入图片描述

(6)加速操作(不需要做)

教程中顺便讲到了使所有动画加速的操作。这个操作可以实现不同武器不同攻速等操作,或者实现整体加速。
用player.anim.speed 进行实现,在进入攻击状态时加速,在退出时恢复原速。

    //进入
    public override void Enter()
    {
        base.Enter();
        if (comboCounter > 2 || Time.time > lastTimeAttacked + comboWindow)
            comboCounter = 0;

        player.anim.SetInteger("comboCounter",comboCounter);
        player.anim.speed = 3.0f;

        player.SetVelocity(player.attackMovement[comboCounter].x * player.facingDir, player.attackMovement[comboCounter].y);

        stateTimer = 0.1f;
        
    }

    //退出
    public override void Exit()
    {
        base.Exit();

        player.StartCoroutine("BusyFor", 0.15f);
        player.anim.speed = 1.0f;

        comboCounter++;
        lastTimeAttacked = Time.time;
    }

为了效果明显,我调了三倍速。
在这里插入图片描述

三、代码整理

速度置零的操作,我们在Player中写一个函数ZeroVelocity()用来调用

    //速度置零
    public void ZeroVelocity() => rb.velocity = new Vector2(0, 0);

在PlayerPrimaryAttack中改为调用函数

//PlayerPrimaryAttackState:基本攻击状态
    // 更新
    public override void Update()
    {
        base.Update();

        if (stateTimer < 0)
            player.ZeroVelocity();

        if(triggerCalled)
            stateMachine.ChangeState(player.idleState);
    }

Player中代码划分区域
速度设置

    #region 速度设置
    //速度置零
    public void ZeroVelocity() => rb.velocity = new Vector2(0, 0);

    //设置速度
    public void SetVelocity(float _xVelocity, float _yVelocity)
    {
        rb.velocity = new Vector2(_xVelocity, _yVelocity);
        FlipController(_xVelocity);
    }
    #endregion

翻转

    #region 翻转
    //翻转实现
    public void Flip()
    {
        facingDir = -1 * facingDir;
        facingRight = !facingRight;
        transform.Rotate(0, 180, 0);
    }
    //翻转控制
    public void FlipController(float _x)
    {
        if (_x > 0 && !facingRight)
            Flip();
        else if(_x < 0 && facingRight)
            Flip();
    }
    #endregion

碰撞

    #region 碰撞
    //碰撞检测
    public bool isGroundDetected() => Physics2D.Raycast(groundCheck.position, Vector2.down, groundCheckDistance, whatIsGround);
    public bool isWallDetected() => Physics2D.Raycast(wallCheck.position,Vector2.right * facingDir,wallCheckDistance,whatIsGround);


    //绘制碰撞检测
    private void OnDrawGizmos()
    {
        Gizmos.DrawLine(groundCheck.position, new Vector3(groundCheck.position.x, groundCheck.position.y - groundCheckDistance));
        Gizmos.DrawLine(wallCheck.position, new Vector3(wallCheck.position.x+ wallCheckDistance, wallCheck.position.y));
    }
    #endregion

在这里插入图片描述
给PlayerPrimaryAttack改名为PlayerPrimaryAttackState
右键状态名->重命名->Enter
在这里插入图片描述
在这里插入图片描述

总结 完整代码

Player.cs

添加攻击位移变量
添加isBusy和协程

//Player:玩家
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    [Header("Attack details")]
    public Vector2[] attackMovement;
    public bool isBusy { get; private set; }

    [Header("Move Info")]
    public float moveSpeed = 8f;
    public int facingDir { get; private set; } = 1;
    private bool facingRight = true;

    public float jumpForce = 12f;


    [Header("Dash Info")]
    [SerializeField] private float dashCoolDown;
    private float dashUsageTimer;
    public float dashSpeed=25f;
    public float dashDuration=0.2f;
    public float dashDir {  get; private set; }
    


    [Header("Collision Info")]
    [SerializeField] private Transform groundCheck;
    [SerializeField] private float groundCheckDistance;
    [SerializeField] private Transform wallCheck;
    [SerializeField] private float wallCheckDistance;
    [SerializeField] private LayerMask whatIsGround;

    #region 组件
    public Animator anim { get; private set; }
    public Rigidbody2D rb { get; private set; }
    #endregion


    #region 状态
    public PlayerStateMachine StateMachine { get; private set; }
    public PlayerIdleState idleState { get; private set; }
    public PlayerMoveState moveState { get; private set; }
    public PlayerJumpState jumpState { get; private set; }
    public PlayerAirState airState { get; private set; }
    public PlayerDashState dashState { get; private set; }
    public PlayerWallSlideState wallSlideState { get; private set; }
    public PlayerWallJumpState wallJumpState { get; private set; }
    public PlayerPrimaryAttack primaryAttack { get; private set; }  

    #endregion

    //创建对象
    private void Awake()
    {
        StateMachine = new PlayerStateMachine();

        idleState = new PlayerIdleState(StateMachine, this, "Idle");
        moveState = new PlayerMoveState(StateMachine, this, "Move");
        jumpState = new PlayerJumpState(StateMachine, this, "Jump");
        airState = new PlayerAirState(StateMachine, this, "Jump");
        dashState = new PlayerDashState(StateMachine, this, "Dash");
        wallSlideState = new PlayerWallSlideState(StateMachine, this, "WallSlide");
        wallJumpState = new PlayerWallJumpState(StateMachine, this, "Jump");
        primaryAttack = new PlayerPrimaryAttack(StateMachine, this, "Attack");

        anim = GetComponentInChildren<Animator>();
        rb = GetComponent<Rigidbody2D>();

    }

    // 设置初始状态
    private void Start()
    {
        StateMachine.Initialize(idleState);
    }

    // 更新
    private void Update()
    {
        StateMachine.currentState.Update();

        CheckForDashInput();
    }

    public IEnumerator BusyFor(float _seconds)
    {
        isBusy = true;

        yield return new WaitForSeconds(_seconds);

        isBusy = false;
    }

    //设置触发器
    public void AnimationTrigger() => StateMachine.currentState.AnimationFinishTrigger();

    //检查冲刺输入
    public void CheckForDashInput()
    {

        dashUsageTimer -= Time.deltaTime;

        if (Input.GetKeyDown(KeyCode.LeftShift) && dashUsageTimer<0)
        {
            dashUsageTimer = dashCoolDown;
            dashDir = Input.GetAxisRaw("Horizontal");

            if (dashDir == 0)
                dashDir = facingDir;

            StateMachine.ChangeState(dashState);
        }
    }

    #region 速度设置
    //速度置零
    public void ZeroVelocity() => rb.velocity = new Vector2(0, 0);

    //设置速度
    public void SetVelocity(float _xVelocity, float _yVelocity)
    {
        rb.velocity = new Vector2(_xVelocity, _yVelocity);
        FlipController(_xVelocity);
    }
    #endregion

    #region 翻转
    //翻转实现
    public void Flip()
    {
        facingDir = -1 * facingDir;
        facingRight = !facingRight;
        transform.Rotate(0, 180, 0);
    }
    //翻转控制
    public void FlipController(float _x)
    {
        if (_x > 0 && !facingRight)
            Flip();
        else if(_x < 0 && facingRight)
            Flip();
    }
    #endregion

    #region 碰撞
    //碰撞检测
    public bool isGroundDetected() => Physics2D.Raycast(groundCheck.position, Vector2.down, groundCheckDistance, whatIsGround);
    public bool isWallDetected() => Physics2D.Raycast(wallCheck.position,Vector2.right * facingDir,wallCheckDistance,whatIsGround);


    //绘制碰撞检测
    private void OnDrawGizmos()
    {
        Gizmos.DrawLine(groundCheck.position, new Vector3(groundCheck.position.x, groundCheck.position.y - groundCheckDistance));
        Gizmos.DrawLine(wallCheck.position, new Vector3(wallCheck.position.x+ wallCheckDistance, wallCheck.position.y));
    }
    #endregion
}

PlayerPrimaryAttackState.cs

攻击时速度置零
添加协程
添加攻击位移
添加攻击方向

//PlayerPrimaryAttackState:基本攻击状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerPrimaryAttackState : PlayerState
{

    private int comboCounter;

    private float lastTimeAttacked;
    private float comboWindow = 2
        ;
    public PlayerPrimaryAttackState(PlayerStateMachine _stateMachine, Player _player, string _animBoolName) : base(_stateMachine, _player, _animBoolName)
    {
    }

    //进入
    public override void Enter()
    {
        base.Enter();
        if (comboCounter > 2 || Time.time > lastTimeAttacked + comboWindow)
            comboCounter = 0;

        player.anim.SetInteger("comboCounter",comboCounter);


        float attackDir = player.facingDir;

        if (xInput != 0)
            attackDir = xInput;

        player.SetVelocity(player.attackMovement[comboCounter].x * attackDir, player.attackMovement[comboCounter].y);

        stateTimer = 0.1f;
        
    }

    //退出
    public override void Exit()
    {
        base.Exit();

        player.StartCoroutine("BusyFor", 0.15f);

        comboCounter++;
        lastTimeAttacked = Time.time;
    }

    // 更新
    public override void Update()
    {
        base.Update();

        if (stateTimer < 0)
            player.ZeroVelocity();

        if(triggerCalled)
            stateMachine.ChangeState(player.idleState);
    }
}

PlayerIdleState.cs

修改转到移动状态的条件

//PlayerIdleState:空闲状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;

public class PlayerIdleState : PlayerGroundedState
{
    //构造函数
    public PlayerIdleState(PlayerStateMachine _stateMachine, Player _player, string _animBoolName) : base(_stateMachine, _player, _animBoolName)
    {
    }

    //进入
    public override void Enter()
    {
        base.Enter();

        player.SetVelocity(0, rb.velocity.y);
    }

    //退出
    public override void Exit()
    {
        base.Exit();
    }

    //更新
    public override void Update()
    {
        base.Update();

        //切换到移动状态
        if(xInput!=0 && !player.isBusy)
            stateMachine.ChangeState(player.moveState);
    }
}

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

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

相关文章

WindowsAPI 查阅笔记:进程间管道通信

进程间有名管道的通信&#xff1a; 1.1 重叠I/O&#xff08;Overlapped I/O&#xff09; 重叠I/O&#xff08;Overlapped I/O&#xff09;是Windows编程中的一种异步 I / O 处理方式&#xff0c;它允许程序在发出I/O请求后继续执行其他任务&#xff0c;而不必等待I/O操作完成…

萌啦定价工具,萌啦数据ozon定价工具

在电商行业日益竞争激烈的今天&#xff0c;精准定价成为了商家们获取市场竞争优势的关键一环。尤其是对于在Ozon平台上耕耘的卖家而言&#xff0c;无论是本土卖家还是跨境商家&#xff0c;如何快速、准确地制定出既符合市场需求又能保障利润的价格策略&#xff0c;成为了亟待解…

高防服务器的机制和原理

高防服务器是一种具备强大防御能力的服务器&#xff0c;旨在保护网站免受各种网络攻击&#xff0c;如DDoS&#xff08;分布式拒绝服务&#xff09;攻击、CC&#xff08;ChallengeCollapsar&#xff09;攻击等。今天小编将从流量过滤与清洗、负载均衡与反向代理、实时监控与报警…

圈内水刊“三巨头”之首实至名归?发文量飙升至9000+,硕博小白照样发1区TOP!

【SciencePub学术】昨天&#xff0c;小编给大家介绍了环境水刊“三巨头”之一的《Journal of Hazardous Materials》&#xff0c;本期&#xff0c;给大家带来的是位于环境水刊“三巨头”之首的《Science of the Total Environment》&#xff0c;属于JCR1区中科院1区TOP&#xf…

冷数据归档(历史库),成本与性能如何兼得?| OceanBase应用实践

随着数据量的迅猛增长&#xff0c;企业和组织在数据库管理方面遭遇的挑战愈发凸显。数据库性能逐渐下滑、存储成本节节攀升&#xff0c;以及数据运维复杂性的增加&#xff0c;这些挑战使得DBA和开发者在数据管理上面临更大的压力。 为了应对这些挑战&#xff0c;对数据生命周期…

vulnstack-5

环境搭建 靶场虚拟机共用两个&#xff0c;一个外网一个内网&#xff0c;用来练习红队相关内容和方向&#xff0c;主要包括常规信息收集、Web攻防、代码审计、漏洞利用、内网渗透以及域渗透等相关内容学习。 虚拟机密码 win7 sun\heart 123.com sun\Administrator dc123.com # …

华为软件测试笔试真题,赶快收藏

软件测试工程师笔试题目 一&#xff0e;填空 1、 系统测试使用&#xff08; C &#xff09;技术, 主要测试被测应用的高级互操作性需求, 而无需考虑被测试应用的内部结构。 A、 单元测试 B、 集成测试 C、 黑盒测试 D、白盒测试 2、单元测试主要的测试技术不包括&#xff08;…

【nvidia-smi】Failed to initialize NVML: Driver/library version mismatch

服务器更新后&#xff0c;输入nvidia-smi出现如下报错&#xff1a; 解决方法参考&#xff1a; 已解决【nvidia-smi】Failed to initialize NVML: Driver/library version mismatch解决方法-腾讯云开发者社区-腾讯云 (tencent.com) 输入命令查看nvidia驱动的版本号&#xff1a…

Linux软件包yum

目录 Linux软件包管理器 yum关于rzsz注意事项查看软件包如何安装软件卸载命令 Linux开发工具Linux编辑器-vim使用1. vim的基本概念2. vim的基本操作3. vim正常模式命令集4. vim末行模式命令集5. vim操作总结 小彩蛋 Linux软件包管理器 yum 软件包 在Linux下安装软件&#xff…

java基础学习笔记2(8.9)

String equals比较堆里值 字符串比较用str1.equals(str2)&#xff1b; 比较栈里的值 JDK7以后字符串常量池进入了堆里面。 在Java中&#xff0c;StringBuffer 和 StringBuilder 是用于创建可变字符串的类。它们提供了比 String 更高效的字符串操作&#xff0c;尤其是在需要…

ICM-20948芯片详解(13)

接前一篇文章&#xff1a;ICM-20948芯片详解&#xff08;12&#xff09; 六、寄存器详解 2. USER BANK 0寄存器详述 &#xff08;60&#xff09;FIFO_COUNTH 高5位&#xff0c;计数表示FIFO中写入的字节数。 &#xff08;61&#xff09;FIFO_COUNTL 低8位&#xff0c;计数表…

IMAX ENHANCED认证的护眼三色激光投影仪,选极米 RS 10 Pro把专业IMAX影院带回家

对于追求大屏体验的用户来说&#xff0c;智能投影仪有着电视机无法比拟的优势。因此&#xff0c;智能投影仪如今也逐步替代传统电视机&#xff0c;成为了许多家庭的必备家电之一。对于当代年轻人而言&#xff0c;无论是追剧、看电影还是打游戏&#xff0c;大屏幕都始终比传统电…

西部数据拒绝2.62亿美元巨额赔偿:硬盘专利侵权案将上诉

西部数据&#xff08;Western Digital&#xff0c;简称WD&#xff09;被加州的一个陪审团裁定需支付2.62亿美元的赔偿金&#xff0c;原因是该公司侵犯了德国科学家Dieter Suess拥有的硬盘驱动器&#xff08;HDD&#xff09;记录技术专利。Suess是维也纳大学功能性材料物理学教授…

linux系统编程:进程(2)

1.在fork函数前打开函数和fork之后打开文件区别 1.fork之前open 子进程会继承父进程已打开的文件的相关信息所以&#xff0c;此时父子进程 会影响统一个offset值 2.fork之后open 父子进程各自有各自的打开文件的信息:相互之间不会有影响。 2.进程创建好之后: 1.任务--- …

Java流程控制01:用户交互Scanner

本节教学视频链接&#xff1a;https://www.bilibili.com/video/BV12J41137hu?p33&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5https://www.bilibili.com/video/BV12J41137hu?p33&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 Scanner 类用于扫描输入文本从字符串中提…

具备长短距离注意力模块的Lite Transformer

Transformer在自然语言处理(例如&#xff0c;机器翻译、问答)中已经变得无处不在;然而&#xff0c;它需要大量的计算才能实现高性能&#xff0c;这使得它不适合受硬件资源和电池严格限制的移动应用程序。在本文中&#xff0c;提出了一个高效的移动NLP架构&#xff0c;Lite Tran…

jq8900-16p与stm32f103c8t6测试

jq8900-16p与stm32f103c8t6测试 引言 本文从购买器件, 到连线步骤, 再到驱动的模块原理讲解, 后面根据不同的语音, 进行文字转语音步骤,全在资料导航里面. 本模块后面着重讲解, jq8900快速移植 本文资料导航 模块购买步骤 跳转 连线步骤 跳转 https://blog.csdn.net/qq_57484…

Vue的事件处理、事件修饰符、键盘事件

目录 1. 事件处理基本使用2. 事件修饰符3. 键盘事件 1. 事件处理基本使用 使用v-on:xxx或xxx绑定事件&#xff0c;其中xxx是事件名&#xff0c;比如clickmethods中配置的函数&#xff0c;都是被Vue所管理的函数&#xff0c;this的指向是vm或组件实例对象 <!DOCTYPE html&g…

探秘未来驾驶,汽车智能座舱软件测试的艺术与科学

什么是智能座舱软件测试 智能座舱软件测试是一种专门针对现代汽车中集成的先进驾驶辅助系统(ADAS)、信息娱乐系统、人机交互界面(HMI)以及其他智能座舱组件的软件质量保证过程。随着汽车行业的数字化和智能化转型&#xff0c;无人驾驶的出现&#xff0c;智能座舱已成为汽车用户…

Keepalived+Haproxy实现高可用

keepalived利用 VRRP Script 技术&#xff0c;可以调用外部的辅助脚本进行资源监控&#xff0c;并根据监控的结果实现优先 动态调整&#xff0c;从而实现其它应用的高可用性功能。 一、VRRP Script 配置 1、定义脚本 vrrp_script&#xff1a;自定义资源监控脚本&#xff0c;…