ROS2与Rviz2的贪吃蛇代码学习
【摘要】
参考网址:
github.com/Desperationis/rviz-snake/tree/v1.1.0
端午不休,学习代码。
官方效果如下(引用):
ROS2 humble
编译全过程:
zhangrelay@LAPTOP-5REQ7K1L:~/ros_ws$ cd rviz...
参考网址:
github.com/Desperationis/rviz-snake/tree/v1.1.0
端午不休,学习代码。
官方效果如下(引用):
ROS2 humble
编译全过程:
-
zhangrelay@LAPTOP-5REQ7K1L:~/ros_ws$ cd rviz_snake/
-
zhangrelay@LAPTOP-5REQ7K1L:~/ros_ws/rviz_snake$ colcon build
-
Starting >>> snake_publisher
-
-- The C compiler identification is GNU 11.2.0
-
-- The CXX compiler identification is GNU 11.2.0
-
-- Detecting C compiler ABI info
-
-- Detecting C compiler ABI info - done
-
-- Check for working C compiler: /usr/bin/cc - skipped
-
-- Detecting C compile features
-
-- Detecting C compile features - done
-
-- Detecting CXX compiler ABI info
-
-- Detecting CXX compiler ABI info - done
-
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-
-- Detecting CXX compile features
-
-- Detecting CXX compile features - done
-
-- Found ament_cmake: 1.3.2 (/opt/ros/humble/share/ament_cmake/cmake)
-
-- Found Python3: /usr/bin/python3.10 (found version "3.10.4") found components: Interpreter
-
-- Found rclcpp: 16.0.1 (/opt/ros/humble/share/rclcpp/cmake)
-
-- Found rosidl_generator_c: 3.1.3 (/opt/ros/humble/share/rosidl_generator_c/cmake)
-
-- Found rosidl_adapter: 3.1.3 (/opt/ros/humble/share/rosidl_adapter/cmake)
-
-- Found rosidl_generator_cpp: 3.1.3 (/opt/ros/humble/share/rosidl_generator_cpp/cmake)
-
-- Using all available rosidl_typesupport_c: rosidl_typesupport_fastrtps_c;rosidl_typesupport_introspection_c
-
-- Using all available rosidl_typesupport_cpp: rosidl_typesupport_fastrtps_cpp;rosidl_typesupport_introspection_cpp
-
-- Found rmw_implementation_cmake: 6.1.0 (/opt/ros/humble/share/rmw_implementation_cmake/cmake)
-
-- Found rmw_fastrtps_cpp: 6.2.1 (/opt/ros/humble/share/rmw_fastrtps_cpp/cmake)
-
-- Found OpenSSL: /usr/lib/x86_64-linux-gnu/libcrypto.so (found version "3.0.2")
-
-- Found FastRTPS: /opt/ros/humble/include
-
-- Using RMW implementation 'rmw_fastrtps_cpp' as default
-
-- Found visualization_msgs: 4.2.2 (/opt/ros/humble/share/visualization_msgs/cmake)
-
-- Found Curses: /usr/lib/x86_64-linux-gnu/libcurses.so
-
-- Found ament_lint_auto: 0.12.4 (/opt/ros/humble/share/ament_lint_auto/cmake)
-
-- Added test 'copyright' to check source files copyright and LICENSE
-
-- Added test 'cppcheck' to perform static code analysis on C / C++ code
-
-- Configured cppcheck include dirs: /home/zhangrelay/ros_ws/rviz_snake/src/snake_publisher/include/
-
-- Configured cppcheck exclude dirs and/or files:
-
-- Added test 'cpplint' to check C / C++ code against the Google style
-
-- Configured cpplint exclude dirs and/or files:
-
-- Added test 'lint_cmake' to check CMake code style
-
-- Added test 'uncrustify' to check C / C++ code style
-
-- Configured uncrustify additional arguments:
-
-- Added test 'xmllint' to check XML markup files
-
-- Configuring done
-
-- Generating done
-
-- Build files have been written to: /home/zhangrelay/ros_ws/rviz_snake/build/snake_publisher
-
[ 50%] Building CXX object CMakeFiles/publisher.dir/src/main.cpp.o
-
[100%] Linking CXX executable publisher
-
[100%] Built target publisher
-
-- Install configuration: ""
-
-- Installing: /home/zhangrelay/ros_ws/rviz_snake/install/snake_publisher/lib/snake_publisher/publisher
-
-- Set runtime path of "/home/zhangrelay/ros_ws/rviz_snake/install/snake_publisher/lib/snake_publisher/publisher" to ""
-
-- Installing: /home/zhangrelay/ros_ws/rviz_snake/install/snake_publisher/share/ament_index/resource_index/package_run_dependencies/snake_publisher
-
-- Installing: /home/zhangrelay/ros_ws/rviz_snake/install/snake_publisher/share/ament_index/resource_index/parent_prefix_path/snake_publisher
-
-- Installing: /home/zhangrelay/ros_ws/rviz_snake/install/snake_publisher/share/snake_publisher/environment/ament_prefix_path.sh
-
-- Installing: /home/zhangrelay/ros_ws/rviz_snake/install/snake_publisher/share/snake_publisher/environment/ament_prefix_path.dsv
-
-- Installing: /home/zhangrelay/ros_ws/rviz_snake/install/snake_publisher/share/snake_publisher/environment/path.sh
-
-- Installing: /home/zhangrelay/ros_ws/rviz_snake/install/snake_publisher/share/snake_publisher/environment/path.dsv
-
-- Installing: /home/zhangrelay/ros_ws/rviz_snake/install/snake_publisher/share/snake_publisher/local_setup.bash
-
-- Installing: /home/zhangrelay/ros_ws/rviz_snake/install/snake_publisher/share/snake_publisher/local_setup.sh
-
-- Installing: /home/zhangrelay/ros_ws/rviz_snake/install/snake_publisher/share/snake_publisher/local_setup.zsh
-
-- Installing: /home/zhangrelay/ros_ws/rviz_snake/install/snake_publisher/share/snake_publisher/local_setup.dsv
-
-- Installing: /home/zhangrelay/ros_ws/rviz_snake/install/snake_publisher/share/snake_publisher/package.dsv
-
-- Installing: /home/zhangrelay/ros_ws/rviz_snake/install/snake_publisher/share/ament_index/resource_index/packages/snake_publisher
-
-- Installing: /home/zhangrelay/ros_ws/rviz_snake/install/snake_publisher/share/snake_publisher/cmake/snake_publisherConfig.cmake
-
-- Installing: /home/zhangrelay/ros_ws/rviz_snake/install/snake_publisher/share/snake_publisher/cmake/snake_publisherConfig-version.cmake
-
-- Installing: /home/zhangrelay/ros_ws/rviz_snake/install/snake_publisher/share/snake_publisher/package.xml
-
Finished <<< snake_publisher [30.4s]
rviz2中的蛇游戏;这是为了好玩,作为 ROS2 的介绍。
要求
目前,这仅rclcpp针对 ROS2 Galactic/Humble 进行了测试,尽管它很可能在任何稍旧的设备上都可以正常工作。此外,您需要安装 rviz2 和 ncurses(用于用户输入),通过sudo apt-get install libncurses-dev.
运行
首先,配置 ros2 环境。
- 通过进入根目录并运行colcon build.
- 源项目通过source install/setup.bash.
- 通过运行游戏ros2 run snake_publisher publisher。
- 在单独的终端中,运行rviz2 src/snake_publisher/rvizSetup.rviz.
这样,游戏就应该运行了。输入是在ros2 run运行的终端上进行的。
配置
目前实现了以下节点参数:
game_fps- 游戏更新的速率。
input_fps- 从队列中处理输入的速率。
snake_color_*- 蛇在其 RGB 组件中的颜色。
fruit_color_*- RGB 成分中水果的颜色。
grid_color_*- 网格在其 RGB 组件中的颜色。
限制/错误
没有提供启动文件(将来可以制作一个)
如果足够好来填满整个棋盘,游戏就会崩溃。
一段时间后,消息会慢慢延迟明显的数量;只需重新启动 rviz2 或关闭并打开标记显示(不要问为什么会这样)
核心代码:
-
#ifndef SNAKEGAME_HPP
-
#define SNAKEGAME_HPP
-
-
#include <memory>
-
#include <thread>
-
#include <functional>
-
#include <chrono>
-
#include <vector>
-
#include <ncurses.h>
-
#include <csignal>
-
#include <list>
-
#include "rclcpp/rclcpp.hpp"
-
#include "rclcpp/executor.hpp"
-
#include "visualization_msgs/msg/marker.hpp"
-
#include "geometry_msgs/msg/point.hpp"
-
#include "std_msgs/msg/color_rgba.hpp"
-
#include "Markers.hpp"
-
-
using namespace std::chrono_literals;
-
-
/**
-
* Snake.hpp
-
*
-
* This files holds classes that directly relate to running "Snake". Everything
-
* is managed interally as simply points, and only get turned into cubes the
-
* moment they are rendered; This allows for multiple possible ways to render
-
* the game.
-
*/
-
-
-
/**
-
* Details the color of each game piece.
-
*/
-
struct GamePieceColors {
-
std_msgs::msg::ColorRGBA snakeColor;
-
std_msgs::msg::ColorRGBA fruitColor;
-
std_msgs::msg::ColorRGBA gridColor;
-
};
-
-
-
/**
-
* Wrapper for GridMarker so that origin is at the topleft, x-axis is positive
-
* to the right, y-axis is positive downwards, and the grid is drawn. This is
-
* where points are turned into cubes.
-
*/
-
class SnakeGrid {
-
public:
-
enum GRID_PIECES {EMPTY, SNAKE, FRUIT};
-
private:
-
int sideLength;
-
int worldXOffset, worldYOffset;
-
std::vector<std::vector<GRID_PIECES>> gridElements;
-
public:
-
/**
-
* Creates a grid that is at maximum size `sideLength` on each side.
-
*/
-
SnakeGrid(int sideLength) {
-
this->sideLength = sideLength;
-
worldXOffset = -sideLength / 2;
-
worldYOffset = -sideLength / 2;
-
-
for(int i = 0; i < sideLength; i++) {
-
gridElements.push_back(std::vector<GRID_PIECES>());
-
for(int j = 0; j < sideLength; j++) {
-
gridElements[i].push_back(EMPTY);
-
}
-
}
-
}
-
-
/**
-
* Determine whether a point is within the bounds of [0, side_length).
-
*/
-
bool InBounds(int x, int y) {
-
return x < sideLength && x >= 0 &&
-
y < sideLength && y >= 0;
-
-
}
-
-
/**
-
* Reserve a specific spot to be drawn as a snake in the next frame, given
-
* that it is in bounds. If it is not in bounds, nothing will happen.
-
*/
-
void ReserveSnake(int x, int y) {
-
if (this->InBounds(x, y)) {
-
gridElements[y][x] = SNAKE;
-
}
-
}
-
-
/**
-
* Reserve a specific spot to be drawn as a fruit in the next frame, given
-
* that it is in bounds. If it is not in bounds, nothing will happen.
-
*/
-
void ReserveFruit(int x, int y) {
-
if (this->InBounds(x, y)) {
-
gridElements[y][x] = FRUIT;
-
}
-
}
-
-
/**
-
* Get the type of piece that is currently reserved. By default, every
-
* square on the grid is reserved EMPTY on every frame.
-
*/
-
GRID_PIECES GetReserved(int x, int y) {
-
return gridElements[y][x];
-
}
-
-
/**
-
* Returns the side length of the grid.
-
*/
-
int GetSideLength() {
-
return sideLength;
-
}
-
-
/**
-
* Draws the grid by iterating through all reserved portions and drawing a
-
* specific cube based on its type. For SNAKE and FRUIT, the square is
-
* elevated by 1 unit in order to give the apperance of 3D.
-
*/
-
void Draw(std::shared_ptr<MarkerPublisher> publisher, GamePieceColors colors) {
-
GridMarker grid;
-
for(size_t i = 0; i < gridElements.size(); i++) {
-
for(size_t j = 0; j < gridElements[i].size(); j++) {
-
GRID_PIECES type = gridElements[i][j];
-
Cube cube;
-
cube.SetPos(i + worldXOffset, j + worldYOffset, 1);
-
-
switch(type) {
-
case EMPTY:
-
cube.SetPos(i + worldXOffset, j + worldYOffset, 0);
-
cube.color = colors.gridColor;
-
break;
-
case SNAKE:
-
cube.color = colors.snakeColor;
-
break;
-
case FRUIT:
-
cube.color = colors.fruitColor;
-
break;
-
};
-
-
grid.AddCube(cube);
-
}
-
}
-
-
publisher->PublishMarker(grid);
-
}
-
-
/**
-
* Completely clears the grid with EMPTY tiles.
-
*/
-
void Clear() {
-
for(int i = 0; i < sideLength; i++) {
-
for(int j = 0; j < sideLength; j++) {
-
gridElements[i][j] = EMPTY;
-
}
-
}
-
}
-
};
-
-
struct Fruit {
-
Fruit(int x, int y) {
-
p.x = x;
-
p.y = y;
-
}
-
-
geometry_msgs::msg::Point p;
-
};
-
-
/**
-
* Manager for controlling the spawning and erasing of fruits on the board.
-
*/
-
class FruitManager {
-
public:
-
FruitManager() {
-
requestedFruit = 0;
-
}
-
-
/**
-
* Randomly spawn a single fruit that is not occupied by a SNAKE tile.
-
* TODO: Prevent this from being an infinite loop should a player good
-
* enough completely fill up the board.
-
*/
-
void SpawnFruit(SnakeGrid snakeGrid) {
-
while(requestedFruit > 0) {
-
int x = rand() % snakeGrid.GetSideLength();
-
int y = rand() % snakeGrid.GetSideLength();
-
-
if(snakeGrid.GetReserved(x, y) == SnakeGrid::EMPTY) {
-
fruits.push_back(Fruit(x, y));
-
snakeGrid.ReserveFruit(x, y);
-
requestedFruit--;
-
}
-
}
-
}
-
-
/**
-
* Request a single fruit to be drawn the next frame. Can be called
-
* multiple times for multiple fruits.
-
*/
-
void RequestFruit() {
-
requestedFruit++;
-
}
-
-
/**
-
* Try to eat a fruit at a specific point. If there is not fruit at that
-
* point, return false. If there is, return true and erase the fruit from
-
* existence.
-
*/
-
bool Eat(int x, int y) {
-
for(size_t i = 0; i < fruits.size(); i++) {
-
auto fruit = fruits[i];
-
if(fruit.p.x == x && fruit.p.y == y) {
-
fruits.erase(fruits.begin() + i);
-
return true;
-
}
-
}
-
-
return false;
-
}
-
-
/**
-
* Get the list of points that occupy fruits.
-
*/
-
std::vector<Fruit> GetFruits() {
-
return fruits;
-
}
-
-
-
private:
-
std::vector<Fruit> fruits;
-
-
// This variable holds the amount of requested fruit that should be spawned
-
// on the next frame.
-
int requestedFruit;
-
};
-
-
/**
-
* The Snake. Body is completely represented by points.
-
*/
-
class Snake {
-
public:
-
enum DIRECTION {LEFT, RIGHT, UP, DOWN};
-
-
Snake(int x, int y) {
-
Respawn(x, y);
-
}
-
-
std::list<geometry_msgs::msg::Point> GetBody() {
-
return body;
-
}
-
-
/**
-
* Updates the snake by a single frame. Essentially, it determines when it
-
* dies, how to move, and how to eat fruits.
-
*/
-
void Update(SnakeGrid& snakeGrid, FruitManager& fruitManager) {
-
if(!dead) {
-
geometry_msgs::msg::Point nextPoint;
-
nextPoint = body.front();
-
switch(currentDirection) {
-
case LEFT:
-
nextPoint.x -= 1;
-
break;
-
case RIGHT:
-
nextPoint.x += 1;
-
break;
-
case UP:
-
nextPoint.y -= 1;
-
break;
-
case DOWN:
-
nextPoint.y += 1;
-
break;
-
};
-
actualDirection = currentDirection;
-
-
dead = WillDie(nextPoint.x, nextPoint.y, snakeGrid);
-
-
if(!dead) {
-
body.push_front(nextPoint);
-
-
bool fruitEaten = fruitManager.Eat(nextPoint.x, nextPoint.y);
-
-
if(!fruitEaten) {
-
body.pop_back();
-
}
-
else {
-
fruitManager.RequestFruit();
-
}
-
}
-
}
-
}
-
-
void SetDirection(DIRECTION dir) {
-
currentDirection = dir;
-
}
-
-
DIRECTION GetDirection() {
-
return currentDirection;
-
}
-
-
DIRECTION GetActualDirection() {
-
return actualDirection;
-
}
-
-
bool IsDead() {
-
return dead;
-
}
-
-
/**
-
* Checks whether or not the head is out of bounds our intersected its own
-
* body to determine if it died.
-
*/
-
bool WillDie(int x, int y, SnakeGrid& snakeGrid) {
-
if(!snakeGrid.InBounds(x, y))
-
return true;
-
-
for(auto point : body) {
-
if (point.x == x && point.y == y) {
-
return true;
-
}
-
}
-
-
return false;
-
}
-
-
/**
-
* Respawn the snake at a specific point.
-
*/
-
void Respawn(int x, int y) {
-
body.clear();
-
-
geometry_msgs::msg::Point p;
-
p.x = x;
-
p.y = y;
-
body.push_front(p);
-
dead = false;
-
-
currentDirection = RIGHT;
-
}
-
-
private:
-
// First element is head, final element is tail.
-
std::list<geometry_msgs::msg::Point> body;
-
bool dead;
-
DIRECTION currentDirection;
-
-
// User input and the game are asynchronous as run at different hertz, so
-
// this gives the direction the snake is headed based on the last frame so
-
// that the user doesn't circumnavigate the input-checking code to prevent
-
// crashing directly backwards.
-
DIRECTION actualDirection;
-
};
-
-
-
/**
-
* ROS Node that actually runs the game. Due to not being able to create a
-
* standard game loop with a delay, this node runs the loop via wall timers and
-
* asynchronously receives input from the terminal via ncurses.
-
*/
-
class GameNode : public rclcpp::Node {
-
private:
-
void OnGameFPSChange(const rclcpp::Parameter& p) {
-
SetGameFPS(p.as_int());
-
}
-
-
void OnInputFPSChange(const rclcpp::Parameter& p) {
-
SetInputFPS(p.as_int());
-
}
-
-
void SetGameFPS(int FPS) {
-
int millisecondsToDelay = static_cast<int>(1000.0 / FPS);
-
auto duration = std::chrono::duration<int, std::milli>(millisecondsToDelay);
-
this->renderTimer = this->create_wall_timer(duration, std::bind(&GameNode::Loop, this));
-
}
-
-
void SetInputFPS(int FPS) {
-
int millisecondsToDelay = static_cast<int>(1000.0 / FPS);
-
auto duration = std::chrono::duration<int, std::milli>(millisecondsToDelay);
-
this->inputTimer = this->create_wall_timer(duration, std::bind(&GameNode::UserInput, this));
-
}
-
-
public:
-
GameNode(std::shared_ptr<MarkerPublisher> publisher) : Node("game_node"), snake(5,5), snakeGrid(15) {
-
snake.SetDirection(Snake::RIGHT);
-
-
this->declare_parameter("game_fps", 12);
-
this->declare_parameter("input_fps", 100);
-
-
this->declare_parameter("snake_color_r", 0);
-
this->declare_parameter("snake_color_g", 255);
-
this->declare_parameter("snake_color_b", 0);
-
-
this->declare_parameter("fruit_color_r", 255);
-
this->declare_parameter("fruit_color_g", 0);
-
this->declare_parameter("fruit_color_b", 0);
-
-
this->declare_parameter("grid_color_r", 255);
-
this->declare_parameter("grid_color_g", 255);
-
this->declare_parameter("grid_color_b", 255);
-
-
this->SetGameFPS(get_parameter("game_fps").as_int());
-
this->SetInputFPS(get_parameter("input_fps").as_int());
-
-
// Handle parameter changes
-
paramSubscriber = std::make_shared<rclcpp::ParameterEventHandler>(this);
-
gameFPSHandle = paramSubscriber->add_parameter_callback("game_fps", std::bind(&GameNode::OnGameFPSChange, this, std::placeholders::_1));
-
inputFPSHandle = paramSubscriber->add_parameter_callback("input_fps", std::bind(&GameNode::OnInputFPSChange, this, std::placeholders::_1));
-
-
this->publisher = publisher;
-
-
fruitManager.RequestFruit();
-
fruitManager.SpawnFruit(snakeGrid);
-
}
-
-
/**
-
* Game loop.
-
*/
-
void Loop() {
-
snake.Update(snakeGrid, fruitManager);
-
for(auto body : snake.GetBody()) {
-
snakeGrid.ReserveSnake(body.x, body.y);
-
}
-
-
fruitManager.SpawnFruit(snakeGrid);
-
for(auto fruit : fruitManager.GetFruits()) {
-
snakeGrid.ReserveFruit(fruit.p.x, fruit.p.y);
-
}
-
-
snakeGrid.Draw(publisher, GetColors());
-
snakeGrid.Clear();
-
}
-
-
/**
-
* Sole place for handling user input.
-
*/
-
void UserInput() {
-
int c = getch();
-
auto currentDirection = snake.GetActualDirection();
-
if (c != -1) {
-
if(c == 'w' && currentDirection != Snake::DOWN)
-
snake.SetDirection(Snake::UP);
-
if(c == 'a' && currentDirection != Snake::RIGHT)
-
snake.SetDirection(Snake::LEFT);
-
if(c == 's' && currentDirection != Snake::UP)
-
snake.SetDirection(Snake::DOWN);
-
if(c == 'd' && currentDirection != Snake::LEFT)
-
snake.SetDirection(Snake::RIGHT);
-
if(c == '\n')
-
snake.Respawn(5,5);
-
}
-
}
-
-
GamePieceColors GetColors() {
-
GamePieceColors colors;
-
colors.snakeColor.r = this->get_parameter("snake_color_r").as_int() / 255;
-
colors.snakeColor.g = this->get_parameter("snake_color_g").as_int() / 255;
-
colors.snakeColor.b = this->get_parameter("snake_color_b").as_int() / 255;
-
colors.snakeColor.a = 1;
-
-
colors.gridColor.r = this->get_parameter("grid_color_r").as_int() / 255;
-
colors.gridColor.g = this->get_parameter("grid_color_g").as_int() / 255;
-
colors.gridColor.b = this->get_parameter("grid_color_b").as_int() / 255;
-
colors.gridColor.a = 1;
-
-
colors.fruitColor.r = this->get_parameter("fruit_color_r").as_int() / 255;
-
colors.fruitColor.g = this->get_parameter("fruit_color_g").as_int() / 255;
-
colors.fruitColor.b = this->get_parameter("fruit_color_b").as_int() / 255;
-
colors.fruitColor.a = 1;
-
-
return colors;
-
}
-
-
private:
-
Snake snake;
-
SnakeGrid snakeGrid;
-
std::shared_ptr<MarkerPublisher> publisher;
-
FruitManager fruitManager;
-
-
-
rclcpp::TimerBase::SharedPtr renderTimer, inputTimer;
-
std::shared_ptr<rclcpp::ParameterEventHandler> paramSubscriber;
-
std::shared_ptr<rclcpp::ParameterCallbackHandle> gameFPSHandle;
-
std::shared_ptr<rclcpp::ParameterCallbackHandle> inputFPSHandle;
-
};
-
-
#endif
文章来源: zhangrelay.blog.csdn.net,作者:zhangrelay,版权归原作者所有,如需转载,请联系作者。
原文链接:zhangrelay.blog.csdn.net/article/details/125108123
【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)