当前位置: 首页 > article >正文

Unity实现按键设置功能代码

一、前言

最近在学习unity2D,想做一个横版过关游戏,需要按键设置功能,让用户可以自定义方向键与攻击键等。

自己写了一个,总结如下。

二、界面效果图

在这里插入图片描述
这个是一个csv文件,准备第一列是中文按键说明,第二列是英文,第三列是日文(还没有翻译);第四列是默认按键名称,第五列是默认按键ascII码(如果用户选了恢复默认设置,就会用到);第六列是用户自己设置的按键名称,第七列是用户自己设置的按键ascII码;第八列保留,暂未使用。

在这里插入图片描述
这个是首页,如果选到了这个按钮,就是按键设置页面。

在这里插入图片描述

这个是按键设置页面,实现了按 上下/用户设置的上下移动光标,按回车/ 开始键确定,然后按另一个键进行按键修改。
(图中灰色块就是光标,还没有找图片;最后3行按钮进行了特殊处理)

三、主要部分代码

unity代码比较多,总结下主要用到的3个类。

1.FileManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using UnityEngine.SceneManagement;
using System.Text;

public class FileManager : MonoSingleTon<FileManager>
{

    private bool isInit =false;

    public TextAsset keyConfigCsv;

    //012列是语言,3是默认名称,4是默认ascii,56是用户设置的
    //这个要装第二行的数据
    /*
        w
        s
        a
        d
        j
        k
        l
        i
        q
        e
        u
        o
        c
        v
        j
        k
     */

    //语言,这个不能修改,只能读取
    public static string[] LanguageWasd = new string[16];



    private static string[] LanguageWasd0 = new string[16];
    private static string[] LanguageWasd1 = new string[16];
    private static string[] LanguageWasd2 = new string[16];


    //默认的key的str与int,这个不能修改,只能读取
    public static int[] DefaultWasdNumKeys = new int[16];
    public static string[] DefaultWasdNumKeysStr = new string[16];

    //实际用的key的str与int
    public static int[] WasdNumKeys = new int[16];
    public static string[] WasdNumKeysStr = new string[16];

    //临时用的key的str与int,因为可能只修改不保存,所以用
    public static int[] WasdNumKeysTemp = new int[16];
    public static string[] WasdNumKeysStrTemp = new string[16];

    // Start is called before the first frame update


    // 这个方法会在脚本被启用时注册监听事件
    void OnEnable()
    {
        
        //Debug.Log("进入这个场景playerData");
        //Debug.Log("执行完毕这个场景playerData");

    }

    private void Awake()
    {
        if (!isInit)
        {
            InitKeyConfig();
        }
    }


    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {

    }



    private bool InitKeyConfig()
    {
        string path = GetKeyConfigPath();

        Debug.Log(path);

        if (File.Exists(path))
        {
            //用用户的初始化
            LoadKeyConfig(File.ReadAllText(path).Split("\n"));

            Debug.Log("使用用户的初始化");

            isInit = true;
            return false;

        }
        else
        {
            string text = keyConfigCsv.text;
            //把默认的文件写进本地
            File.WriteAllText(path, text, Encoding.UTF8);
            //用默认的初始化
            LoadKeyConfig(text.Split("\n"));
            //string[] dataRow = text.Split("\n");
            //File.WriteAllLines(path, dataRow);

            Debug.Log("使用默认的初始化");

            isInit = true;
            return true;
        }


    }




    //读取到临时变量里
    public void LoadKeyConfig(string[] dataRow)
    {

        int language = LanguageManager.GetLanguage();

        //File.WriteAllLines(path, dataRow);
        for(int i = 0; i < dataRow.Length; i++)
        {
            string[] dataCell = dataRow[i].Split(",");
            if (i >= 16)
            {
                break;
            }
            else
            {
                LanguageWasd[i] = dataCell[language];

                LanguageWasd0[i] = dataCell[0];
                LanguageWasd1[i] = dataCell[1];
                LanguageWasd2[i] = dataCell[2];

                DefaultWasdNumKeysStr[i] = dataCell[3];

                DefaultWasdNumKeys[i] = int.Parse(dataCell[4]);

                WasdNumKeysStr[i] = dataCell[5];
                WasdNumKeysStrTemp[i] = dataCell[5];

                WasdNumKeys[i] = int.Parse(dataCell[6]);
                WasdNumKeysTemp[i] = int.Parse(dataCell[6]);

            }
        }
    }




    private static void SaveKeyConfig(string[] dataRow)
    {

        //Application.dataPath
        //这个打包后就不能用了,可能没权限
        string path = GetKeyConfigPath();

        if (File.Exists(path))
        {
            //删了重新创建
            File.Delete(path);
            File.CreateText(path).Close();

        }
        else
        {
            File.CreateText(path).Close();
        }

        //List<string> datas2 = new List<string>();
        //datas.Add("coins,1");


        Debug.Log("写入数据");
        File.WriteAllLines(path, dataRow, Encoding.UTF8);
        Debug.Log("写入数据完毕");

    }




    public void ResetKeyConfig()
    {
        string text = keyConfigCsv.text;
        //用默认的初始化
        LoadKeyConfig(text.Split("\n"));
        //并且保存
        SaveKeyConfig(text.Split("\n"));


        //SceneManager.LoadScene(0);

    }

    public static string GetKeyConfigPath()
    {
        return Application.persistentDataPath + "/KeyConfig.csv";
    }

    public static void SaveKeyConfig()
    {
        string[] newData = new string[16];

        //保存文件
        for(int i=0; i<16; i++)
        {
            newData[i] = LanguageWasd0[i] + "," + LanguageWasd1[i] + "," + LanguageWasd2[i] + ","
                + DefaultWasdNumKeysStr[i] + "," + DefaultWasdNumKeys[i] + "," + WasdNumKeysStr[i] + "," + WasdNumKeys[i] + "," + ";";
        }


        SaveKeyConfig(newData);

        Debug.Log("保存键盘信息完毕");


    }


}



这个类负责操作配置文件,要把内置的配置文件保存到本地配置文件中,以及读取配置文件。(要区分读取内置的还是本地的)

2.TitleScreen.cs

using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class TitleScreen : MonoBehaviour
{
    TextMeshProUGUI tmpTitleText;

    bool calledNextScene;

    bool inputDetected = false;

    bool isTitle = true;

    bool isKeySetting = false;

    bool isLanguageSetting = false;

    int alphaKeyPressText = 255;

    bool alphaKeyPressTextShow = true;

    public AudioClip keyPressClip;

    public AudioClip keyChangeClip;

    [SerializeField] Image[] buttons = new Image[5];
    [SerializeField] Text[] buttonsText = new Text[5];
    private int buttonsChoosedNum = 0;


    private enum TitleScreenStates { WaitForInput, NextScene };
    TitleScreenStates titleScreenState = TitleScreenStates.WaitForInput;

#if UNITY_STANDALONE
    string insertKeyPressText = "PRESS ANY KEY";
#endif

#if UNITY_ANDROID || UNITY_IOS
    string insertKeyPressText = "TAP TO START";
#endif

    string titleText =
@"<size=18><color=#ffffff{0:X2}>{1}</color></size>";

    private void Awake()
    {
        //tmpTitleText = GameObject.Find("TitleText").GetComponent<TextMeshProUGUI>();


    }
    // Start is called before the first frame update
    void Start()
    {
        //tmpTitleText.alignment = TextAlignmentOptions.Center;
        //tmpTitleText.alignment = TextAlignmentOptions.Midline;
        //tmpTitleText.fontStyle = FontStyles.UpperCase;

        titleScreenState = TitleScreenStates.WaitForInput;

        if (isHaveSavePoint())
        {
            initButton(1);
        }
        else {
            initButton(0);
        }

    }

    // Update is called once per frame
    void Update()
    {
        switch(titleScreenState)
        {
            case TitleScreenStates.WaitForInput:
                //buttonsText[buttonsChoosedNum].text = String.Format(titleText, alphaKeyPressText, buttonsText[buttonsChoosedNum].text);
                buttonsText[buttonsChoosedNum].enabled = alphaKeyPressTextShow;
                if (!inputDetected)
                {
                    ChooseButton();
                    SelectButton();
                }
                break;
            case TitleScreenStates.NextScene:
                if (!calledNextScene)
                {
                    GameManager.Instance.StartNextScene();
                    calledNextScene = true;
                }
                break;
        }
    }

    private IEnumerator FlashTitleText() { 
        for(int i = 0; i < 5; i++)
        {
            alphaKeyPressTextShow = true;
            alphaKeyPressText = 0;
            yield return new WaitForSeconds(0.1f);

            alphaKeyPressTextShow = false;
            alphaKeyPressText = 255;
            yield return new WaitForSeconds(0.1f);
        }

        alphaKeyPressTextShow = true;
        alphaKeyPressText = 0;
        yield return new WaitForSeconds(0.1f);
        titleScreenState = TitleScreenStates.NextScene;
    }

    private IEnumerator FlashTitleTextAndOpenKeyConfig()
    {
        for (int i = 0; i < 5; i++)
        {
            alphaKeyPressTextShow = true;
            alphaKeyPressText = 0;
            yield return new WaitForSeconds(0.1f);

            alphaKeyPressTextShow = false;
            alphaKeyPressText = 255;
            yield return new WaitForSeconds(0.1f);
        }

        alphaKeyPressTextShow = true;
        alphaKeyPressText = 0;
        yield return new WaitForSeconds(0.1f);
        //退出的时候,需要改为接受输入
        KeyConfigScript.Instance.Show(() => { inputDetected = false;Debug.Log("回调方法"); });

    }

    private bool isHaveSavePoint() {

        return false;
    
    }

    private void ChooseButton() {

        if (  Input.GetKeyDown(KeyCode.UpArrow) || Input.GetKeyDown((KeyCode)FileManager.WasdNumKeys[0])  )
        {
            if (buttonsChoosedNum > 0)
            {
                buttons[buttonsChoosedNum].enabled = false;
                buttonsChoosedNum--;
                buttons[buttonsChoosedNum].enabled = true;
                PlayChangeButton();
            }
            else
            {
                PlayChangeButton();
            }
        }

        if (Input.GetKeyDown(KeyCode.DownArrow) || Input.GetKeyDown((KeyCode)FileManager.WasdNumKeys[1]) )
        {
            if (buttonsChoosedNum < buttons.Length - 1)
            {
                buttons[buttonsChoosedNum].enabled = false;
                buttonsChoosedNum++;
                buttons[buttonsChoosedNum].enabled = true;
                PlayChangeButton();
            }
            else
            {
                PlayChangeButton();
            }
        }
        
    }

    private void SelectButton() { 
        if( Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown((KeyCode)FileManager.WasdNumKeys[13])  )
        {
            if (buttonsChoosedNum == 0)
            {
                inputDetected = true;
                StartCoroutine(FlashTitleText());
                SoundManager.Instance.Play(keyPressClip);

            } else if (buttonsChoosedNum == 1) {
                inputDetected = true;
                SoundManager.Instance.Play(keyPressClip);
            }
            else if (buttonsChoosedNum == 2)
            {
                inputDetected = true;
                SoundManager.Instance.Play(keyPressClip);
            }
            else if (buttonsChoosedNum == 3)
            {
                inputDetected = true;
                SoundManager.Instance.Play(keyPressClip);
                StartCoroutine(FlashTitleTextAndOpenKeyConfig());

            }
            else if (buttonsChoosedNum == 4)
            {
                Application.Quit();
            }
        }
    
    }

    private void PlayChangeButton() { 
        if(keyChangeClip != null)
        {
            SoundManager.Instance.Play(keyChangeClip);
        }
    
    }

    private void initButton(int n) {
        buttonsChoosedNum = n;
        for(int i = 0; i < buttons.Length; i++)
        {
            if (i == n)
            {
                buttons[i].enabled = true;
            }
            else {
                buttons[i].enabled = false;
            }
        }
    
    }

}

这个是游戏首页用的类,主要管是否显示按键设置页面;
按键设置页面也在首页,是个Canvas对象,刚开始会隐藏,选择按键设置按钮并确定后,才会展示。

3.KeyConfigScript.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.UIElements;

public class KeyConfigScript : MonoBehaviour
{

    bool flashText = false;

    public static KeyConfigScript Instance = null;

    [SerializeField] Canvas canvas;

    public AudioClip keyChangeClip;
    public AudioClip keySelectClip;

    /*
        w
        s
        a
        d
        j
        k
        l
        i
        q
        e
        u
        o
        c
        v
        j
        k
     */
    public UnityEngine.UI.Image[] buttons = new UnityEngine.UI.Image[19];
    [SerializeField] Text[] buttonsText = new Text[19];

    [SerializeField] ScrollRect scrollRect;

    private Action nowScreenAction;


    private int buttonsChoosedNum = 0;

    bool alphaKeyPressTextShow = true;

    //检测按键
    bool canInput = false;

    bool canMove = true;

    private void Awake()
    {
        // If there is not already an instance of SoundManager, set it to this.
        if (Instance == null)
        {
            Instance = this;
        }
        //If an instance already exists, destroy whatever this object is to enforce the singleton.
        else if (Instance != this)
        {
            Destroy(gameObject);
        }

        //Set SoundManager to DontDestroyOnLoad so that it won't be destroyed when reloading our scene.
        DontDestroyOnLoad(gameObject);
        //默认隐藏
        Close();
    }


    private void OnEnable()
    {

    }
    

    private void initKeysObjects() {

        //读取下当前配置文件,获得每个按键配置的名称和值



        for (int i = 0; i < buttons.Length; i++)
        {
            if (i >= 16)
            {
                buttons[i].enabled = false;
            }
            else
            {
                buttonsText[i].text = FileManager.LanguageWasd[i] + ":" + FileManager.WasdNumKeysStr[i];

                //初始化方法,只有0才是
                if (i == 0)
                {
                    buttons[i].enabled = true;
                }
                else
                {
                    buttons[i].enabled = false;
                }
            }
        }
    
    }

    private void ChooseButton()
    {

        if ( Input.GetKeyDown(KeyCode.UpArrow) || Input.GetKeyDown((KeyCode)FileManager.WasdNumKeys[0]) )
        {
            if (buttonsChoosedNum > 0)
            {
                buttons[buttonsChoosedNum].enabled = false;
                buttonsChoosedNum--;
                buttons[buttonsChoosedNum].enabled = true;
                PlayChangeButton();
                ScrollUpOrDown(buttonsChoosedNum, false);
            }
            else
            {
                //移动到最后一个
                buttons[buttonsChoosedNum].enabled = false;
                buttonsChoosedNum = buttons.Length - 1;
                buttons[buttonsChoosedNum].enabled = true;
                PlayChangeButton();
            }
        }

        if (Input.GetKeyDown(KeyCode.DownArrow) || Input.GetKeyDown((KeyCode)FileManager.WasdNumKeys[1]) )
        {
            if (buttonsChoosedNum < buttons.Length - 1)
            {
                buttons[buttonsChoosedNum].enabled = false;
                buttonsChoosedNum++;
                buttons[buttonsChoosedNum].enabled = true;
                PlayChangeButton();
                ScrollUpOrDown(buttonsChoosedNum, true);
            }
            else
            {
                //移动到第一个
                buttons[buttonsChoosedNum].enabled = false;
                buttonsChoosedNum = 0;
                buttons[buttonsChoosedNum].enabled = true;
                PlayChangeButton();
            }
        }

    }

    private void SelectButton()
    {
        //如果是可以移动状态
        if (canMove)
        {
            //如果按下选择键
            if (  Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown((KeyCode)FileManager.WasdNumKeys[13])  ) 
            {
                //播放声音
                PlaySelectButton();

                //关闭移动
                canMove = false;

                //如果是这些键,特殊处理然后返回
                switch (buttonsChoosedNum)
                {
                    case 16:
                        StartCoroutine(FlashTitleTextSecond(Reset));
                        return;
                    case 17:
                        StartCoroutine(FlashTitleTextSecond(SaveAndClose));
                        return;
                    case 18:
                        StartCoroutine(FlashTitleTextSecond(Close));
                        return;
                }

                //如果是普通键

                //闪烁标志位打开
                flashText = true;
                //字幕闪烁
                StartCoroutine(FlashTitleText());

            }


        }
        else
        {
            BeforeSettingKey();
        }
       
    }

    private void BeforeSettingKey()
    {
        if (Input.anyKeyDown)
        {
            //需要看这个键能不能设置
            bool canSetting = false;

            foreach (KeyCode key in System.Enum.GetValues(typeof(KeyCode)))
            {
                if (Input.GetKeyDown(key))
                {
                    // 检查按键是否为可打印字符
                    if ((key >= KeyCode.A && key <= KeyCode.Z) || (key >= KeyCode.Alpha0 && key <= KeyCode.Alpha9))
                    {
                        // 将字符转换为 ASCII 码
                        string keyStr = key.ToString();
                        //char keyChar = keyStr[0];
                        int asciiValue = (int)key;

                        Debug.Log($"按下的键 {key} 对应的 ASCII 码值是:{asciiValue}");

                        SettingKey(keyStr, asciiValue);

                        canSetting = true;
                        break;
                    }
                    else
                    {


                        // 非字母数字键(你可以根据需求处理这些按键)
                        Debug.Log($"按下了非字符键:{key}");

                        if (key == KeyCode.Space)
                        {
                            SettingKey("Space", 32);

                            canSetting = true;
                            break;
                        }
                        else if (key == KeyCode.Return)
                        {
                            SettingKey("Enter", 13);

                            canSetting = true;
                            break;
                        }
                        else if (key == KeyCode.UpArrow)
                        {
                            SettingKey("Up", 273);

                            canSetting = true;
                            break;
                        }
                        else if (key == KeyCode.DownArrow)
                        {
                            SettingKey("Down", 274);

                            canSetting = true;
                            break;
                        }
                        else if (key == KeyCode.LeftArrow)
                        {
                            SettingKey("Left", 276);

                            canSetting = true;
                            break;
                        }
                        else if (key == KeyCode.RightArrow)
                        {
                            SettingKey("Right", 275);

                            canSetting = true;
                            break;
                        }
                    }
                }
            }


            //如果可以设置,再进行后续操作
            if (canSetting)
            {
                //停止闪烁
                StartCoroutine(ResetTitleText());
            }

        }
    }

    private void SettingKey(string str, int ascII)
    {

        //更新设置的信息
        FileManager.WasdNumKeysStrTemp[buttonsChoosedNum] = str;
        FileManager.WasdNumKeysTemp[buttonsChoosedNum] = ascII;

        //更新按键文本信息
        buttonsText[buttonsChoosedNum].text = FileManager.LanguageWasd[buttonsChoosedNum] + ":"+ str;

    }

    private IEnumerator FlashTitleText()
    {
        while (flashText) { 
            alphaKeyPressTextShow = true;
            yield return new WaitForSeconds(0.1f);

            alphaKeyPressTextShow = false;
            yield return new WaitForSeconds(0.1f);
        }
    }

    private IEnumerator FlashTitleTextSecond(Func<bool> func)
    {
        for (int i=0; i<5; i++)
        {
            alphaKeyPressTextShow = true;
            yield return new WaitForSeconds(0.1f);

            alphaKeyPressTextShow = false;
            yield return new WaitForSeconds(0.1f);
        }

        alphaKeyPressTextShow = true;
        yield return new WaitForSeconds(0.1f);

        //闪烁完毕后才能移动
        canMove = true;

        //闪烁完毕后执行
        func();

    }

    private IEnumerator ResetTitleText()
    {
        flashText = false;
        yield return new WaitForSeconds(0.2f);
        alphaKeyPressTextShow = true;
        buttonsText[buttonsChoosedNum].enabled = true;
        canMove = true;
    }



    private void PlayChangeButton()
    {
        if (keyChangeClip != null)
        {
            SoundManager.Instance.Play(keyChangeClip);
        }

    }

    private void PlaySelectButton()
    {
        if (keySelectClip != null)
        {
            SoundManager.Instance.Play(keySelectClip);
        }

    }

    //先从配置文件读取配置信息
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        if (canInput)
        {
            buttonsText[buttonsChoosedNum].enabled = alphaKeyPressTextShow;
            if (canMove)
            {
                ChooseButton();
            }
            SelectButton();
        }
    }


    //翻页,现在不用
    private void ScrollUpOrDown(int count, bool isDown)
    {
        /*
        if(count == 8 && isDown)
        {
            scrollRect.normalizedPosition = new Vector2(0, 0);
        }
        else if (count == 7 && !isDown) 
        {
            scrollRect.normalizedPosition = new Vector2(0, 1);
        }
        */

    }

    public void Show(Action action) {

        //翻页,现在不用
        //scrollRect.normalizedPosition = new Vector2(0, 0);
        alphaKeyPressTextShow = true;
        buttonsChoosedNum = 0;
        initKeysObjects();

        this.nowScreenAction = action;

        Debug.Log("show Key Config");
        canvas.enabled = true;
        canInput = true;


    }

    private bool Reset()
    {
        for (int i = 0; i < 16; i++)
        {
            //先把text内容改了
            buttonsText[i].text = FileManager.LanguageWasd[i] + ":" + FileManager.DefaultWasdNumKeysStr[i];
            //然后把临时数组改了
            FileManager.WasdNumKeysStrTemp[i] = FileManager.DefaultWasdNumKeysStr[i];
            FileManager.WasdNumKeysTemp[i] = FileManager.DefaultWasdNumKeys[i];
        }

        return true;
    }

    private bool Save()
    {
        for (int i = 0; i < 16; i++)
        {
            //给实际用的赋值
            FileManager.WasdNumKeysStr[i] = FileManager.WasdNumKeysStrTemp[i];
            FileManager.WasdNumKeys[i] = FileManager.WasdNumKeysTemp[i];
        }

        //写入文件
        FileManager.SaveKeyConfig();

        return true;
    }

    private bool SaveAndClose()
    {
        Save();
        Close();
        return true;
    }

    public bool Close() {

        canvas.enabled = false;
        canInput = false;
        //上一个屏幕,改为接受输入
        if(nowScreenAction != null)
        {
            nowScreenAction();
        }
        return true;
        //nowScreenManager.GetComponent<TitleScreen>().inputDetected = false;
    }


}

这个类就是按键设置页面用的类,有光标上下移动功能、按钮选择后让字幕闪烁的功能、再次按键后设置新按键功能。

四、备注

unity代码比较复杂,直接复制粘贴是无法使用的,每人的场景元素也不一样,很多变量会不存在,仅供参考。

以上就是个人编写的设置按键的代码,大致逻辑如下:

1.游戏启动后,先判断有没有本地配置文件,如果有、就读取本地配置文件并初始化;如果没有,就用内置配置文件初始化,并把内置配置文件保存到本地。

2.进入按键选择页面时,上下方向键移动光标,按回车可以选择按键,此时按键闪烁,再按另一个键可以设置为新按键。

3.有恢复默认设置功能,有保存并退出功能,有直接退出功能;如果选保存并退出,才把设置的临时按键数组赋值到实际使用的按键数组,并保存到本地配置文件。


http://www.kler.cn/a/525628.html

相关文章:

  • fscan全家桶更新:fscan免杀版,可过360、火绒、微步云沙箱,其他的自行测试
  • 微服务入门(go)
  • 项目部署(springboot项目)
  • hive:数据导入,数据导出,加载数据到Hive,复制表结构
  • Node.js与MySQL模块结合:打造安全高效的用户信息管理系统
  • 新年祝词(原创)
  • 分享|通过Self-Instruct框架将语言模型与自生成指令对齐
  • 为大模型提供webui界面的利器:Open WebUI 完全本地离线部署deepseek r1
  • 【memgpt】letta 课程6:代理RAG和外部内存
  • 130周四复盘(162)研究神作
  • Qt u盘自动升级软件
  • 【愚公系列】《循序渐进Vue.js 3.x前端开发实践》036-案例:实现支持搜索和筛选的用户列表
  • 【某大厂一面】JDK1.8中对HashMap数据结构进行了哪些优化
  • 手撕Diffusion系列 - 第十一期 - lora微调 - 基于Stable Diffusion(代码)
  • Kafka常见问题之 org.apache.kafka.common.errors.RecordTooLargeException
  • 《DeepSeek 网页/API 性能异常(DeepSeek Web/API Degraded Performance):网络安全日志》
  • MIMIC IV数据库中mimiciv_hosp的transfers表的careunit分析
  • Java CAS操作
  • Windows平台最新视频号内容下载工具(MP4格式一键解析)
  • Vue.js 路由守卫:前置和后置守卫
  • 安卓(android)读取手机通讯录【Android移动开发基础案例教程(第2版)黑马程序员】
  • 一文大白话讲清楚webpack进阶——9——ModuleFederation实战
  • YOLO11/ultralytics:环境搭建
  • 菜鸟之路Day11-12一一集合进阶(四)
  • Effective Python:(10)
  • 电路研究9.2.5——合宙Air780EP中GPS 相关命令使用方法研究