状态机的深入理解

article/2025/9/29 17:36:41

目录

1.什么是状态机

2. 生活中的状态机

3. 工程中的应用

4. 发散思考


1.什么是状态机

■1.1 有向图

  • 有向图是指由定点和边构成的集合,我们常用G(E, V)来表示一个有向图,其中定点之间由有向边进行连接就构成了有向图。有向图在实际生活中有很多实际的意义,比如拓扑排序(简单说一下就是不断的去掉没有入度的节点)以及在网络寻址上迪杰斯特拉算法以及图数据库都是有向图的应用。换一个角度来看这个问题,有向图仿佛就是对一种实际场景的抽象。

■1.2 状态机与有向图

  • 状态机之间的转换其实和有向图有着紧密的关系,打一个比方来理解状态机,一个人在一个地图上行走,地图上有若干个城市,他从一个起点出发根据自己的喜好选择不同的路,从而走到不同的城市。这个例子中包含了两个重要的信息一个是城市一个是路,那么分别对应的是有向图中的定点和边,那么在状态机中的就是状态和转变。

  • 所以这么来看,要想在实际开发中利用好状态机模式,第一步其实就是设计好自己的状态机转化图,只有合理的设计了状态转化的逻辑才可以让代码的逻辑更贱简单清晰。换句话说,当你在一个简单的地图上找到一个目的地肯定是要比在一个复杂的地图上容易的多。

■1.3 Google中的Android是怎么做的?(mediaplayer)
在这里插入图片描述
■1.4 状态机的几个重要概念

  • State 状态:
    一个状态机至少要包含两个状态。例如上面自动门的例子,有 open 和 closed 两个状态。

  • Event 事件:
    事件就是执行某个操作的触发条件或者口令。对于自动门,“按下开门按钮”就是一个事件。

  • Action 动作:
    事件发生以后要执行动作。例如事件是“按开门按钮”,动作是“开门”。编程的时候,一个 Action 一般就对应一个函数。

  • Transition 变换:
    也就是从一个状态变化为另一个状态。例如“开门过程”就是一个变换

    当然了概念总是规定的,在实际的工程中我们可能需要根据实际的需要来调整我们具体的实现。

在这里插入图片描述
状态,程序在运行时,仅仅允许存在一种状态,这个例子中就是Idle
在这里插入图片描述

状态的转变,事件的发生往往会触发状态的变化,对于这个例子,
setDataSourece就是一个事件,在这个事件下Mediaplayer
进行了状态的切换,Idle -> Initialized
在这里插入图片描述
往往一个状态机的设置都会包含一个起始态和终态

■1.5 场景与好处

  最常见的应用场景就是游戏中人物角色的控制,比如控制一个士兵(射击,跑动和跳跃等),当人数多的时候

还可以采用分层状态机来进行控制。

 那么使用状态机有哪些好处呢?
1. 当一个程序有多个状态时,规范了状态机的状态转换,避免了一些引入一些复杂的判断逻辑。
2. 规范了程序在不同状态下所能提供的能力。
3. 在能力上可以进行横向扩展,提供新的状态来完善现有逻辑

■1.6 总结与回顾

  • 把状态机模型引入到实际工程中时候,最重要的就是确定好整个状态机的有向图,确定好有哪些状态,事件以及动作。

2. 生活中的状态机

■2.1 本节引言

  • 其实在生活中有很多状态机的例子,我想最简单可能就是灯泡了吧,设想一下一个简单的电灯泡,他可能存在几个状态:OFF -> LOW -> MEDIUM -> HIGH -> OFF。本节的内容就是通过一个例子把状态机的编程思想介绍给大家。
    下面将会一步一步的对灯泡的例子进行优化,来把它整理成状态机设计模式的代码。

■2.2 第一次编码
在这里插入图片描述
在这里插入图片描述

  • 在这一次编码的时候我们定义了一个枚举类型包含了所有的灯泡状态同时也定义了一个状态转换表。状态转换表具体的定义了每一状态可以转变的下一个状态,避免了错误的状态的转换。这可能也是最简单的状态机了,在类中定义一个状态,根据不同的事件进行切换。

  • 但是在如果我们把状态从枚举变量里面抽出来呢?这是我们要做的第一步,将每一个状态写成一个独立的类。但是同时我们要注意一个问题,状态类必须是单例的,因为每一次切换的时候我们可能还要切换回上一次的相同的状态,我们没有必要再重新去申请释放内存。

■2.3 第二次编码
在这里插入图片描述
在这里插入图片描述

Light
在这里插入图片描述
■2.4 总结与回顾

  • 状态机不外乎就是状态的定义和切换,对于一些简单的例子可能使用switch就可以完成相关编码。对于一些复杂的例子我们可能需要抽象出具体状态和业务实体,以便于业务的扩展,但这并一定是绝对的。
  • 看了这个例子,我想大家应该对状态机有了初步的认识,至少不会纠结在第一节中那些无聊的概念上了。 那么在实际工程上我们应该怎么样才能结合状态机的设计模型呢?这个问题我花了不少时间去思考,查阅了相关的代码,也去翻阅了Android中mediaplayer的实现,令我惊讶的是安卓的源码并没有将状态机封装的那么抽象,并没有将每一个状态抽象成一个一个单独的类,这种做法最大的一个好处就是避免了大量状态类的定义,但是不代表不可以这样去做,下一节我将尝试用一个通用的工具类来完成一个“mediaplayer”的开发,并且给大家演示一下实际效果,做进一步的讨论。

3. 工程中的应用

  • 第一节的时候我们提及了andriod的mediaplayer那么我们来用状态机模拟一个复杂的场景。先看
    demo演示
    在这里插入图片描述
  • 源码
  • 类图带补充
#include "tinyfsm.hpp"
#include <iostream>class Idle;
class End;
class Error;
class Initialized;
class Prepared;
class Started;
class Stoped;
class Completed;
class Paused;
class Preparing;#define EVENT_CODE_BASE     0
#define EVENT_SET_DATA_SOURCE       EVENT_CODE_BASE + 1
#define EVENT_SET_PREPARE_ASYNC     EVENT_CODE_BASE + 2
#define EVENT_SET_RESET             EVENT_CODE_BASE + 3#define EVENT_INVAILED_INPUT        EVENT_CODE_BASE - 1struct MedieEvent : tinyfsm::Event {int code;
};class MediaPlayer {
public:private:friend class Idle;friend class Idle;friend class End;friend class Error;friend class Initialized;friend class Prepared;friend class Started;friend class Stoped;friend class Completed;friend class Paused;void setDataSource(MedieEvent* event) {std::cout << "MediaPlayer setDataSource working" << std::endl;};void prepareAync(MedieEvent* event) {std::cout << "MediaPlayer prepareAync working" << std::endl;};void resetReasource(MedieEvent* event) {std::cout << "MediaPlayer resetReasource working" << std::endl;};int sourceCount;
};struct SetDataSource : MedieEvent
{SetDataSource(MediaPlayer* pMedia):m_pMediaPlayer(pMedia){code = EVENT_SET_DATA_SOURCE;}MediaPlayer* m_pMediaPlayer;
};struct PreparedAsync : MedieEvent {PreparedAsync(MediaPlayer* pMedia) :m_pMediaPlayer(pMedia) {code = EVENT_SET_PREPARE_ASYNC;}MediaPlayer* m_pMediaPlayer;
};struct InviledInput : MedieEvent
{InviledInput(MediaPlayer* pMedia) :m_pMediaPlayer(pMedia) {code = EVENT_INVAILED_INPUT;}MediaPlayer* m_pMediaPlayer;
};struct Reset :MedieEvent {Reset(MediaPlayer* pMedia) :m_pMediaPlayer(pMedia) {code = EVENT_SET_RESET;}MediaPlayer* m_pMediaPlayer;
};struct MediaState : tinyfsm::Fsm<MediaState> {virtual void react(MedieEvent* ) { };// alternative: enforce handling of Toggle in all states (pure virtual)//virtual void react(Toggle const &) = 0;virtual void entry(MediaPlayer* pM) { };  /* entry actions in some states */virtual void exit(MediaPlayer* pM) { };  /* no exit actions */
};//_state_instance 由于这个里面模板类和static 的原因,这些类都是单例的
class Error : public MediaState {
public:void react(MedieEvent* event) override{if (nullptr == event)return;switch (event->code){case EVENT_INVAILED_INPUT: {std::cout << "Error: your input wrong! what should I do !" << std::endl;break;}case EVENT_SET_RESET: {std::cout << "Error: EVENT_SET_RESET working" << std::endl;Reset* resetEvent = static_cast<Reset*>(event);if (nullptr == resetEvent)return;resetEvent->m_pMediaPlayer->resetReasource(resetEvent);transit<Idle>(resetEvent->m_pMediaPlayer);break;}default:std::cout << "Error: current status can't execute your command" << std::endl;break;}}void entry(MediaPlayer* pM) override {std::cout << "Error: entry" << std::endl;//do something about mepM->sourceCount = 0;}void exit(MediaPlayer* pM) override {std::cout << "Error: exit" << std::endl;}
};class Idle : public MediaState {
public:void react(MedieEvent* event) override{std::cout << "Idle: react" << std::endl;if (nullptr == event)return;if (event->code < 0) {// TODO (songxufei) : 定义error的base类InviledInput* invaledEvent = static_cast<InviledInput*>(event);//transmit to error statetransit<Error>(invaledEvent->m_pMediaPlayer);//do our repair workdispatch<MedieEvent>(event);return;}switch (event->code){case EVENT_SET_DATA_SOURCE: {std::cout << "Idle: setDataSource working" << std::endl;SetDataSource* eventSetSource = static_cast<SetDataSource*>(event);if (nullptr == eventSetSource)return;eventSetSource->m_pMediaPlayer->setDataSource(eventSetSource);transit<Initialized>(eventSetSource->m_pMediaPlayer);break;}default:std::cout << "Error: current status can't execute your command" << std::endl;break;}MediaState::react(event);}void entry(MediaPlayer* pM) override{std::cout << "Idle: entry" << std::endl;//do something about mepM->sourceCount = 0;}void exit(MediaPlayer* pM) override{ std::cout << "Idle: exit" << std::endl;}
};class Initialized : public MediaState {
public:void react(MedieEvent* event) override {std::cout << "Initialized: react" << std::endl;if (nullptr == event)return;if (event->code < 0) {// TODO (songxufei) : 定义error的base类InviledInput* invaledEvent = static_cast<InviledInput*>(event);//transmit to error statetransit<Error>(invaledEvent->m_pMediaPlayer);//do our repair workdispatch<MedieEvent>(event);return;}switch (event->code){case EVENT_SET_PREPARE_ASYNC: {std::cout << "Initialized: prepare async working" << std::endl;PreparedAsync* preEvent = static_cast<PreparedAsync*>(event);if (nullptr == preEvent ) {std::cout << "Initialized: prepare async error" << std::endl;return;}preEvent->m_pMediaPlayer->prepareAync(preEvent);//transmist 带入指针便于操作相应资源transit<Preparing>(preEvent->m_pMediaPlayer);break;}default:std::cout << "Initialized: current status can't execute your command" << std::endl;break;}}void entry(MediaPlayer* pM) override {std::cout << "Initialized: entry" << std::endl;}void exit(MediaPlayer* pM) override {std::cout << "Initialized: exit" << std::endl;}
};class Preparing : public MediaState {
public:void react(MedieEvent* event) override {std::cout << "Preparing: react" << std::endl;if (nullptr == event)return;if (event->code < 0) {// TODO (songxufei) : 定义error的base类InviledInput* invaledEvent = static_cast<InviledInput*>(event);//transmit to error statetransit<Error>(invaledEvent->m_pMediaPlayer);//do our repair workdispatch<MedieEvent>(event);return;}switch (event->code){default:std::cout << "Preparing: current status can't execute your command" << std::endl;break;}}void entry(MediaPlayer* pM) override {std::cout << "Preparing: entry" << std::endl;}void exit(MediaPlayer* pM) override {std::cout << "Preparing: exit" << std::endl;}
};//init state with idle
FSM_INITIAL_STATE(MediaState, Idle)using fsm_handle = MediaState;//只有使用模板类是不同的地址
template<typename X>
class C {
public:static int a;
};
template<typename X>
int C<X>::a = 0;class D : public C<D> {};int main()
{printf("##1 %ld  ", (long)&(C<int>::a));printf("##2 %ld  ", (long)&(D::a));MediaPlayer* player = new MediaPlayer();fsm_handle::start(player);std::cout << "EVENT_SET_DATA_SOURCE : 1 " << std::endl ;std::cout << "EVENT_SET_PREPARE_ASYNC : 2 " << std::endl;std::cout << "EVENT_SET_RESET : 3 " << std::endl;while (1){int n;std::cout << std::endl << "1 - 100=Event id, 0=Quit ? ";std::cin >> n;switch (n) {case EVENT_SET_DATA_SOURCE: {SetDataSource* event = new SetDataSource(player);fsm_handle::dispatch<MedieEvent>(event);std::cout << "deleteing event resource " << std::endl;delete event;break;}case EVENT_SET_PREPARE_ASYNC: {PreparedAsync* event = new PreparedAsync(player);fsm_handle::dispatch<MedieEvent>(event);std::cout << "deleteing event resource " << std::endl;delete event;break;}case EVENT_SET_RESET: {Reset* event = new Reset(player);fsm_handle::dispatch<MedieEvent>(event);std::cout << "deleteing event resource " << std::endl;delete event;break;}case 0:goto del;default: {std::cout << "> Invalid input" << std::endl;InviledInput* event = new InviledInput(player);fsm_handle::dispatch<MedieEvent>(event);delete event;break;}}}
del:delete player;return 0;
}
  • 再看封装之前应该先温习一些C++的知识
  1. 模板的嵌套从属类型
    用于声明模板类中的的类
    例如
    template
    void fun(const T& proto ,typename T::const_iterator it);

  2. std::is_same
    可判断两个类型是否一样,特别在模板里面,在不清楚模板的参数时,此功能可以对一些特定的参数类型进行特
    殊的处理。

  3. Constexpr 关键字
    告诉编译器进行编译时计算

  • 源码
namespace tinyfsm
{// --------------------------------------------------------------------------struct Event { };// --------------------------------------------------------------------------#ifdef TINYFSM_NOSTDLIB// remove dependency on standard library (silent fail!).// useful in conjunction with -nostdlib option, e.g. if your compiler// does not provide a standard library.// NOTE: this silently disables all static_assert() calls below!template<typename F, typename S>struct is_same_fsm { static constexpr bool value = true; };
#else//这里是一个继承// check if both fsm and state class share same fsmtypetemplate<typename F, typename S>struct is_same_fsm : std::is_same< typename F::fsmtype, typename S::fsmtype > { };#endiftemplate<typename S>struct _state_instance{using value_type = S;using type = _state_instance<S>;//这样做相当于每一个类都是一个单利类static S value;};//这里是定义了一下一个静态变量template<typename S>typename _state_instance<S>::value_type _state_instance<S>::value;// --------------------------------------------------------------------------template<typename F>class Fsm{public:using fsmtype = Fsm<F>;using state_ptr_t = F*;static state_ptr_t current_state_ptr;// public, leaving ability to access state instance (e.g. on reset)template<typename S>static constexpr S& state(void) {static_assert(is_same_fsm<F, S>::value, "accessing state of different state machine");return _state_instance<S>::value;}template<typename S>static constexpr bool is_in_state(void) {static_assert(is_same_fsm<F, S>::value, "accessing state of different state machine");return current_state_ptr == &_state_instance<S>::value;}/// state machine functionspublic:// explicitely specialized in FSM_INITIAL_STATE macrostatic void set_initial_state();static void reset() { };template<typename P>static void enter(P* p) {current_state_ptr->entry(p);}static void enter() {current_state_ptr->entry();}static void start() {set_initial_state();enter();}// This S is user base class pointtemplate<typename P>static void start(P* p) {set_initial_state();enter(p);}template<typename E>static void dispatch(E const& event) {current_state_ptr->react(event);}template<typename E>static void dispatch(E* const event) {current_state_ptr->react(event);}/// state transition functionsprotected:static std::mutex mutexTransmit;template<typename S>void transit(void) {std::unique_lock<std::mutex> lk(mutexTransmit);static_assert(is_same_fsm<F, S>::value, "transit to different state machine");current_state_ptr->exit();current_state_ptr = &_state_instance<S>::value;current_state_ptr->entry();}// add this for our stitucation, add by sxftemplate<typename S, typename M>void transit(M* pM) {std::unique_lock<std::mutex> lk(mutexTransmit);static_assert(is_same_fsm<F, S>::value, "transit to different state machine");current_state_ptr->exit(pM);current_state_ptr = &_state_instance<S>::value;current_state_ptr->entry(pM);}template<typename S, typename ActionFunction>void transit(ActionFunction action_function) {std::unique_lock<std::mutex> lk(mutexTransmit);static_assert(is_same_fsm<F, S>::value, "transit to different state machine");current_state_ptr->exit();// NOTE: we get into deep trouble if the action_function sends a new event.// TODO: implement a mechanism to check for reentrancyaction_function();current_state_ptr = &_state_instance<S>::value;current_state_ptr->entry();}template<typename S, typename ActionFunction, typename ConditionFunction>void transit(ActionFunction action_function, ConditionFunction condition_function) {if (condition_function()) {transit<S>(action_function);}}};//初始化了一个F* 的静态变量template<typename F>typename Fsm<F>::state_ptr_t Fsm<F>::current_state_ptr;template<typename F>std::mutex  Fsm<F> ::mutexTransmit;// --------------------------------------------------------------------------template<typename... FF>struct FsmList;template<> struct FsmList<> {static void set_initial_state() { }static void reset() { }static void enter() { }template<typename E>static void dispatch(E const&) { }};template<typename F, typename... FF>struct FsmList<F, FF...>{using fsmtype = Fsm<F>;static void set_initial_state() {fsmtype::set_initial_state();FsmList<FF...>::set_initial_state();}static void reset() {F::reset();FsmList<FF...>::reset();}static void enter() {fsmtype::enter();FsmList<FF...>::enter();}static void start() {set_initial_state();enter();}template<typename E>static void dispatch(E const& event) {fsmtype::template dispatch<E>(event);FsmList<FF...>::template dispatch<E>(event);}};// --------------------------------------------------------------------------template<typename... SS> struct StateList;template<> struct StateList<> {static void reset() { }};template<typename S, typename... SS>struct StateList<S, SS...>{static void reset() {//_state_instance<S>::value.exit();_state_instance<S>::value = S();StateList<SS...>::reset();}};// --------------------------------------------------------------------------template<typename F>struct MooreMachine : tinyfsm::Fsm<F>{virtual void entry(void) { };  /* entry actions in some states */void exit(void) { };           /* no exit actions */};template<typename F>struct MealyMachine : tinyfsm::Fsm<F>{// input actions are modeled in react():// - conditional dependent of event type or payload// - transit<>(ActionFunction)void entry(void) { };  /* no entry actions */void exit(void) { };   /* no exit actions */};} /* namespace tinyfsm */#define FSM_INITIAL_STATE(_FSM, _STATE)                               \
namespace tinyfsm {                                                   \template<> void Fsm< _FSM >::set_initial_state(void) {              \current_state_ptr = &_state_instance< _STATE >::value;            \}                                                                   \
}

4. 发散思考

■4.1 面向消息编程
本文的开始,我介绍了有向图的一些相关内容,有向图是一个很有趣的数据结构,它似乎模拟了我们生活中的很多场景,当我们把每一个定点看做一个状态,那么它就是我们的状态图。那么接下来我想介绍的就是把每一个定点看做一个协程一个具体的人把它们应用在编程中会是什么样?业务的驱动完全有消息来完成,没有锁的限制,这就是Carl Hewitt在1973年提出的Actor模型,也是我个人一直很喜欢的一种编程模型,可以将Actor想象成面向对象编程语言中的对象实例,不同的是Actor的状态不能直接读取和修改,方法也不能直接调用。Actor只能通过消息传递的方式与外界通信。每个参与者存在一个代表本身的地址,但只能向该地址发送消息。如下图。
在这里插入图片描述


http://chatgpt.dhexx.cn/article/rFite3de.shtml

相关文章

有限状态机

文章目录 有限状态机状态机的表示状态转移图二维表 实现穷举法查表法状态模式 总结 有限状态机 有限状态机(Finite State Machine) 缩写为 FSM。以下简称为状态机。 状态机有 3 个组成部分&#xff1a;状态、事件、动作。 状态&#xff1a;所有可能存在的状态。包括当前状态和…

什么是状态机?

前言 状态机在实际工作开发中应用非常广泛&#xff0c;在刚进入公司的时候&#xff0c;根据公司产品做流程图的时候&#xff0c;发现自己经常会漏了这样或那样的状态&#xff0c;导致整体流程会有问题&#xff0c;后来知道了状态机这样的东西&#xff0c;发现用这幅图就可以很…

什么是状态机(Finite-state machine)?

有限状态机 有限状态机(FSM)1、 什么是“状态”2、什么是状态机&#xff1f;3、状态机图怎么画&#xff1f;参考 有限状态机(FSM) 1、 什么是“状态” 先来解释什么是“状态”&#xff08; State &#xff09;。现实事物是有不同状态的&#xff0c;例如一个自动门&#xff0c…

什么是状态机?用C语言实现进程5状态模型

前言 状态机在实际工作开发中应用非常广泛&#xff0c;在刚进入公司的时候&#xff0c;根据公司产品做流程图的时候&#xff0c;发现自己经常会漏了这样或那样的状态&#xff0c;导致整体流程会有问题&#xff0c;后来知道了状态机这样的东西&#xff0c;发现用这幅图就可以很…

状态机(state machine)

一、状态机分类 Mealy状态机:输出取决于输入和当前状态 状态寄存器:由一组触发器组成,用来记忆状态机当前所处的状态,状态的改变只发生在时钟的跳变沿。状态寄存器由一组触发器组成,用来记忆状态机当前所处的状态,状态的改变只发生在时钟的跳变沿。 状态是否改变、如何…

状态机(有限状态自动机 FSM)介绍以及常用状态机种类对比

目录 状态机概念 : 为什么需要状态机: 使用场景 状态机四要素: 常见类型状态机: Squirrel State Machine Spring Statemachine 状态机概念 : 概念 : 状态机是有限状态自动机&#xff08;英语&#xff1a;finite-state machine&#xff0c;缩写&#xff1a;FSM&#xff…

STM32状态机编程----什么是状态机?

万事万物都有其状态 什么是状态 状态是人或事物表现出来的形态。是指现实&#xff08;或虚拟&#xff09;事物处于生成、生存、发展、消亡时期或各转化临界点时的形态或事物态势。 通过上面那句话&#xff0c;我们知道了状态就是一个对象在不同情况下对应的各种形态 做产品的…

什么是状态机?一篇文章就够了

1 概述 状态机[1]一般指有限状态机&#xff08;英语&#xff1a;finite-state machine&#xff0c;缩写&#xff1a;FSM&#xff09;又称有限状态自动机&#xff08;英语&#xff1a;finite-state automaton&#xff0c;缩写&#xff1a;FSA&#xff09;&#xff0c;是表示有限…

C语言_有限状态机(FSM)

C语言_有限状态机&#xff08;Finite State Machine&#xff09; 基本介绍 许多小型或复杂的应用程序都使用有限状态机 (FSM)&#xff0c;C 语言中的有限状态机是嵌入式系统的流行设计模式之一&#xff0c;有限状态机使开发变得容易和顺利。 有很多设备使用事件基态&#xf…

Unity字体展示下载

Unity字体种类展示 这是字体包里面的图片,是不是很多种字体. 下载链接: https://download.csdn.net/download/qq_42603590/12001130 这是下载字体包的地方,很便宜.没有积分的可以留言,我发给你 有时候可能回复的不是很快(抱拳了) 喜欢的话点个赞,关注一下再走吧,谢谢

Unity 之 官网下载地址,方便各个版本的 Unity 安装包下载

Unity 之 官网下载地址&#xff0c;方便各个版本的 Unity 安装包下载 目录 Unity 之 官网下载地址&#xff0c;方便各个版本的 Unity 安装包下载 一、简单介绍 二、各个版本下载入口网址 一、简单介绍 在 Unity 的下载地址现在不是很好找&#xff0c;这里保存一下 Unity 各…

Unity入门之路0-Unity下载安装以及版本选择

文章目录 下载链接Unity Hub和Unity的关系UnityHub下载(Win)两者比较 Unity版本选择许可证问题 下载链接 一定不要百度或者去垃圾网站下载盗版网站 &#xff0c;Unity是正版免费的&#xff0c;官方很关注使用者的感受&#xff0c;所以下载官网的就没问题。 https://unity.cn/re…

UnityHub下载缓存位置

一、下载Unity各版本的编辑器 C:\Users\XXX\AppData\Local\Temp\unityhub-xxx-xxx-xxx-xxx 我电脑是 C:\Users\Administrator\AppData\Local\Temp\unityhub-xxxx-xxxx 如果你不需要备份安装包&#xff0c;那么这个缓存的文件夹&#xff0c;就与你无关了&#xff0c;因为安装完…

使用UnityHub下载任意版本Unity

目录 方法一 使用链接方法二 官网下载(适用于2018.4.23及以上版本) unityHub上只能下载官方指定的版本,很多其他版本不能下载,下面介绍的是在unityHub下载任意版本的方法 方法一 使用链接 举例: 2019.2.11f1版本的unity----> unityhub://2019.2.11f1/5f859a4cfee5 格式 unit…

Unity给游戏对象贴图、从官网下载资源、导入导出

1、新建项目、在项目场景中创建几何对象并修改参数 在层级“”中创建一个立方体&#xff08;3D对象&#xff09;&#xff0c;同理也创建一个球体 创建好的立方体会显示在场景视图中 &#xff08;从场景视图或层级视图中&#xff09;选中几何体&#xff0c;选择场景视图中竖排工…

unity下载网页所有图片

用unity的c#脚本批量下载网页上的所有图片 1、将网页的html保存到本地 在网页上鼠标右击另存为如下图所示 保存html文件 2、通过截取<img“”>获取图片存储的地址 经过两个步骤之后就可以开始着手敲代码了 代码 html下载的本地地址和要保存的图片地址 //保存在本地ht…

Unity下载方法(超详细)

一、进入官网&#xff0c;点击[下载Unity]&#xff0c;点击右上角的小人头像&#xff0c;点击[创建Unity ID](创建ID的方法你点进去按照它要求你的一步一步做就行啦)。 二、创建完Unity ID并登录(或已有Unity ID并登录)后&#xff0c;下拉网页&#xff0c;点击[下载Unity Hub]&…

Unity 改变下载资源商店中资源默认路径的方法

Unity 改变下载资源商店中资源默认路径的方法 Unity资源商店中免费资源可以被我们很好的使用&#xff0c;尤其对于暂时还不会自己设计资源的创作者。但是&#xff0c;unity默认是将资源商店的下载路径设置在了C:\Users\操作系统当前用户\AppData\Roaming\Unity\Asset Store-5.x…

Unity 各版本下载方法

开发Unity的&#xff0c;获取不同版本Unity版本和了解Unity最新动态很重要&#xff0c;现在更新迭代很频繁&#xff0c;在开发时&#xff0c;不论遇到项目升级&#xff0c;还是插件要求&#xff0c;还是老项目运行&#xff0c;总是在多个版本间切换。 是不是经常遇到&#xff0…

Unity3d_NGUI和UGUI的学习

由于之前刚入门的时候&#xff0c;应Unity3d整体发展的要求我们自学了UGUI(相对来说UGUI比NGUI做得更好一些&#xff0c;后面会有2者对比)&#xff0c;但是后来公司要求使用NGUI&#xff0c;所以我这边把之前学习UGUI&#xff08;不全&#xff0c;当时资源有限&#xff09;和NG…