【Unity学习心得】如何制作俯视角射击游戏
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、导入素材
- 二、制作流程
- 1.制作地图
- 2.实现人物动画和移动脚本
- 3.制作单例模式和对象池
- 4.制作手枪pistol和子弹bullet和子弹壳bulletShell
- 5.制作散弹枪shotgun
- 总结
前言
俯视角射击游戏类似元气骑士那种,懂的都懂好吧。
本节课我们将要实现:制作地图,实现人物动画和移动脚本,制作单例模式和对象池,制作手枪pistol和子弹bullet和子弹壳bulletShell,制作散弹枪shotgun。
一、导入素材
素材链接: 、
https://o-lobster.itch.io/simple-dungeon-crawler-16x16-pixel-pack https://humanisred.itch.io/weapons-and-bullets-pixel-art-asset
二、制作流程
1.制作地图
还是要用我们的老朋友Tilemap来做这种像素地图:
可以看到,我们创建了三个层级的Grid,记得在Sorting Layer分别给它们排好选择顺序,除了Ground的那一层以外其它记得要给 tilemap Collider2D和Composite Collider2D,Rigibody2D记得设置成静态,这些后面都要用到的。
2.实现人物动画和移动脚本
绘制完简单地图后,我们就要开始导入人物素材了。公式化三件套:Sprite Renderer,Rb2D设置为Kinematic被动检测碰撞,别忘了锁Z轴旋转,Animator自己根据素材创建Idle,Walk
接下来创建PlayerController.cs给Player对象
代码如下:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class PlayerController : SingletonMono<PlayerController>
{public float speed = 3f;public bool enabledInput = true;private Rigidbody2D rb2d;private Animator anim;private Camera mainCamera;private Vector2 input;private Vector2 mousePosition;private new void Awake(){rb2d = GetComponent<Rigidbody2D>();anim = GetComponent<Animator>();mainCamera = Camera.main;}void Update(){if (enabledInput){mousePosition = mainCamera.ScreenToWorldPoint(Input.mousePosition);input.x = Input.GetAxisRaw("Horizontal");input.y = Input.GetAxisRaw("Vertical");rb2d.velocity = input.normalized * speed;if(input != Vector2.zero){anim.SetBool("isMoving", true);}else{anim.SetBool("isMoving", false);}if(mousePosition.x >= transform.position.x){transform.rotation = Quaternion.Euler(new Vector3(0f, 0f, 0f));}else{transform.rotation = Quaternion.Euler(new Vector3(0f, 180f, 0f));}}}}
这些大伙都应该懂了就获取X和Y上的Input,方向化后经典给rb2d设置velocity,然后根据鼠标位置判断玩家是否要沿Y轴翻转。
顺便给camera搞个cinemachine,让相机跟随玩家移动:
至此,我们实现了实现人物动画和移动脚本,接下来该开始制作单例模式和对象池模式了。
3.实现单例模式和对象池模式
单例模式大伙肯定也懂的,但这几天重温C#知识我突然就想用泛型<T>来做个泛型单例类,让所有MONOBehaviour的类都能继承:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SingletonMono<T> : MonoBehaviour where T : MonoBehaviour
{public static T _instance;public static T Instance{get{if(_instance == null){GameObject gameObject = new GameObject();_instance = gameObject.AddComponent<T>();DontDestroyOnLoad(gameObject);}return _instance;}}public void Awake(){if (_instance == null){_instance = (gameObject as T);DontDestroyOnLoad(gameObject);return;}if (this != _instance){DestroyImmediate(gameObject);return;}}
}
单例模式随便完成咯,接下来开始做对象池模式,很多人对对象池的编写还是比较陌生的,这里先写出主要思想:
在Unity中,实时创建(GameObject.Instantiate())和销毁游戏对象(GameObject.Destory ())会造成相当大的开销。
对于一些简单的,可以复用的物体,我们可以考虑用Enable/Disable来代替创建与销毁,这是因为Enable/Disable对性能的消耗搞更小。
我们可以采用对象池的思想实现这个功能。
所谓对象池,就是把所有相同的对象放在一个池子中,每当要使用到一个对象时,就从池子中找找看有没有之前创建过但现在空闲的物体可以直接拿来用,如果没有的话我们再创建物体并扔进池子里。
想要销毁一个物体,我们只需要标记其“空闲”即可,并不会直接销毁它。
理解过后我们就可以编写一个脚本ObjectPool.cs
using System.Collections.Generic;
using UnityEngine;public class ObjectPool : SingletonMono<ObjectPool>
{private Dictionary<string, Queue<GameObject>> objectPool = new Dictionary<string, Queue<GameObject>>();private GameObject pool;private new void Awake(){objectPool = new Dictionary<string, Queue<GameObject>>();}//从对象池中获取对象public GameObject GetObject(GameObject prefab){GameObject gameObject;
//先从name判断是否存在对应的键值或者队列的内容数量为0if (!objectPool.ContainsKey(prefab.name) || objectPool[prefab.name].Count == 0){
//如果没有就新建从Object Pool -> Child Pool -> PrefabgameObject = GameObject.Instantiate(prefab);PushObject(gameObject);if (pool == null)pool = new GameObject("ObjectPool");GameObject childPool = GameObject.Find(prefab.name + "Pool");if (!childPool){childPool = new GameObject(prefab.name + "Pool");childPool.transform.SetParent(pool.transform);}gameObject.transform.SetParent(childPool.transform);}gameObject = objectPool[prefab.name].Dequeue();gameObject.SetActive(true); //可视化return gameObject;}
//从对象池中取出对象public void PushObject(GameObject prefab){
//要保证名字和objectPool的名字相等,因此我们要用空的字符串取代Unity新建游戏对象会有个"(Clone)"string name = prefab.name.Replace("(Clone)", string.Empty);if (!objectPool.ContainsKey(name))objectPool.Add(name, new Queue<GameObject>());objectPool[name].Enqueue(prefab); //创建该prefab名字的队列并让prefab入栈prefab.SetActive(false);//默认为不可见}
}
4.制作手枪pistol和子弹bullet和子弹壳bulletShell
终于来到了重点制作这些东西,当一些事物存在共性的时候我们会想使用抽象类来减少代码的耦合度,同样这些手枪火箭筒啥的都输入枪,我们可以创建一个Gun的Prefab,让这些枪都成为Gun的Varient。
其中muzzle是子弹发射位置,bulletshell是生成弹壳的位置。
我们Varient第一个目标是pistol手枪,如图我们给它sprite和animator
制作两个动画并连接即可
别忘了动态调整muzzle和bulletshell的位置
回到脚本当中,新建一个基类脚本Gun.cs,我们打算让所有的枪械类都继承这个脚本:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Gun : MonoBehaviour
{public GameObject bulletPrefab;public GameObject bulletShellPrefab;public float interval = 0.5f;protected Animator animator;protected Camera mainCamera;protected Transform muzzleTrans;protected Transform bulletShellTrans;protected float flipY;protected Vector2 mousePos;protected Vector2 direction;private float timer;protected virtual void Start(){animator = GetComponent<Animator>();mainCamera = Camera.main;muzzleTrans = transform.Find("Muzzle"); ;bulletShellTrans = transform.Find("BulletShell");flipY = transform.localScale.y;}protected virtual void Update(){mousePos = mainCamera.ScreenToWorldPoint(Input.mousePosition);if(mousePos.x >= transform.position.x){transform.localScale = new Vector3(transform.localScale.x, flipY, 1);}else{transform.localScale = new Vector3(transform.localScale.x, -flipY, 1);}Shoot();}//控制枪械发射间隔protected virtual void Shoot(){direction = (mousePos - new Vector2(transform.position.x, transform.position.y)).normalized;transform.right = direction;if (timer != 0){timer -= Time.deltaTime;if (timer < 0)timer = 0;}if (Input.GetKeyDown(KeyCode.Mouse0)) //按下鼠标左键{if(timer == 0){timer = interval;Fire();}}}
//控制开火protected virtual void Fire(){animator.SetTrigger("Shoot");
//生成子弹预制体GameObject bullet = ObjectPool.Instance.GetObject(bulletPrefab);bullet.transform.position = muzzleTrans.position;
//发射子弹角度偏差float angle = UnityEngine.Random.Range(-5f, 5f);bullet.GetComponent<Bullet>().SetSpeed(Quaternion.AngleAxis(angle , Vector3.forward) * direction);
//生成子弹壳预制体GameObject bulletShell = ObjectPool.Instance.GetObject(bulletShellPrefab);bulletShell.transform.position = bulletShellTrans.position;bulletShell.transform.rotation = bulletShellTrans.rotation;}
}
然后我们再给pistol添加同名脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Pistol : Gun
{}
写到这里我们注意到还要接着制作子弹Bullet和子弹壳BulletShell的预制体:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Bullet : MonoBehaviour
{public float bulletSpeed = 15f;public GameObject explosionPrefab;private Rigidbody2D rb2d;private void Awake(){rb2d = GetComponent<Rigidbody2D>();}
//在这里,我们用通过给子弹的rb2d设置速度velocity控制其移动速度和方向public void SetSpeed(Vector2 direction){rb2d.velocity = bulletSpeed * direction;}
//最后当子弹碰到墙壁时对象池回收该对象并生成爆炸对象private void OnTriggerEnter2D(Collider2D other){if (other.gameObject.layer == LayerMask.NameToLayer("Wall")){GameObject exp = ObjectPool.Instance.GetObject(explosionPrefab);exp.transform.position = transform.position;ObjectPool.Instance.PushObject(gameObject);}}}
新建一个bullet的prefab
新建一个Explosion的prefab
给它制作一个爆炸的动画
在它的同名脚本中回收爆炸游戏对象:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Explosion : MonoBehaviour
{private Animator anim;private AnimatorStateInfo info;private void OnEnable(){anim = GetComponent<Animator>();}private void Update(){info = anim.GetCurrentAnimatorStateInfo(0);if(info.normalizedTime >= 1){ObjectPool.Instance.PushObject(gameObject);}}}
同样的操作也给bulletshell
同名脚本中:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class BulletShell : MonoBehaviour
{public float speed;public float stopTime = 0.5f;public float fadeSpeed = 0.01f;private Rigidbody2D rb2d;private SpriteRenderer sprite;void Awake(){rb2d = GetComponent<Rigidbody2D>();sprite = GetComponent<SpriteRenderer>();}private void OnEnable(){float angel = Random.Range(-30f, 30f);rb2d.velocity = Quaternion.AngleAxis(angel, Vector3.forward) * Vector3.up * speed;sprite.color = new Color(sprite.color.r, sprite.color.g, sprite.color.b, 1);rb2d.gravityScale = 3;StartCoroutine(Stop());}private IEnumerator Stop(){yield return new WaitForSeconds(stopTime);rb2d.velocity = Vector2.zero;rb2d.gravityScale = 0;
//通过spriterenderer的透明度来渐变颜色淡出直到0while (sprite.color.a > 0){sprite.color = new Color(sprite.color.r, sprite.color.g, sprite.color.g, sprite.color.a - fadeSpeed);yield return new WaitForFixedUpdate();}
//然后回收该游戏对象ObjectPool.Instance.PushObject(gameObject);}
}
5.制作散弹枪shotgun
有了前车之鉴,我们就可以照着葫芦画瓢。还是老配方先生成varient:
别忘了调整muzzle和bulletshell的位置。
创建同名脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class ShotGun : Gun
{public int bulletNum = 3;public float bulletAngle = 15f;
//我们只需要重写fire脚本protected override void Fire(){animator.SetTrigger("Shoot");int med = bulletNum / 2;for (int i = 0; i < bulletNum; i++){GameObject bullet = ObjectPool.Instance.GetObject(bulletPrefab);bullet.transform.position = muzzleTrans.position;if (bulletNum % 2 == 1){
//这段代码的意思是如果有奇数个bulletnum那给这个bulletshell设置的角度应该是bulletAngle * (i - //med)bullet.GetComponent<Bullet>().SetSpeed(Quaternion.AngleAxis(bulletAngle * (i - med), Vector3.forward) * direction);}else if(bulletNum % 2 == 0){
//这段代码的意思是如果有偶数个bulletnum那给这个bulletshell设置的角度应该是bulletAngle * (i - //med) + bulletAngle / 2fbullet.GetComponent<Bullet>().SetSpeed(Quaternion.AngleAxis(bulletAngle * (i - med) + bulletAngle / 2f, Vector3.forward) * direction);}}GameObject shell = ObjectPool.Instance.GetObject(bulletShellPrefab);shell.transform.position = bulletShellTrans.transform.position;shell.transform.rotation = bulletShellTrans.transform.rotation;}
}
可以看到奇数个子弹:
偶数个子弹:你看是不是还要再加偏转角的一半即bulletAngle / 2
最后我们还想要根据键盘的Q和E键切换武器,回到playerController.cs当中:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class PlayerController : SingletonMono<PlayerController>
{public float speed = 3f;public bool enabledInput = true;public GameObject[] guns;private int currentGun;private Rigidbody2D rb2d;private Animator anim;private Camera mainCamera;private Vector2 input;private Vector2 mousePosition;private new void Awake(){rb2d = GetComponent<Rigidbody2D>();anim = GetComponent<Animator>();mainCamera = Camera.main;guns[0].SetActive(true);}void Update(){if (enabledInput){SwitchGun();mousePosition = mainCamera.ScreenToWorldPoint(Input.mousePosition);input.x = Input.GetAxisRaw("Horizontal");input.y = Input.GetAxisRaw("Vertical");rb2d.velocity = input.normalized * speed;if(input != Vector2.zero){anim.SetBool("isMoving", true);}else{anim.SetBool("isMoving", false);}if(mousePosition.x >= transform.position.x){transform.rotation = Quaternion.Euler(new Vector3(0f, 0f, 0f));}else{transform.rotation = Quaternion.Euler(new Vector3(0f, 180f, 0f));}}}
//切换武器private void SwitchGun(){if (Input.GetKeyDown(KeyCode.Q)){guns[currentGun].SetActive(false);if(--currentGun < 0){currentGun = guns.Length - 1;}guns[currentGun].SetActive(true);}else if (Input.GetKeyDown(KeyCode.E)){guns[currentGun].SetActive(false);if (++currentGun > guns.Length - 1){currentGun = 0;}guns[currentGun].SetActive(true);}}
}
给这两个武器添加上去(其它三个是下一期要讲的先别在意):
总结
最后的效果如图所示:
相关文章:

【Unity学习心得】如何制作俯视角射击游戏
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、导入素材二、制作流程 1.制作地图2.实现人物动画和移动脚本3.制作单例模式和对象池4.制作手枪pistol和子弹bullet和子弹壳bulletShell5.制作散弹枪shotgun总…...

【资料分析】常见的坑
in 比较或计数类问题 差别大的基期比较,可以直接用现期进行比较 注意单位可能不同! 注意顺序是从小到大还是从大到小 以及老问题,名字本身就叫XX增量,XX增加值,而非还要另外去算的东东 给出的图表可能是不完整的 2…...

GitLab权限及设置
之前很少关注这些,项目的权限,一般由专门的管理人员设置。 但自己创建的项目自己可以设置权限。下面是一些笔记。 GitLab中用户权限_gitlab 权限-CSDN博客 开发中遇到要将自己这块的代码上传到Git,由其他组的同事拉取后继续开发。上传代码后…...
算法练习题24——查找杨辉三角中的组合数
题目描述 杨辉三角中的每个元素是一个组合数。第 ( i ) 行的第 ( j ) 个元素表示组合数 ( C(i, j) ) ,即从 ( i ) 个元素中选 ( j ) 个元素的组合方式。已知一个正整数 ( N ),要求在杨辉三角中找到这个数,并输出它在杨辉三角中的具体位置。位…...

string类的模拟实现
实现string的模拟实现分为三个文件,分别为:string.h、sting.cpp、test.cpp string.h 其中包含一些短小常用的函数的实现,头文件,函数的声明 #include<iostream> #include<string> #include<assert.h>using n…...
如何训练机器学习力场
机器学习力场(MLFF)的训练主要依赖于通过量子力学计算生成的高质量训练数据集,并利用不同的机器学习算法来拟合分子系统中的势能面(Potential Energy Surface, PES)和原子间作用力。这种训练过程包括数据准备、特征提取…...
AI创作新手册:精通Prompt提示词的提问策略
文章目录 🍊AI创作核心:提示词 Prompt 的重要性1. 什么是提示词工程?1.1 提示词的作用原理1.2 提示词工程师的薪资与行业前景1.3 提示词工程的适用性 2. 提示词的编写技巧3. 常见的提示词框架3.1 CO-STAR 框架3.2 BORKE 框架 4. 提示词的实际…...

gingivitis
gingivitis 牙龈炎 1)这个是啥不知道 2)七叶莲片 3)甲硝唑芬布芬胶囊 4)盐酸左氧氟沙星胶囊 5)纳珍 开始学习记录医生开的药。日常备药记录一下。【不要乱吃药哈】...

开源 AI 智能名片小程序:开启内容营销新境界
摘要:本文深入探讨了在当今数字化时代,内容营销的重要性以及如何实现让用户主动找你的最佳效果。通过引入开源 AI 智能名片小程序这一创新工具,阐述了其在明确目标用户群体、迎合用户需求痛点和打造风格特色方面的独特优势,为企业…...

p12docker 进入容器的命令和拷贝的命令
进入当前正在运行的容器 第一种方式是执行docker exec -it 8d57ffda7a29 /bin/bash这个时候可以根据docker容器的id进入到指定id的容器当中***(这个是比较常用的)*** 老师的笔记 第二种方式是docker attach 8d57ffda7a29 这里还是直接引用老师的笔记吧 从容器内部拷贝文…...
代码随想录Day 45|leetcode题目:115.不同的子序列、583. 两个字符串的删除操作、72. 编辑距离
提示:DDU,供自己复习使用。欢迎大家前来讨论~ 文章目录 题目题目一: 115.不同的子序列解题思路:1. 确定dp数组(dp table)以及下标的含义2. 确定递推公式3. dp数组如何初始化4. 确定遍历顺序5. 举例推导dp数…...

浮点数在内存中的存储详解(超详细)
目录 1. 浮点数存储规则 2. IEEE754规定: 3. 关于M的说明: 4. 关于E的说明: 5. 关于S的说明: 6.浮点数从内存中取出(三种情况) 情况1:E不全为0或不全为1 情况2:E全为0 情况3&a…...

Maven下载安装
下载 下载地址:Maven – Download Apache Maven 选择合适的版本进行下载 windows&Linux安装 1, 解压apache-maven-3.6.1.rar即安装完成 2, 配置环境变量MAVEN_HOME为安装路径,并将MAVEN_HOME的bin目录配置到PATH下 3,…...

Qt:Q_GLOBAL_STATIC实现单例(附带单例使用和内存管理)
前言 本文主要写Q_GLOBAL_STATIC实现单例以及单例的释放,网上很多教程只有单例的创建,但是并没有告诉我们单例的内存管理,这就很头疼。 正文 使用 Qt 的 Q_GLOBAL_STATIC // Singleton.h #ifndef SINGLETON_H #define SINGLETON_H#includ…...
URL.createObjectURL 与 FileReader:Web 文件处理两大法宝的对比
URL.createObjectURL 与 FileReader:Web 文件处理两大法宝的对比 在Web开发中,处理用户上传的文件是一项常见且重要的任务。URL.createObjectURL和FileReader是两种常用于此目的的Web API,它们各有特点,适用于不同的场景。本文将…...

零基础考过软考信息系统项目管理师经验分享
选择适合的课程:如果你是零基础,建议找一些专门针对新手的课程,讲解通俗易懂。 刷题至关重要:软考的题库很庞大,多做题是必须的。 做好笔记和复习:上课时要做好笔记,课后及时复习,…...

机器学习课程学习周报十二
机器学习课程学习周报十二 文章目录 机器学习课程学习周报十二摘要Abstract一、机器学习部分1.1 fGAN: General Framework of GAN1.2 CycleGAN1.3 Auto-Encoder1.4 概率论复习(一) 总结 摘要 本周的学习内容涵盖了fGAN框架、CycleGAN、自编码器以及概率…...
python多线程程序设计 之二
python多线程程序设计 之二 线程同步机制lock对象acquirereleaselocked RLock对象条件变量条件变量应用实列实列代码 线程同步机制 lock对象 原语锁是一种同步原语,锁定时不属于特定线程。在Python中,它是目前可用的最低级别的同步原语,由_…...
k8s用StatefulSet部署redis
redis-config.yaml (配置文件) apiVersion: v1 kind: ConfigMap metadata:name: redis-config data:redis.conf: |# Redis general configuration bind 0.0.0.0 protected-mode no port 6379 dir /data appendonly yesse…...
flink on k8s
1.修改host文件 vi /etc/hosts 添加如下内容 这样搭集群的时候就不用记ip了 #::1 localhost localhost.localdomain localhost6 localhost6.localdomain6127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 165.154.221.97 tlb-001 k8s01 k8s-m…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...

微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...

select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...

【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅
目录 前言 操作系统与驱动程序 是什么,为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中,我们在使用电子设备时,我们所输入执行的每一条指令最终大多都会作用到硬件上,比如下载一款软件最终会下载到硬盘上&am…...
离线语音识别方案分析
随着人工智能技术的不断发展,语音识别技术也得到了广泛的应用,从智能家居到车载系统,语音识别正在改变我们与设备的交互方式。尤其是离线语音识别,由于其在没有网络连接的情况下仍然能提供稳定、准确的语音处理能力,广…...