(八)C++自制植物大战僵尸游戏植物基类讲解

植物大战僵尸游戏开发教程专栏地址icon-default.png?t=N7T8http://t.csdnimg.cn/m0EtD


在植物大战僵尸游戏中,最重要的两个类别就是植物与僵尸。植物可以对僵尸进行攻击,不同的植物攻击方式千差万别,但是不同植物又有许多相同的属性。在基类(父类)中定义植物共有属性,供派生类(子类)继承。

植物状态转换

植物继承关系图

文件位置

植物基类代码文件在Class/Plants文件夹中,详细位置如下图所示。

Plants.h

植物类型定义

首先在植物基类的头文件中使用了枚举定义了植物类型。 植物类型定义如下。

enum class PlantsType
{
	None = -1,
	SunFlower,          /* 向日葵 */
	PeaShooter,         /* 豌豆射手 */
	WallNut,            /* 坚果墙 */
	CherryBomb,         /* 樱桃炸弹 */
	PotatoMine,         /* 土豆雷 */
	CabbagePult,        /* 卷心菜投手 */
	Torchwood,          /* 火炬树桩 */
	Spikeweed,          /* 地刺 */
	Garlic,             /* 大蒜 */
	FirePeaShooter,     /* 火焰豌豆射手 */
	Jalapeno,           /* 火爆辣椒 */
	AcidLemonShooter,   /* 强酸柠檬射手 */
	Citron              /* 离子缘 */
	//...
}; 

植物基类定义

植物基类继承Cocos2d-x中Node类。在基类中定义了大量的属性与方法。植物共有的属性包括阳光数量需求、冷却时间、植物攻击力、植物标签、植物的绘制顺序、植物编号、位置等。详细内容请看下方代码。

class  Plants :public Node
{
public:
	/**
	 *暂停植物所有动作
	 */
	static void stopPlantsAllAction();

	/**
	 *创建图片
	*/
	virtual Sprite* createPlantImage() = 0;

	/**
	 *创建植物动画
	 */
	virtual void createPlantAnimation() = 0;

	/**
	 *判断僵尸与植物之间的相对位置
	 */
	virtual void determineRelativePositionPlantsAndZombies();

	/**
	 *检测植物生命情况
	 */
	virtual void checkPlantHealthPoint() {}

	/**
	 *设置节点
	 */
	virtual void setPlantNode(Node* node) { _node = node; }

	/**
	 *设置位置
	 */
	virtual void setPlantPosition(const Vec2& position) { _position = position; }

	/**
	 *设置绘制顺序
	 */
	virtual void setPlantLocalZOrder(const int& order) { _zOrder = order; }

	/**
	 *设置植物所在行列
	 */
	virtual void setPlantRowAndColumn(const Vec2& rowAndColumn) { _rowAndColumn = rowAndColumn; }

	/**
	 *设置植物是否可见
	 */
	virtual void setPlantVisible(const bool visible);

	/**
	 *获取植物类型
	 */
	PlantsType getPlantType() const { return _plantsType; }

	/**
	 *设置植物标记
	 */
	virtual void setPlantTag(const int& tag) { _plantTag = tag; }

	/**
	 *设置植物生命值
	 */
	virtual void setPlantHealthPoint(int healthPoint) { _healthPoint = healthPoint; }

	/**
	 *设置植物大小
	 */
	virtual void setPlantScale();

	/**
	 *获取植物标记
	 */
	virtual int getPlantTag() const { return _plantTag; }

	/**
	 *获取动画
	 */
	SkeletonAnimation* getPlantAnimation() { return _plantAnimation; }

	/**
	 *判断植物是否存活
	 */
	virtual bool getPlantIsSurvive() const;

	/**
	 *获取植物生命值
	 */
	virtual float getPlantHealthPoint() const { return _healthPoint; }

	/**
	 *@ 1.获取植物所在行列
	 */
	virtual Vec2 getPlantRowAndColumn() const { return _rowAndColumn; }

	/**
	 *@ 2.获取植物所在行
	 */
	virtual int getPlantRow() const { return _rowAndColumn.x; }

	/**
	 *@ 3.获取植物所在列
	 */
	virtual int getPlantColumn() const { return _rowAndColumn.y; }

	/**
	 *获取是否显示
	 */
	virtual bool getPlantVisible();

	/**
	 *获取植物是否可以删除
	 */
	virtual bool* getPlantIsCanDelete();

CC_CONSTRUCTOR_ACCESS:
	Plants(Node* node = nullptr, const Vec2& position = Vec2::ZERO);
	~Plants();
	
protected:
	/**
	 *种植植物
	 */
	virtual SkeletonAnimation* plantInit(const std::string& plantname, const std::string& animaionname);

	/**
	 *创建植物图片
	 */
	virtual void imageInit(const std::string& name, const Vec2& position);

	/**
	 *减少植物生命值
	 */
	virtual void reducePlantHealthPoint(int number) { _healthPoint -= number; }

	/**
	 *泥土飞溅动画
	 */
	virtual void setPlantSoilSplashAnimation(const float& scale);

	/**
	 *设置植物影子
	 */
	virtual void setPlantShadow(const float& scale);

	/**
	 *设置植物受到伤害闪烁
	 */
	virtual void setPlantHurtBlink();
	virtual void setPlantHurtBlink(PlantsType type) const;

	/**
	 *获取僵尸是否在植物前方
	 */
	virtual bool getZombieIsTheFrontOfPlant(Zombies* zombie) const;

	/**
	 *获取僵尸是否于植物在同一行
	 */
	virtual bool getZombieIsSameLineWithPlant(Zombies* zombie) const;

	/**
	 *获取僵尸是否遇到植物
	 */
	virtual bool getzombieIsEncounterPlant(Zombies* zombie) const;

	/**
	 *僵尸吃植物
	 */
	virtual void zombieEatPlant(Zombies* zombie);

	/**
	 *僵尸从吃植物中恢复
	 */
	virtual void zombieRecoveryMove(Zombies* zombie);

private:
	void setPlantGLProgram();

protected:
	int _sunNeed;                               // 阳光需求
	int _costGold;                              // 花费金币
	int _costMasonry;                           // 花费砖石
	int _combatEffecttiveness;                  // 战斗力
	int _plantTag;                              // 植物标签
	int _zOrder;                                // 绘制顺序
	int _plantNumber;                           // 存储植物编号(唯一性 )
	bool _isLoop;                               // 是否循环
	bool _isCanDelete[2];                       // 是否可以删除
	float _coolDownTime;                        // 冷却时间
	float _healthPoint;                         // 生命值
	float _totalHealthPoint;                    // 生命总值
	Vec2 _position;                             // 位置
	Vec2 _rowAndColumn;                         // 详细地址,所在行列
	SkeletonAnimation* _plantAnimation;         // 植物动画
	Node* _node;                                // 节点
	Global* _global;                            // 全局变量
	Sprite* _plantImage;                        // 植物图片
	Sprite* _plantShadow;                       // 植物影子
	PlantsType _plantsType;                     // 植物类型
	SkeletonAnimation* _soilSplashAnimation;    // 泥土飞溅动画
	static int plantNumber;                     // 植物编号(唯一性)

private:
	static GLProgram* _normalGLProgram;
	static GLProgram* _highLightGLProgram;
	GLProgramState* _highLightGLProgramState;
	float _highLightIntensity;
};

 Plants.cpp

在源文件中实现了头文件件中定义的函数。

构造函数

植物构造函数使用初始化列表的方式将变量进行初始化。

Plants::Plants(Node* node, const Vec2& position) :
	_node(node)
,	_position(position)
,	_plantShadow(nullptr)
,	_plantImage(nullptr)
,	_plantAnimation(nullptr)
,	_soilSplashAnimation(nullptr)
,   _highLightGLProgramState(nullptr)
,	_sunNeed(NOINITIALIZATION)
,	_coolDownTime(NOINITIALIZATION)
,	_costGold(NOINITIALIZATION)
,	_costMasonry(NOINITIALIZATION)
,	_healthPoint(NOINITIALIZATION)
,	_plantTag(NOINITIALIZATION)
,	_isLoop(true)
,   _isCanDelete{ false,false }
,   _zOrder(0)
,   _highLightIntensity(0.6f)
,   _plantNumber(++plantNumber)
,   _combatEffecttiveness(0)
,   _plantsType(PlantsType::None)
,	_global(Global::getInstance())
{
}

plantInit()函数

函数有两个参数,第一个参数表示文件名称,第二个参数表示动画名称。

SkeletonAnimation* Plants::plantInit(const std::string& plantname, const std::string& animaionname)
{
	auto iter = _global->userInformation->getAnimationData().find(plantname);
	if (iter != _global->userInformation->getAnimationData().end())/* 如果可以找到 */
	{
		_plantAnimation = SkeletonAnimation::createWithData(iter->second);
		_plantAnimation->setPosition(_position);
		_plantAnimation->setAnimation(0, animaionname, _isLoop);
		_plantAnimation->setLocalZOrder(_zOrder);
		_plantAnimation->setTag(_plantTag);
		_plantAnimation->setTimeScale(0.7f + rand() % 4 / 10.f);
		setPlantGLProgram();
		return _plantAnimation;
	}
	return nullptr;
}

函数根据参数提供的文件名称进行查找,如果没有找到返回空指针(nullptr)表示失败,否则创建东,并根据参数二设置为指定动画。游戏中使用的是Spine骨骼动画,创建完成后会返回SkeletonAnimation的骨骼动画类。动画创建完成后并设置相应属性,如位置、绘制顺序、植物标签(编号)以及植物播放动画播放速率,为了使植物动画看起来并不是完全统一的,所以设置了随机数。

setPlantGLProgram()函数设置了植物动画的shader。植物被僵尸攻击后会出现短暂高亮状态,表示受到攻击。在Cocos2d-x中不能直接调整动画的亮度,所以使用shader实现该功能。


setPlantGLProgram()函数

函数对_normalGLProgram_highLightGLProgram进行了初始化,_normalGLProgram用于渲染正常状态下的动画、_highLightGLProgram用于渲染高亮状态下的动画。

void Plants::setPlantGLProgram()
{
	if (!_normalGLProgram || !_highLightGLProgram)
	{
		_normalGLProgram = _plantAnimation->getGLProgram();
		_highLightGLProgram = Zombies::getHighLight();
	}
}

Cocos2d-x加载自定义shader的方法如下,使用 GLProgramCache类加载shader文件并使用GLProgram类创建shader,创建好的类放入GLProgramCache中存储,供后续使用。

GLProgram* Zombies::getHighLight()
{
	auto program = GLProgramCache::getInstance()->getGLProgram("MyHighLightShader");
	if (!program)
	{
		program = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert,
			FileUtils::getInstance()->getStringFromFile("resources/Text/Bloom.fsh").c_str());
		program->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_POSITION, GLProgram::VERTEX_ATTRIB_POSITION);
		program->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_COLOR, GLProgram::VERTEX_ATTRIB_COLOR);
		program->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_TEX_COORD, GLProgram::VERTEX_ATTRIB_TEX_COORD);
		program->link();
		program->updateUniforms();

		GLProgramCache::getInstance()->addGLProgram(program, "MyHighLightShader");
	}
	return program;
}

determineRelativePositionPlantsAndZombies()函数

函数用于判断植物与僵尸的位置,该函数会被在游戏的每一帧中调用。通过遍历僵尸集合,判断每一只僵尸与该植物的位置关系,如果僵尸移动到植物的攻击范围内,就会更新植物动画并攻击僵尸,同样的,植物进入僵尸攻击范围,僵尸就会吃掉植物。

此函数是一个虚函数,不同的派生类植物可能会重写该函数。在植物基类中,绝大部分的函数都是虚函数,派生类可以对函数进行重写。

void Plants::determineRelativePositionPlantsAndZombies()
{
	for (auto zombie : ZombiesGroup)
	{
		zombieEatPlant(zombie);      /* 僵尸吃植物 */

		zombieRecoveryMove(zombie);  /* 僵尸恢复移动 */
	}
}

zombieEatPlant()函数

函数有一个参数,传入的是攻击植物的僵尸实例。

僵尸攻击植物,首先要进行各种判断,植物是否存活、僵尸是否存活、植物是否在僵尸的攻击范围内等。如果全部符合要求则改变僵尸动画,并对监听动画,触发条件后使用reducePlantHealthPoint()函数扣除植物血量,并使用setPlantHurtBlink()函数让植物产生短暂高亮状态。

void Plants::zombieEatPlant(Zombies* zombie)
{
	if (getPlantIsSurvive() && getZombieIsSameLineWithPlant(zombie) && getzombieIsEncounterPlant(zombie)&& /* 植物存活 && 植物与僵尸在同一行 && 僵尸遇到植物 */
		zombie->getZombieType() != ZombiesType::SnowZombies) /* 僵尸不是雪人僵尸 */
	{
		if (zombie->getZombieIsSurvive() && !zombie->getZombieIsEat())
		{
			const string eateffect[3] = { "chomp","chomp2","chompsoft" };
			zombie->setZombieEatPlantNumber(_plantNumber);
			zombie->setZombieStop();
			zombie->setZombieIsEat(true);
			zombie->getZombieAnimation()->setAnimation(1,
				zombie->getZombieType() == ZombiesType::LmpZombies ? "Zombies_Eat" : rand() % 2 ? "Zombies_Eat" : "Zombies_Eat1", true);
			zombie->getZombieAnimation()->setEventListener([this, eateffect](spTrackEntry* entry, spEvent* event)
				{
					if (!strcmp(event->data->name, "eat"))
					{
						if (event->intValue == 1)
						{
							reducePlantHealthPoint(100);
							Bullet::playSoundEffect(eateffect[rand() % 3]);
							setPlantHurtBlink();
						}
					}
				});
		}
	}
}

setPlantHurtBlink()函数

创建Cocos2d-x动画序列不断改变shader中的亮度值_highLightIntensity,实现植物动画高亮闪动状态。

void Plants::setPlantHurtBlink()
{
	auto action = Repeat::create(Sequence::create(
		CallFunc::create([this]()
			{
				_highLightIntensity -= 0.01f;
				_highLightGLProgramState->setUniformFloat("intensity", _highLightIntensity);
			}), DelayTime::create(0.005f), nullptr), 50);

	_plantAnimation->runAction(Sequence::create(
		CallFunc::create([this]()
			{
				_plantAnimation->setGLProgram(_highLightGLProgram);
				_highLightGLProgramState = _plantAnimation->getGLProgramState();
				_highLightGLProgramState->setUniformFloat("intensity", 0.6f);
			}), DelayTime::create(0.15f),
		CallFunc::create([this]()
			{
				_plantAnimation->setGLProgram(_normalGLProgram);
				_highLightIntensity = 0.6f;
			}), nullptr));
}

zombieRecoveryMove()函数

函数有一个参数,传入僵尸实例。如果僵尸正在吃该植物,并且植物死亡,则僵尸会恢复移动。死亡的植物设置为不可见,后续植物将被清除。

void Plants::zombieRecoveryMove(Zombies* zombie)
{
	if (!getPlantIsSurvive() && zombie->getZombieEatPlantNumber() == _plantNumber &&  /* 植物死亡 && 僵尸是吃的该植物 */
		zombie->getZombieIsEat() && zombie->getZombieIsStop()) /* 僵尸正在吃植物 && 僵尸正在停止移动 */
	{
		setPlantVisible(false);
		_plantAnimation->stopAllActions();
		Bullet::playSoundEffect("gulp");
		zombie->setZombieIsEat(false);
		if (!zombie->getZombieIsPlayDieAnimation()) /* 僵尸没有播放死亡动画 */
		{
			zombie->getZombieAnimation()->setMix("Zombies_Eat", Zombies::getZombieAniamtionName(zombie->getZombieType()), 1.5f);
			zombie->getZombieAnimation()->addAnimation(1, Zombies::getZombieAniamtionName(zombie->getZombieType()), true);
			zombie->setZombieCurrentSpeed(zombie->getZombieSpeed());
		}
	}
}

getZombieIsSameLineWithPlant()函数

函数有一个参数,传入僵尸实例。通过植物与僵尸的纵坐标进行比较,判断植物与僵尸是否在同一行。

bool Plants::getZombieIsSameLineWithPlant(Zombies* zombie) const
{
	return fabs(zombie->getZombiePositionY() - 10 - _plantAnimation->getPositionY()) <= 10 ? true : false;
}

getzombieIsEncounterPlant()函数

函数有一个参数,传入僵尸实例。通过植物与僵尸的横坐标进行比较,判断植物与僵尸是否在同一列。

bool Plants::getzombieIsEncounterPlant(Zombies* zombie) const
{
	return fabs(zombie->getZombiePositionX() - _plantAnimation->getPositionX()) <= 80 ? true : false;
}

getZombieIsTheFrontOfPlant()函数

函数有一个参数,传入僵尸实例。通过植物与僵尸的横坐标进行比较,僵尸的横坐标是否大于植物横坐标,来判断僵尸是否在植物的前方。

bool Plants::getZombieIsTheFrontOfPlant(Zombies* zombie) const
{
	return zombie->getZombiePositionX() >= _plantAnimation->getPositionX() ? true : false;
}

其他函数

其他函数较为较为简单,这里不一一列举,可以自行查看。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

temux安装debian自用记录

http://ip:9001/ user/123 http://ip:5705/index admin/drpy 一、安装Ubuntu1804 1&#xff0e;首先安装termux.app 2&#xff0e;启动该app&#xff0c;输入命令 curl -Lo l l.tmoe.me; sh l 3&#xff0e;运行过程中连续选“Y”&…

面向对象设计模式之概念

一、面向对象设计模式 按目的分为创建型&#xff08;creational&#xff09;、结构型&#xff08;structural&#xff09;和行为型&#xff08;behavioural&#xff09;&#xff1b;按范围分为类模式和对象模式。 二、设计原则 两大基础设计原则 程序设计的原则&#xff1a;模…

electron项目打包慢、打包报错

项目使用了electron框架&#xff0c;在第一次打包或者网络条件不好的环境下进行打包时熟速度慢的出奇&#xff0c;甚至经常出现打包失败的情况&#xff08;如上面图片的报错&#xff09;。 这是因为&#xff0c;在electron打包的过程中&#xff0c;需要去官方源https://github.…

安卓手机怎么连接电脑?这三个方法即刻解决!

随着智能手机的普及&#xff0c;我们越来越依赖手机进行工作和娱乐。然而&#xff0c;有时候我们需要将手机上的数据或文件传输到电脑上&#xff0c;或者需要在电脑上进行某些操作。那么&#xff0c;安卓手机怎么连接电脑呢&#xff1f;这篇文章将为你介绍三种简单的方法&#…

车载摄像头画质增强解决方案,赋能智能驾驶新时代

在智能化浪潮席卷汽车产业的今天&#xff0c;车载摄像头作为智能驾驶的“眼睛”&#xff0c;其画质清晰度直接关系到车辆感知环境的准确性和驾驶的安全性。然而&#xff0c;面对复杂多变的行车环境&#xff0c;如何确保车载摄像头在不同场景下都能呈现出高质量的图像&#xff0…

OpenHarmony轻量系统开发【1】初始OpenHarmony

1.1系统类型 OpenHarmony是由开放原子开源基金会&#xff08;OpenAtom Foundation&#xff09;孵化及运营的开源项目&#xff0c;目标是面向全场景、全连接、全智能时代&#xff0c;基于开源的方式&#xff0c;搭建一个智能终端设备操作系统的框架和平台&#xff0c;促进万物互…

mac使用docker部署confluence

第一步拉镜像 选择7.13.6 点击pull 在镜像出 创建容器 并配置映射 需要等待一会 浏览器访问 confluence 服务 http://localhost:8090/ 语言选择中文 复制服务器ID 然后停止docker 容器 BBN3-O8RH-XJPI-GYAA 复制docker中confluence的系统文件 进入Files 考文件 文件目录…

基于springboot实现在线考试系统设计【项目源码+论文说明】

基于springboot实现在线考试管理系统演示 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了基于JavaWeb技术的在线考试系统设计与实现的开发全过程。通过分析基于Java Web技术的在线考试系统设计与实现管理的不…

vue给页面添加水印

vue给页面添加水印 <template><div class"home"><router-view /></div> </template><script>export default {components: {},data() {return {}},computed: {},mounted() {// 获取需要观察的节点const target document.queryS…

【DM8】ET SQL性能分析工具

通过统计SQL每个操作符的时间花费&#xff0c;从而定位到有性能问题的操作&#xff0c;指导用户去优化。 开启ET工具 INI参数&#xff1a; ENABLE_MONITOR1 MONITOR_SQL_EXEC1 查看参数 select * FROM v$dm_ini WHERE PARA_NAMEMONITOR_SQL_EXEC;SELECT * FROM v$dm_ini WH…

sky08、09笔记常用组合逻辑电路

本节的目的是为了更好的预估delay。 1.1bit全加器 module fadd_1b( a, b, cin, s, cout ); input wire a,b,cin; output wire s,cout;wire p,g; assign p a|b;//propagate carry assign g a&b;//generate carry assign s a^b^cin; assign cout (p&cin)|g; endmodu…

c++的学习之路:19、模板

摘要 本章主要是说了一些模板&#xff0c;如非类型模板参数、类模板的特化等等&#xff0c;文章末附上测试代码与导图 目录 摘要 一、非类型模板参数 二、类模板的特化 1、概念 2、函数模板特化 3、类模板特化 三、模板的分离编译 1、什么是分离编译 2、模板的分离编…

一次网卡驱动BUG故障的排错历程

前言 在日常运维中&#xff0c;总会遇到一些棘手的故障或问题&#xff0c;尤其面临多系统融合的兼容性或一些融合节点可能存在未知bug等方面&#xff0c;排错难度都会增加。 本文将从一次小事件为入口进行延伸&#xff0c;将宿主机esxi基础系统的多融合节点故障的排错历程展开…

分布式监控平台---Zabbix

一、Zabbix概述 作为一个运维&#xff0c;需要会使用监控系统查看服务器状态以及网站流量指标&#xff0c;利用监控系统的数据去了解上线发布的结果&#xff0c;和网站的健康状态。 利用一个优秀的监控软件&#xff0c;我们可以&#xff1a; 通过一个友好的界面进行浏览整个…

C/C++ C/C++ 入门(6)模板初阶

个人主页&#xff1a;仍有未知等待探索-CSDN博客 专题分栏&#xff1a;C 多多指教&#xff01; 一、泛型编程 在之前&#xff0c;我们进行编程的时候&#xff0c;总是针对于某一个具体的问题。就比如说&#xff0c;如何实现一个int类型的swap函数呢&#xff1f;大家肯定会写。…

MySQL 表管理

目录 建库 语法&#xff1a; 库名命名规则&#xff1a; 相关命令&#xff1a; 建表 语法&#xff1a; 相关命令&#xff1a; 修改表 语法&#xff1a; 常用操作命令 复制表 数据类型 MySQL的10种常用数据类型&#xff1a; 数据的导入和导出 导入&#xff1a; 格…

iptables 学习

文章目录 iptables 学习iptables基本组件&#xff1a;常用iptables命令&#xff1a;iptables -L 输出及解释解释&#xff1a; iptables “奇淫巧技”端口转发&#xff08;port forwarding&#xff09;流量重定向到透明代理防止DDoS攻击防止SYN洪泛攻击黑名单使用状态模块跟踪连…

第14届java A组蓝桥杯做题记录

A题 特殊日期 package Java14省赛.Java研究生组;import java.time.Year; //特殊判断一下2月份&#xff0c;leaf 为true 1 import java.util.*;import 蓝桥杯.dfs_n皇后; public class 特殊日期 {static int sum(int d){int res 0;while(d > 0){res d % 10;d / 10;}return…

基于Springboot+Vue的Java项目-房产销售系统(附演示视频+源码+LW)

大家好&#xff01;我是程序员一帆&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &am…

FlexLua低代码便捷打造4G转RS485网关设备

在物联网时代&#xff0c;各种设备之间的互联互通变得越来越重要&#xff0c;而4G转RS485网关设备的出现为不同设备之间的通信提供了更便捷的方式&#xff0c;推动了物联网技术的发展。 4G转RS485网关的通信原理相对简单易懂。它通过4G网络接收数据&#xff0c;然后将数据转换成…