ROS中7自由度机械臂自定义发布订阅节点

article/2025/9/28 0:02:45

本篇用来记录一次作业的学习例程,错误之处敬请谅解,后续修改

作业要求:
写两个ROS节点,一个节点发布连续变化(可以按sin曲线变化)的7自由度的关节角信息;另一个节点订阅第一个节点发布的关节角信息,然后通过正运动学(可以用DH法建模)计算出最终的末端位姿,并把这个末端位姿在rviz中显示出来。机器人模型可以考虑用KUKA,其他的也可以。

1.准备工作

创建工作空间及功能包

创建工作空间及src文件夹,src文件夹下 init 使它变成工作空间的属性

mkdir -p ~/catkin_ws/src
cd ~/catkin_wc/src
catkin_init_workspace

在src文件夹下创建lwr_description功能包,依赖std_msgs、roscpp、message_generation、joint_state_controller、robot_state_publisher库

catkin_create_pkg lwr_description message_generation std_msgs roscpp joint_state_controller robot_state_publisher

lwr_description功能包中包含以下文件夹:

urdf:用于存放机器人模型的URDF或xacro文件

meshes: 用于放置URDF中引用的模型渲染文件

launch:用于保存相关启动文件

config: 用于保存Rviz的配置文件

src: 用于存放编写的节点

include: 放置功能包中需要用到的头文件
在这里插入图片描述

准备URDF模型

在GitHub上找到一个7自由度KUKA-LWR的urdf模型文件 lwr.urdf,放在urdf文件夹下

https://github.com/ipabslmc/exotica/blob/master/exotica_examples/resources/robots/lwr_simplified.urdf

check_urdf
check_urdf命令检查urdf文件没有问题
在这里插入图片描述

Rviz中显示模型

找到URDF模型后,可以在Rviz中将该模型显示出来

在lwr_description功能包下的launch文件夹中创建显示lwr.urdf模型的launch文件,display_lwr_urdf.launch

参考《ROS机器人开发实践》6.2.4,初步编写launch文件

<launch><param name="robot_description" textfile="$(find lwr_description)/urdf/lwr.urdf" /><node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher" /><node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" /><node name="rviz" pkg="rviz" type="rviz" args="-d $(find lwr_description)/config/lwr_urdf.rviz" required="true" />	</launch>

通过launch文件启动

roslaunch lwr_description display_lwr_urdf.launch

可以看到KUKA-LWR URDF模型在Rviz中成功显示
在这里插入图片描述

2.编写发布者代码

创建一个发布连续变化 (按sin曲线变化) 的7自由度关节角信息的节点

#include <ros/ros.h>
#include <sensor_msgs/JointState.h>			//使用的消息类型为sensor_msgs::JointState
#include <math.h>
#include <string>
#define PI acos(-1.0)int main(int argc, char** argv)
{ros::init(argc, argv, "joint_state_publisher_sy");  	//节点初始化          ros::NodeHandle nh;										//创建节点句柄ros::Publisher joint_state_pub1 = nh.advertise<sensor_msgs::JointState>("joint_states", 100);//创建发布者joint_state_pub1,发布话题joint_states,消息类型sensor_msgs::JointStateros::Rate loop_rate(0.5);								//设置循环的频率0.5HZ(2s)//值设为10时有利于观察Rvizint count = 0;while (ros::ok()){sensor_msgs::JointState joint_msg;       			            //声明 sensor_msgs::JointState 类的对象为 joint_msgjoint_msg.header.stamp = ros::Time::now();    //消息头的赋值    joint_msg.name.resize(7);     						                           joint_msg.position.resize(7);//7个joint对应数组长度为7joint_msg.name[0] = "lwr_arm_0_joint";                    joint_msg.position[0] = sin(count * PI / 18);joint_msg.name[1] = "lwr_arm_1_joint";                       joint_msg.position[1] = sin(count * PI / 18);joint_msg.name[2] = "lwr_arm_2_joint";joint_msg.position[2] = sin(count * PI / 18);joint_msg.name[3] = "lwr_arm_3_joint";joint_msg.position[3] = sin(count * PI / 18);joint_msg.name[4] = "lwr_arm_4_joint";joint_msg.position[4] = sin(count * PI / 18);joint_msg.name[5] = "lwr_arm_5_joint";joint_msg.position[5] = sin(count * PI / 18);joint_msg.name[6] = "lwr_arm_6_joint";joint_msg.position[6] = sin(count * PI / 18);joint_state_pub1.publish(joint_msg);  				//publish()方法 发布消息                 ROS_INFO("publish success");       					//输出 publish success                      loop_rate.sleep();    								//按照循环频率延时						                     count++;}return 0;
}

关于sensor_msgs::JointState 消息类型

为了使创建的URDF机器人模型正确运动,必须给出robot_state_publisher 节点所需的sensor_msgs::JointState型topic: joint_states
sensor_msgs::JointState 消息格式为:

std_msgs/Header header
string[] name
float64[] position
float64[] velocity
float64[] effort

声明消息变量

sensor_msgs::JointState joint_state;

消息头的赋值

joint_state.header.stamp = ros::Time::now();

消息内容的赋值

从上述消息格式中可知,该消息数据包含多个不同含义的数组,并且该数组没有指定数组长度,因此在赋值时需要明确指定其数组长度,并赋值。方法有两种:

1.resize指定数组长度,再赋值

joint_state.name.resize(3); 		//指定name数组长度
joint_state.position.resize(3);		//指定position数组长度
joint_state.name[0] ="joint1";		//name数组的第一个元素赋值
joint_state.position[0] = 0;		//position数组的第一个元素赋值

2.{}赋值 该方法类似于C/C++声明数组并赋初值

joint_state.name={"joint1","joint2","joint3"};	//指定数组大小,并赋值
joint_state.position={0,0,0};

velocity和effort相同,经过上述两步赋值后,可正常发布消息

此处参考:
http://wiki.ros.org/robot_state_publisher?distro=melodic
https://wenku.baidu.com/view/e93ac74d551252d380eb6294dd88d0d233d43cab.html

3.编写订阅者代码

#include <ros/ros.h>
#include <std_msgs/String.h>
#include <sensor_msgs/JointState.h>
#include <string>void stateInfoCallback(const sensor_msgs::JointState::ConstPtr& msg)
{ROS_INFO("I heard: [%s],position: [%f],[%s],position: [%f][%s],position: [%f][%s],position: [%f][%s],position: [%f][%s],position: [%f][%s],position: [%f]",msg->name[0].c_str(),msg->position[0],msg->name[1].c_str(),msg->position[1],msg->name[2].c_str(),msg->position[2],msg->name[3].c_str(),msg->position[3],msg->name[4].c_str(),msg->position[4],msg->name[5].c_str(),msg->position[5],msg->name[6].c_str(),msg->position[6]//输出7个 joint的名字和position);        
}int main(int argc, char **argv)
{ros::init(argc, argv, "joint_state_subscriber_sy"); 	//节点初始化          ros::NodeHandle n;										//创建节点句柄ros::Subscriber joint_state_sub1 = n.subscribe("joint_states", 10, stateInfoCallback);//创建订阅者 joint_state_sub1 订阅话题 joint_states 注册回调函数ros::spin();											//循环等待回调函数											return 0;
}

配置CMakeLists.txt中的编译规则

add_executable(joint_state_publisher_sy src/joint_state_publisher_sy.cpp)		target_link_libraries(joint_state_publisher_sy ${catkin_LIBRARIES})

将joint_state_publisher_sy.cpp编译成可执行文件joint_state_publisher_sy
C++接口通过target_link_libraries库与ROS进行连接

add_executable(joint_state_subscriber_sy src/joint_state_subscriber_sy.cpp) target_link_libraries(joint_state_subscriber_sy ${catkin_LIBRARIES})

终端编译并运行订阅者和发布者

cd catkin_ws
catkin_make
roscore
rosrun lwr_description joint_state_subscriber_sy
rosrun lwr_description joint_state_publisher_sy

可以看到相应的发布订阅信息
在这里插入图片描述

4.完善launch启动文件

加入编写的发布订阅节点,使他们能够在屏幕中显示

<launch><param name="robot_description" textfile="$(find lwr_description)/urdf/lwr.urdf" /><!-- 运行joint_state_publisher节点,发布机器人的关节状态 -->
<node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher" /><!-- 运行robot_state_publisher节点,发布TF,没有这个无法看到模型 -->
<node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" /><!-- 运行编写的joint_state_subscriber_sy节点 -->
<node name="joint_state_subscriber_sy" pkg="lwr_description" type="joint_state_subscriber_sy" output="screen" /><!-- 运行编写的joint_state_publisher_sy节点 -->
<node name="joint_state_publisher_sy" pkg="lwr_description" type="joint_state_publisher_sy" output="screen" /><!-- 运行rviz可视化界面 -->
<node name="rviz" pkg="rviz" type="rviz" args="-d $(find lwr_description)/config/lwr_urdf.rviz" required="true" />	</launch>

通过启动文件运行:

roslaunch lwr_description display_lwr_urdf.launch

可以看到机械臂在rviz中连续不断运动
在这里插入图片描述

5.正运动学DH法解算末端位姿

URDF模型标签的解读:

origin: 定义joint的起点

axis: 定义该joint的旋转轴

截取 lwr.urdf 中的部分需要用到的代码<joint name="base_joint" type="fixed">                                                   <origin rpy="0 0 3.14159265359" xyz="0 0 0.02"/>
<joint name="lwr_arm_0_joint" type="revolute"><origin rpy="0 0 0" xyz="0 0 0.11"/><axis xyz="0 0 1"/>
<joint name="lwr_arm_1_joint" type="revolute"><origin rpy="0 0 0" xyz="0 0 0.2005"/><axis xyz="0 1 0"/>
<joint name="lwr_arm_2_joint" type="revolute"><origin rpy="0 0 0" xyz="0 0 0.20"/><axis xyz="0 0 1"/>
<joint name="lwr_arm_3_joint" type="revolute"><origin rpy="0 0 0" xyz="0 0 0.20"/><axis xyz="0 -1 0"/>
<joint name="lwr_arm_4_joint" type="revolute"><origin rpy="0 0 0" xyz="0 0 0.20"/><axis xyz="0 0 1"/>
<joint name="lwr_arm_5_joint" type="revolute"><origin rpy="0 0 0" xyz="0 0 0.19"/><axis xyz="0 1 0"/>
<joint name="lwr_arm_6_joint" type="revolute"><origin rpy="0 0 0" xyz="0 0 0.078"/><axis xyz="0 0 1"/>

D-H法建模

在这里插入图片描述

D-H table

iα i-1a i-1θ id i
100θ1-PI0.11
2PI/20θ20
3-PI/20θ30.2
4-PI/20θ4+PI0
5-PI/20θ5-PI0.2
6PI/20θ60
7-PI/20θ70.078

通过公式求解末端位姿
在这里插入图片描述
正运动学解算方法参考:https://www.bilibili.com/video/BV1v4411H7ez?p=3&spm_id_from=pageDriver

完善订阅者代码

修改订阅者代码使其能够输出末端位姿

#include <ros/ros.h>
#include <std_msgs/String.h>
#include <sensor_msgs/JointState.h>
#include <string>
#include<math.h>
#define PI acos(-1.0)void personInfoCallback(const sensor_msgs::JointState::ConstPtr& msg)
{float pos[7];				pos[0] = msg->position[0];pos[1] = msg->position[1];pos[2] = msg->position[2];pos[3] = msg->position[3];pos[4] = msg->position[4];pos[5] = msg->position[5];pos[6] = msg->position[6];float theta[] = { pos[0] - PI , pos[1] , pos[2] , pos[3] , PI + pos[4] ,pos[5] - PI ,pos[6] };float d1 = 0.11;float d3 = 0.2;float d5 = 0.2;float d7 = 0.078;//此处只是为了矩阵计算,应当有更好的计算方法,暂时不会,学习中,后续修改float a11 = (cos(theta[0]) * cos(theta[1]) * cos(theta[2]) * cos(theta[3])) + (sin(theta[3]) * cos(theta[0]) * sin(theta[1])) - (sin(theta[0]) * sin(theta[2]) * cos(theta[3]));float a12 = (cos(theta[0]) * sin(theta[1]) * cos(theta[3])) - (cos(theta[0]) * cos(theta[1]) * cos(theta[2]) * sin(theta[3])) + (sin(theta[0]) * sin(theta[2]) * sin(theta[3]));float a13 = ((-1) * cos(theta[0]) * cos(theta[1]) * sin(theta[2])) - (sin(theta[0]) * cos(theta[2]));float a14 = ((-1) * cos(theta[0]) * sin(theta[1]) * d3);float a21 = (sin(theta[0]) * cos(theta[1]) * cos(theta[2]) * cos(theta[3])) + (sin(theta[0]) * sin(theta[1]) * sin(theta[3])) + (cos(theta[0]) * sin(theta[2]) * cos(theta[3]));float a22 = ((-1) * sin(theta[0]) * cos(theta[1]) * cos(theta[2]) * sin(theta[3])) + (sin(theta[0]) * sin(theta[1]) * cos(theta[3])) - (sin(theta[2]) * sin(theta[3]) * cos(theta[0]));float a23 = ((-1) * sin(theta[0]) * cos(theta[1]) * sin(theta[2])) + (cos(theta[0]) * cos(theta[2]));float a24 = ((-1) * sin(theta[0]) * sin(theta[1]) * d3);float a31 = (sin(theta[1]) * cos(theta[2]) * cos(theta[3])) - (sin(theta[3]) * cos(theta[1]));float a32 = ((-1) * sin(theta[1]) * cos(theta[2]) * sin(theta[3])) - (cos(theta[1]) * cos(theta[3]));float a33 = ((-1) * sin(theta[1]) * sin(theta[2]));float a34 = (cos(theta[1]) * d3) + d1;float a41 = 0;float a42 = 0;float a43 = 0;float a44 = 1;float b11 = (cos(theta[4]) * cos(theta[5]) * cos(theta[6])) - (sin(theta[4]) * sin(theta[6]));float b12 = ((-1) * cos(theta[4]) * cos(theta[5]) * sin(theta[6])) - (sin(theta[4]) * cos(theta[6]));float b13 = ((-1) * cos(theta[4]) * sin(theta[5]));float b14 = ((-1) * cos(theta[4]) * sin(theta[5]) * d7);float b21 = (sin(theta[5]) * cos(theta[6]));float b22 = ((-1) * sin(theta[5]) * sin(theta[6]));float b23 = cos(theta[5]);float b24 = (cos(theta[5]) * d7) + d5;float b31 = ((-1) * sin(theta[4]) * cos(theta[5]) * cos(theta[6])) - (cos(theta[4]) * sin(theta[6]));float b32 = (sin(theta[4]) * cos(theta[5]) * sin(theta[6])) - (cos(theta[4]) * cos(theta[6]));float b33 = sin(theta[4]) * sin(theta[5]);float b34 = sin(theta[4]) * sin(theta[5]) * d7;float b41 = 0;float b42 = 0;float b43 = 0;float b44 = 1;float c11 = (a11 * b11) + (a12 * b21) + (a13 * b31) + (a14 * b41);float c12 = (a11 * b12) + (a12 * b22) + (a13 * b32) + (a14 * b42);float c13 = (a11 * b13) + (a12 * b23) + (a13 * b33) + (a14 * b43);float c14 = (a11 * b14) + (a12 * b24) + (a13 * b34) + (a14 * b44);float c21 = (a21 * b11) + (a22 * b21) + (a23 * b31) + (a24 * b41);float c22 = (a21 * b12) + (a22 * b22) + (a23 * b32) + (a24 * b42);float c23 = (a21 * b13) + (a22 * b23) + (a23 * b33) + (a24 * b43);float c24 = (a21 * b14) + (a22 * b24) + (a23 * b34) + (a24 * b44);float c31 = (a31 * b11) + (a32 * b21) + (a33 * b31) + (a34 * b41);float c32 = (a31 * b12) + (a32 * b22) + (a33 * b32) + (a34 * b42);float c33 = (a31 * b13) + (a32 * b23) + (a33 * b33) + (a34 * b43);float c34 = (a31 * b14) + (a32 * b24) + (a33 * b34) + (a34 * b44);float c41 = (a41 * b11) + (a42 * b21) + (a43 * b31) + (a44 * b41);float c42 = (a41 * b12) + (a42 * b22) + (a43 * b32) + (a44 * b42);float c43 = (a41 * b13) + (a42 * b23) + (a43 * b33) + (a44 * b43);float c44 = (a41 * b14) + (a42 * b24) + (a43 * b34) + (a44 * b44);ROS_INFO("I heard: [%s],position: [%f],[%s],position: [%f][%s],position: [%f][%s],position: [%f][%s],position: [%f][%s],position: [%f][%s],position: [%f]",msg->name[0].c_str(),msg->position[0],msg->name[1].c_str(),msg->position[1],msg->name[2].c_str(),msg->position[2],msg->name[3].c_str(),msg->position[3],msg->name[4].c_str(),msg->position[4],msg->name[5].c_str(),msg->position[5],msg->name[6].c_str(),msg->position[6]	);ROS_INFO("[%f] [%f] [%f]",c11,c12,c13);ROS_INFO("[%f] [%f] [%f]",c21,c22,c23);ROS_INFO("[%f] [%f] [%f]",c31,c32,c33);ROS_INFO("Px:[%f] Py:[%f] Pz:[%f]", c14, c24, c34);}int main(int argc, char** argv)
{ros::init(argc, argv, "joint_state_subscriber_sy");ros::NodeHandle n;ros::Subscriber joint_state_sub1 = n.subscribe("joint_states", 10, personInfoCallback);ros::spin();return 0;
}

此处可以参考:
https://www.freesion.com/article/7089625820/

6.最终效果和总结

启动launch文件后可以看到模型在Rviz中按照发布的关节角信息连续不断运动,终端显示发布的关节角度和对应的末端位姿。
在这里插入图片描述
在终端单独运行订阅者和发布者时,会看到发布订阅连续变化的关节角度,但在Rviz中显示时,模型每次运动完成后会回到起始位置,原因暂时没有搞清楚。

正运动学求解末端位姿处应该有更好的方法,可以参考:
https://blog.csdn.net/weixin_37834269/article/details/111183412

本篇没有使用MoveIt,后续会继续学习。

过了几天再次launch发现不会出现回到起始位置的情况了,很奇怪。
在这里插入图片描述


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

相关文章

【自己动手写CPU】加载存储指令的实现

目标 修改之前一直做测试的sopc&#xff0c;为其添加数据RAM&#xff0c;测试一般加载指令的实现&#xff0c;加入特殊加载存储指令。 探讨由于加载指令引起的load相关问题&#xff0c;给出OpenMIPS的解决方法&#xff0c;验证解决效果。 加载存储指令说明 31-2625-2120-161…

自己动手写CPU之第九阶段(2)——载入存储指令说明2(lwl、lwr)

将陆续上传新书《自己动手写CPU》。今天是第38篇&#xff0c;我尽量每周四篇&#xff0c;可是近期已经非常久没有实现这个目标了。一直都有事&#xff0c;不好意思哈。 开展晒书评送书活动&#xff0c;在亚马逊、京东、当当三大图书站点上&#xff0c;发表《自己动手写CPU》书评…

LWR--local weighted regression

转自http://www.cnblogs.com/jeromeblog/p/3396486.html 简单回顾一下线性回归。我们使用了如下变量&#xff1a; x —输入变量/特征&#xff1b; y —目标变量&#xff1b; (x,y) —单个训练样本&#xff1b; m —训练集中的样本数目&#xff1b; n —特征维度&#xff1b; (x…

局部加权回归(LWR) Matlab模板

将百度文库上一份局部加权回归的代码&#xff0c;将其改为模板以便复用。 q2x,q2y为数据集&#xff0c;是n*1的矩阵&#xff1b; r是波长参数&#xff0c;就是对于距离的惩罚力度&#xff1b; q_x是要拟合的数据横坐标&#xff0c;是1*n的矩阵&#xff1b; 得到的q_y即为所求坐…

自己动手写CPU之第九阶段(2)——加载存储指令说明2(lwl、lwr)

将陆续上传新书《自己动手写CPU》&#xff0c;今天是第38篇&#xff0c;我尽量每周四篇&#xff0c;但是最近已经很久没有实现这个目标了&#xff0c;一直都有事&#xff0c;不好意思哈。 开展晒书评送书活动&#xff0c;在亚马逊、京东、当当三大图书网站上&#xff0c;发表…

1.3 欠/过拟合,局部加权回归(Loess/LWR)及Python实现(基于随机梯度下降)

import numpy as np import matplotlib.pyplot as plt #定义一个正态分布&#xff0c;参数分别为均值&#xff0c;方差以及X的行向量 def guassianDistribution(mean,var,x):return 1/np.sqrt( 2 * np.pi * var )*np.exp( - (x[1]-mean) ** 2 / (2*var) ) #定义权值计算函数&am…

ubuntu18.04 编译rtt-lwr

https://rtt-lwr.readthedocs.io/en/latest/install/install-18.04-melodic.html 一路通过。 coundt find AF_INET address #4 Open roboticsai opened this issue on Mar 22, 2018 2 comments Comments roboticsai commented on Mar 22, 2018 after i run the rttlua-gnu…

LWR服务管理框架

详细接口文档地址:https://www.showdoc.cc/lwr2 目前支持微信版本&#xff1a;最新版。 主要介绍开发接口 2.0暂时支持tcp和http开发&#xff0c;两者传输json数据是一样的。如下介绍&#xff1a; 1.请求Lwr框架的数据内容如下 {"serverKey": "软件上设置的…

基于物理信息深度学习的交通状态估计:以LWR和CTM模型为例

1.文章信息 本次介绍的文章是2022年发表在IEEE Open Journal of Intelligent Transportation Systems的一篇名为《Physics-Informed Deep Learning for Traffic State Estimation: Illustrations With LWR and CTM Models》的文章&#xff0c;该文章应用物理信息深度学习方法估…

机器学习实战--局部加权线性回归(LWR)

一 概述 通常情况下的线性拟合不能很好地预测所有的值&#xff0c;因为它容易导致欠拟合&#xff08;under fitting&#xff09;&#xff0c;比如数据集是 一个钟形的曲线。而多项式拟合能拟合所有数据&#xff0c;但是在预测新样本的时候又会变得很糟糕&#xff0c;因为它导…

机器学习与算法(8)--局部加权学习算法(LWR)

局部加权学习算法&#xff08;LWR&#xff09; 局部加权回归&#xff08;LWR&#xff09;是非参数学习方法。 首先参数学习方法是这样一种方法&#xff1a;在训练完成所有数据后得到一系列训练参数&#xff0c;然后根据训练参数来预测新样本的值&#xff0c;这时不再依赖之前的…

局部加权回归

通常情况下的线性拟合不能很好地预测所有的值&#xff0c;因为它容易导致欠拟合&#xff08;under fitting&#xff09;&#xff0c;比如数据集是 一个钟形的曲线。而多项式拟合能拟合所有数据&#xff0c;但是在预测新样本的时候又会变得很糟糕&#xff0c;因为它导致数据的 …

冲击波理论

冲击波理论 冲击波理论&#xff08;the kinematic wave theory&#xff0c;也称LWR理论&#xff09;最初是由Lighthill, M. J和Whitham, G. B. 以及Richards, P. I. 于上世纪50年代提出的。该理论假设车流是一种类似于水流的运动&#xff0c;可以通过流量、密度和速度之间的关…

IOS捷径|九宫格切图工具 分享

还在为切九宫格图片找来找去找不到好工具而烦恼? 快使用九宫格切图快捷指令&#xff0c;5秒切出你想要的效果 为保障更好的切图效果&#xff0c;轻使用正方形图片参与切图&#xff0c;如没有&#xff0c;也请尽量裁剪出正方形图片再参与切图 支持22、23、3*3 多种组合方式 …

canvas切割原图为九宫格图片

originUrl 图片原地址cWidth 生成图片的宽度cHeight 生成图片的高度top 第一条切割线距离原图片顶部的距离bottom 第二条切割线距离原图片底部的距离left 第三条切割线距离原图片左侧的距离right 第四条切割线距离原图片右侧的距离 切割 效果图 index.html <!DOCTYPE html…

Unity的UGUI用TexturePacker全自动打图集,包括九宫格切图信息

Unity的UGUI用TexturePacker全自动打图集&#xff0c;包括九宫格切图信息 前言环境准备实现过程注意总结版权声明 前言 最近在学习UGUI的打图集&#xff0c;之前一直在用SpritePacker和Sprite Atlas打图集&#xff0c;现在记录下另一种打图集方式&#xff1a;TexturePacker 主…

NGUI 九宫格切图

UISprite 的 Type 选择 Sliced 选择Edit 中的 Border

关于9宫格拼图,C++

该文章均为个人编写&#xff0c;如有错误&#xff0c;欢迎各位友友点评&#xff01; 一个简单的小游戏&#xff0c;使用到指针及一二维数组&#xff0c;相关知识可访问&#xff1a; 指针&#xff1a;https://blog.csdn.net/qiu___nan/article/details/127054411。 数组&…

html用九张图片做出九宫图,用ps如何将九张照片做成九宫格?

如何用PS将九张照片做成九宫格&#xff0c;并且随时可以更换呢。下面跟搞设计一起来做一下吧。 ↑ 首先准备好九张照片 ↑ 打开PS&#xff0c;新建一个1000x1000的文件 ↑ 选择矩形工具 ↑ 创建一个300x300的矩形 ↑ 随便填个颜色&#xff0c;把图层命名为1 ↑ 选择视图-新建参…