SFML 小demo

文章目录

    • 项目搭建
    • 代码实现
      • main.cpp
      • object.h
      • snake.h
      • common.h
    • 使用 demo

做到最后的话其实就只是验证了以前自己的一个想法,但是没有做成一个真正的游戏,可以算是一个 demo 而已吧,没做游戏的界面和关卡,不过完成了核心显式机制和功能之后,其实再搭建其他的玩法数值倒是很容易的,只不过现在时间紧张,只做了最关键的可玩部分,也没有很好的封装代码,不过可以明显感觉到的是,自己比以前本科的时候更具有设计的思想了,整个框架的设计会比较清晰一些,更会考虑封装和防御性编程方面的事情。

本来是想把这个画布分成均等的网格然后做普通的贪吃蛇的,做着做着想着做好玩一点于是改成跟着鼠标移动的轨迹了……

在这里插入图片描述

项目搭建

不想重新像本科大一大二的时候一样用字符界面做黑框框的程序,所以使用一个比较好的容易上手的 2D 图像库就是很重要的事情了,一般来说分为以下几种选择:

  1. EasyX、SFML
  2. Cocos2D、SDL2
  3. OpenGL、DirectX

为了快速实现简易的项目验证,所以这里使用 SFML2.1 框架,并且重新写一个 CMakeLists.txt 来完成项目的构建,而不是使用我之前那个非常死板的 CMake 模板,为此踩了不少坑,因为 CMake 更新的也有点快,但是 CMake 的官方文档除了函数的参数解释之外,没有使用示例。这里先记录一下 CMakeLists.txt 的几个坑:

  1. CMake 的函数名是不区分大小写的,但是 CMake 的很多选项是只能大写,坑
  2. CMake 的 include_directories 不会包含最顶层的目录!从给定的目录往下开始,也就是说如果头文件都是放在 abc/ 目录下,那么 include_directories(abc/) 不能使得在代码中可以 include<abc/xxx.h> ,因为顶层目录是不包含的,如果想在引入头文件的时候带上相对路径,则头文件的根目录本身需要被 include_directories 之下,比如 include_directories<include/abc> ,也就是还需要多建立一层额外的包装目录
  3. VScode 的 C++ intellisense 基于 CMake 在配置过程中生成的一条指令来解决头文件和库目录的解析问题,所以如果 CMake 不输出这个 compile_commands.json 的话,C++ 的 intellisense 就无法正常高亮代码工作
  4. CMake 无法把外部预编译好的第三方动态库视作内部的 target 类型变量,也就是说不能 install 这些外部的动态库,当然解决办法也是有的,不过要求 CMake 的版本比较新,要达到 3.21 才有这个功能,那就是 INSTALL(IMPORTED_RUNTIME_ARTIFACTS) 如果不查文档不知道还要琢磨到什么时候,反正就是一直报错 ADD_LIBRARY(IMPORTED) 导入的外部库没有新建 target 类型变量,具体的原因和为什么 CMake 这么设计可以查看这篇 StackOverflow
  5. 为什么需要 install ?因为一般使用动态库的话,最终生成的可执行文件必须和动态库在同一个目录下才能正常运行调试
  6. FOREACH i RANGE n 不是常见编程语言那样理解的从 i 到 n-1 而真的是从 i 到 n ,问题 CMAKE 里面列表的下标又是从 0 开始的,所以如果通过这种取下标的方式同时遍历多个等长的序列的话会越界……只能傻傻地在循环中再添加一个 IF 判断 i 是不是到了 n ……

临时起意做这个项目主要是最近找实习投了一些 C++ 的岗位,但是最近因为都在写 Golang 有段时间没写 C++ 了,很多特性以及八股也都有点忘了,所以回过头来捡起 C++ 用一下吧,复习复习。另外越来越觉得智能指针的好用了,用过 Python 或者 Golang 这种高级语言带有自动垃圾回收(比如标记清楚法之类的),C++ 就不想手动写释放内存了,还是智能指针好用。

CMake 两个自己写的觉得比较有用的小函数是找外部库的路径和名字以及头文件所在的路径,返回结果列表,然后再对这些列表中的每个元素进行 INCLUDE_DIRECTORIES 或者 ADD_LIBRARY 之类的操作,很方便,这样就不用自己手动添加每个头文件路径和外部预编译动态库了。

FUNCTION(FIND_LIBS lib_dir suffix return_lib_name_list return_lib_path_list)
    UNSET(return_lib_name_list CACHE)
    UNSET(return_lib_path_list CACHE)

    FILE(
        GLOB lib_path_list
        ${lib_dir}/*.${suffix}
    )

    FOREACH(_lib_path ${lib_path_list})
        GET_FILENAME_COMPONENT(_lib_name ${_lib_path} NAME_WE)
        # MESSAGE("_lib_name ${_lib_name}")
        LIST(APPEND lib_name_list ${_lib_name})
    ENDFOREACH(_lib_path ${lib_path_list})

    # MESSAGE("lib_name_list ${lib_name_list}")
    # MESSAGE("lib_path_list ${lib_path_list}")
    SET(return_lib_name_list ${lib_name_list} PARENT_SCOPE)
    SET(return_lib_path_list ${lib_path_list} PARENT_SCOPE)
ENDFUNCTION()

FUNCTION(FIND_HDRS hdr_dir return_hdr_dir_list)
    UNSET(hdr_dir_list CACHE)
    SET(hdr_dir_list)
    FILE(
        GLOB_RECURSE hdr_path_list
        ${hdr_dir}/*.h
        ${hdr_dir}/*.hpp
    )
    SET(hdr_dir_list ${hdr_dir})
    FOREACH(_hdr_path ${hdr_path_list})
        GET_FILENAME_COMPONENT(_hdr_dir ${_hdr_path} PATH)
        LIST(APPEND hdr_dir_list ${_hdr_dir})
    ENDFOREACH(_hdr_path ${hdr_path_list})
    LIST(REMOVE_DUPLICATES hdr_dir_list)
    SET(return_hdr_dir_list ${hdr_dir_list} PARENT_SCOPE)
ENDFUNCTION()


# print list item
FUNCTION(PRINT_LIST list_item title prefix)
    IF(NOT list_item OR (list_item STREQUAL ""))
        RETURN()
    ENDIF()
    MESSAGE("┌────────────────── ${title}")
    FOREACH(item ${list_item})
        MESSAGE("│ ${prefix} ${item}")
    ENDFOREACH()
    MESSAGE("└──────────────────]\n")
ENDFUNCTION()

把这些 CMake 函数或者宏单独放到一个文件里然后通过 INCLUDE 来在根目录的 CMakeLists.txt 包含这些子文件以使用函数或者宏。

目录结构如下所示:

C:\USERS\FREDOM\DESKTOP\TEMP
├─.vscode
├─app
├─build
│  ├─.cmake
│  │  └─api
│  │      └─v1
│  │          ├─query
│  │          │  └─client-vscode
│  │          └─reply
│  ├─app
│  │  └─CMakeFiles
│  │      └─main.dir
│  ├─CMakeFiles
│  │  ├─3.27.0-rc4
│  │  │  ├─CompilerIdC
│  │  │  │  └─tmp
│  │  │  └─CompilerIdCXX
│  │  │      └─tmp
│  │  └─pkgRedirects
│  └─install
│      └─Debug
│          └─bin
├─cmake
├─include
│  └─SFML
│      ├─Audio
│      ├─Graphics
│      ├─Network
│      ├─System
│      └─Window
├─lib
└─src

PS:Windows 下自带的这个 tree 树形目录结构显式可以使用的参数太少了,不支持指定展开到第几级?输出的都是目录名,但是输出带文件名的话根本看不完

项目根目录的 CMakeLists.txt 如下:

CMAKE_MINIMUM_REQUIRED(VERSION 3.21)
PROJECT(temp)

MESSAGE("build system: ${CMAKE_SYSTEM_NAME}")
MESSAGE("${CMAKE_CURRENT_BINARY_DIR}")
SET(CMAKE_CXX_STANDARD 17)
SET(CAMKE_C_STANDARD 17)

SET(CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_BINARY_DIR}/install/${CMAKE_BUILD_TYPE})
# set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) # binary executable
# set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) # shared library
# set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) # static library

INCLUDE(cmake/macro.cmake)

# help vscode find compiler_command.json
ADD_DEFINITIONS(-DCMAKE_EXPORT_COMPILE_COMMANDS=ON)
# add_definitions(-DSFML_STATIC)

# 指定库目录
LINK_DIRECTORIES(lib)
# 包含头文件目录
FIND_HDRS(include return_hdr_dir_list)
PRINT_LIST("${return_hdr_dir_list}" "HDR_PATH" "")
FOREACH(_hdr_dir ${return_hdr_dir_list})
    INCLUDE_DIRECTORIES(${_hdr_dir})
ENDFOREACH(_hdr_dir ${return_hdr_dir_list})

FIND_LIBS(lib "dll" return_lib_name_list return_lib_path_list)
PRINT_LIST("${return_lib_name_list}" "LIB_NAME" "")
PRINT_LIST("${return_lib_path_list}" "LIB_PATH" "")


LIST(LENGTH return_lib_name_list num_lib)
FOREACH(i RANGE 0 ${num_lib})
    IF(${i} EQUAL ${num_lib})
        BREAK()
    ENDIF()
    LIST(GET return_lib_name_list ${i} _lib_name)
    LIST(GET return_lib_path_list ${i} _lib_path)
    # MESSAGE("${i} ${_lib_name} ${_lib_path}")
    ADD_LIBRARY(${_lib_name} SHARED IMPORTED)
    SET_PROPERTY(
        TARGET ${_lib_name}
        PROPERTY IMPORTED_LOCATION ${_lib_path}
    )
    # only for windows dll, that's suck
    SET_PROPERTY(
        TARGET ${_lib_name}
        PROPERTY IMPORTED_IMPLIB ${_lib_path}
    )
    INSTALL(
        IMPORTED_RUNTIME_ARTIFACTS ${_lib_name}
    )
ENDFOREACH()

# 移动动态库到 bin 文件同目录下
FOREACH(_lib ${sfmllibs})
    INSTALL(
        TARGETS ${sfmlibs}
        RUNTIME DESTINATION bin
    )
ENDFOREACH(_lib ${sfmllibs})

# 添加可执行文件
ADD_SUBDIRECTORY(app)

然后 app 目录下面的 CMakeList.txt 内容如下:

ADD_EXECUTABLE(main main.cpp)
TARGET_LINK_LIBRARIES(main ${return_lib_name_list})
INSTALL(TARGETS main RUNTIME DESTINATION bin)


# 链接库
# TARGET_LINK_LIBRARIES(
#     main
#     ${sfmllibs}
# )

不过后来发现其实 LINK_DIRECTORIES 不是很需要了,因为这次没有项目内部生成的库目标文件,都是外部动态库链接,不过为了模板具有一些普适性还是放着吧。在 VScode 里面使用快捷键 Ctrl+Shift+B 来执行构建,CMake 插件的配置过程输出如下:

[cmake] Not searching for unused variables given on the command line.
[cmake] build system: Windows
[cmake] C:/Users/fredom/Desktop/temp/build
[cmake] ┌────────────────── HDR_PATH
[cmake] │  include
[cmake] │  C:/Users/fredom/Desktop/temp/include/SFML
[cmake] │  C:/Users/fredom/Desktop/temp/include/SFML/Audio
[cmake] │  C:/Users/fredom/Desktop/temp/include/SFML/Graphics
[cmake] │  C:/Users/fredom/Desktop/temp/include/SFML/Network
[cmake] │  C:/Users/fredom/Desktop/temp/include/SFML/System
[cmake] │  C:/Users/fredom/Desktop/temp/include/SFML/Window
[cmake] │  C:/Users/fredom/Desktop/temp/include
[cmake] └──────────────────]
[cmake] 
[cmake] ┌────────────────── LIB_NAME
[cmake] │  openal32
[cmake] │  sfml-audio-d-2
[cmake] │  sfml-graphics-d-2
[cmake] │  sfml-network-d-2
[cmake] │  sfml-system-d-2
[cmake] │  sfml-window-d-2
[cmake] └──────────────────]
[cmake] 
[cmake] ┌────────────────── LIB_PATH
[cmake] │  C:/Users/fredom/Desktop/temp/lib/openal32.dll
[cmake] │  C:/Users/fredom/Desktop/temp/lib/sfml-audio-d-2.dll
[cmake] │  C:/Users/fredom/Desktop/temp/lib/sfml-graphics-d-2.dll
[cmake] │  C:/Users/fredom/Desktop/temp/lib/sfml-network-d-2.dll
[cmake] │  C:/Users/fredom/Desktop/temp/lib/sfml-system-d-2.dll
[cmake] │  C:/Users/fredom/Desktop/temp/lib/sfml-window-d-2.dll
[cmake] └──────────────────]
[cmake] 
[cmake] -- Configuring done (0.0s)
[cmake] -- Generating done (0.0s)
[cmake] -- Build files have been written to: C:/Users/fredom/Desktop/temp/build

这样一来就把 SFML 库的动态库和头文件目录都引入项目了。

代码实现

主要构想是:

  1. 画布分成均等的网格
  2. 每个网格亮起之后渐弱变暗直到完全变黑(这样比较好看……)
  3. 网格的亮度变化和可移动对象分开,计算可移动对象的位置,可移动对象移动的路径上重置网格的亮度

根据 SFML 官网的入门教程,其实画面的渲染可以做成单独线程的离屏渲染,这样性能会好很多,不过时间关系这里就没有跟着写了,其实本身代码的封装性也不强,一般来说这种小游戏 demo 怎么来说也会有个 runloop、start、stop、eventloop、drawloop 之类的分离模块。

main.cpp

#include <iostream>
#include <vector>

#include <common.h>
#include <SFML/Graphics.hpp>
#include <object.h>
#include <snake.h>

const int WIN_WID = 1000;
const int WIN_HEI = 1000;

const int NUM_ROW = 100;
const int NUM_COL = 100;

int main()
{
    // create the window
    std::shared_ptr<sf::RenderWindow> window = std::make_shared<sf::RenderWindow>(sf::VideoMode(WIN_WID, WIN_HEI), "snake");
    std::vector<ObjectDrawable> board;

    for (int i = 0; i < NUM_ROW; ++i)
    {
        for (int j = 0; j < NUM_COL; ++j)
        {
            ObjectDrawable newObj(window);
            newObj.setBoarder(WIN_WID/NUM_COL, WIN_HEI/NUM_ROW); // width, height
            newObj.setPosition(WIN_WID/NUM_COL*j, WIN_HEI/NUM_ROW*i);
            board.emplace_back(newObj);
        }
    }

    Snake snake;
    snake.extendBody();
    
    // run the program as long as the window is open
    sf::Clock clock;
    float deltaTime = 0.0;
    while (window->isOpen())
    {
        // calculate delta time elapsed
        deltaTime = clock.getElapsedTime().asSeconds();
        clock.restart();
        // std::cout << deltaTime << std::endl;

        // check all the window's events that were triggered since the last iteration of the loop
        sf::Event event;
        while (window->pollEvent(event))
        {
            // "close requested" event: we close the window
            switch (event.type)
            {
            case sf::Event::Closed:
                window->close();
                break;
            
            case sf::Event::KeyPressed:
                switch (event.key.code)
                {
                case sf::Keyboard::Up:
                    snake.changeHeadSpd(sf::Vector2f(0.0f, -5.0f));
                    break;
                case sf::Keyboard::Down:
                    snake.changeHeadSpd(sf::Vector2f(0.0f, +5.0f));
                    break;
                case sf::Keyboard::Right:
                    snake.changeHeadSpd(sf::Vector2f(+5.0f, 0.0f));
                    break;
                case sf::Keyboard::Left:
                    snake.changeHeadSpd(sf::Vector2f(-5.0f, 0.0f));
                    break;
                }
                break;

            case sf::Event::MouseButtonPressed:
                // board[event.mouseMove.y/(WIN_HEI/NUM_ROW) * NUM_COL + event.mouseMove.x/(WIN_WID/NUM_COL)].resetColor();
                snake.extendBody();
                break;
            
            case sf::Event::MouseMoved:
                const sf::Vector2f& headPos = snake.getHeadPos();
                float xdspd = (event.mouseMove.x - headPos.x) * 2e-2;
                float ydspd = (event.mouseMove.y - headPos.y) * 2e-2;
                snake.changeHeadSpd(sf::Vector2f(xdspd, ydspd));
                break;
            }
        }
        // clear the window with black color
        window->clear(sf::Color());


        // draw everything here...
        snake.update(deltaTime);
        for (const auto& item : snake.getBody())
        {
            sf::Vector2f itemPos = item->getPos();
            // std::cout << itemPos.x << ' ' << itemPos.y << std::endl;
            int boardIdx = int(itemPos.y)/(WIN_HEI/NUM_ROW) * NUM_COL + int(itemPos.x)/(WIN_WID/NUM_COL);
            if (boardIdx < 0) boardIdx = 0;
            boardIdx = boardIdx % (NUM_ROW * NUM_COL);
            board[boardIdx].resetColor();
        }

        for (ObjectDrawable& obj : board)
        {
            obj.update(deltaTime);
            obj.draw();
        }


        // end the current frame
        window->display();

        sf::sleep(sf::milliseconds(16));
    }

    return 0;
}

这里还有一个关于 C++ 的 switch 块的注意点,就是 case 中不能声明跨作用域的变量,不过由于我这里每个 case 分支里面的变量都是当前 case 使用的,没有跨 case 使用,所以编译没有报错(是这样吗?也可能是因为 MinGW 的编译支持这么做而已)。

object.h

#ifndef OBJECT_H
#define OBJECT_H

#include <common.h>
#include <algorithm>
#include <SFML/Graphics.hpp>

class ObjectDrawable
{
private:
    std::shared_ptr<sf::RenderWindow> renderWd;
    std::shared_ptr<sf::Shape> shape;
    sf::Color baseColor;
    sf::Color currColor;

    float xpos;
    float ypos;
    float brdx;
    float brdy;

public:
    ObjectDrawable(std::shared_ptr<sf::RenderWindow> _rdwp) : renderWd(_rdwp)
    {
        this->shape = std::make_shared<sf::RectangleShape>(sf::Vector2f(10.0f, 10.0f));
        // this->baseColor = sf::Color(
        //     150 + rand() % 100,
        //     150 + rand() % 100,
        //     150 + rand() % 100
        // );
        this->baseColor = sf::Color(50, 255, 50);
        this->shape->setFillColor(this->baseColor);
    }

    void setPosition(const float& _xpos, const float& _ypos)
    {
        this->xpos = _xpos;
        this->ypos = _ypos;
    }

    void setBoarder(const float& _brdx, const float& _brdy)
    {
        this->brdx = _brdx;
        this->brdy = _brdy;
    }

    void resetColor()
    {
        this->currColor = this->baseColor;
    }

    void draw()
    {
        this->shape->setPosition(this->xpos, this->ypos);
        this->shape->setFillColor(this->currColor);
        std::dynamic_pointer_cast<sf::RectangleShape>(this->shape)->setSize(sf::Vector2f(this->brdx, this->brdy));

        if (renderWd != nullptr)
            this->renderWd->draw(*(this->shape.get()));
    }

    void update(const float& dt)
    {
        this->currColor.r = std::max(0, int(this->currColor.r - dt * 100));
        this->currColor.g = std::max(0, int(this->currColor.g - dt * 100));
        this->currColor.b = std::max(0, int(this->currColor.b - dt * 100));
    }
};

class ObjectMovable
{
private:
    float xpos;
    float ypos;
    float xspd;
    float yspd;

public:
    ObjectMovable() {}
    ObjectMovable(
        const float& _xpos,
        const float& _ypos,
        const float& _xspd = 0.0f,
        const float& _yspd = 0.0f
    ) : xpos(_xpos), ypos(_ypos), xspd(_xspd), yspd(_yspd) {}

    ObjectMovable(
        const sf::Vector2f& _pos,
        const sf::Vector2f& _spd = sf::Vector2f(0.0f, 0.0f)
    ) : xpos(_pos.x), ypos(_pos.y), xspd(_spd.x), yspd(_spd.y) {}

    void update(const float& dt)
    {
        this->xpos += this->xspd * dt;
        this->ypos += this->yspd * dt;
    }

    void changePos(const float& _xpos, const float& _ypos)
    {
        this->xpos = _xpos;
        this->ypos = _ypos;
    }
    void changePos(const sf::Vector2f& _pos)
    {
        this->xpos = _pos.x;
        this->ypos = _pos.y;
    }
    sf::Vector2f getPos() const
    {
        return sf::Vector2f(this->xpos, this->ypos);
    }

    void changeSpd(const float& _xspd, const float& _yspd)
    {
        this->xspd = _xspd;
        this->yspd = _yspd;
    }
    void changeSpd(const sf::Vector2f& _spd)
    {
        this->xspd = _spd.x;
        this->yspd = _spd.y;
    }
    sf::Vector2f getSpd() const{
        return sf::Vector2f(this->xspd, this->yspd);
    }
};

#endif

Object 头文件一开始的设想是有一个纯虚基类 Object 的,但是没时间实现了,只想快速验证 demo 于是分开了两个类来写,实际上 update 这个函数应该是 Object 基类的一个空方法才对。

snake.h

#ifndef SNAKE_H
#define SNAKE_H

#include <common.h>
#include <object.h>

class Snake
{
private:
    std::vector<std::shared_ptr<ObjectMovable>> body;
    int bodyLen;

public:
    Snake()
    {
        this->body.emplace_back(
            std::make_shared<ObjectMovable>(
                0.0, 0.0, 0.0, 0.0
            )
        );
        this->bodyLen = this->body.size();
    }

    void extendBody()
    {
        this->body.emplace_back(
            std::make_shared<ObjectMovable>(
                this->body.back()->getPos(),
                this->body.back()->getSpd()
            )
        );
        bodyLen++;
    }

    void update(const float& dt)
    {
        this->body[0]->update(dt);

        for (int i = 1; i < this->bodyLen; ++i)
        {
            this->body[i]->changeSpd(
                this->body[i-1]->getPos() - this->body[i]->getPos()
            );
            this->body[i]->update(dt);
        }
    }

    void changeHeadSpd(const sf::Vector2f& dspd)
    {
        this->body[0]->changeSpd(
            this->body[0]->getSpd() + dspd
        );
    }

    const std::vector<std::shared_ptr<ObjectMovable>>& getBody() const {
        return this->body;
    }

    sf::Vector2f getHeadPos() {
        return this->body[0]->getPos();
    }
};

#endif

common.h

#ifndef COMMON_H
#define COMMON_H

#include <memory>
#include <random>

#include <vector>

#endif

使用 demo

  1. 鼠标移动来吸引可移动对象
  2. 鼠标左键添加可移动对象

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

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

相关文章

UE5-人物角色动画蓝图

这里主要从零给角色创建移动的蓝图&#xff0c;包含多种状态 创建 首先在角色骨骼网格体上右键创建动画蓝图 进入&#xff0c;在AnimGraph界面创建一个状态机&#xff08;stateMachine&#xff09; Idle 进入状态机&#xff0c;拉出来创建一个newState&#xff0c;这里命名…

【C++修行之道】类和对象(五)日期类的实现、const成员、取地址及const和取地址操作符重载

目录 一、 日期类的实现 Date.h 1.1 GetMonthDay函数&#xff08;获取某年某月的天数&#xff09; 问&#xff1a;这个函数为什么不和其他的函数一样放在Date.cpp文件中实现呢&#xff1f; 1.2 CheckDate函数&#xff08;检查日期有效性&#xff09;、Print函数&#xff08;…

手机建站介绍

随着科技的不断进步和移动互联网的普及&#xff0c;手机应用已经成为人们生活中最不可或缺的一部分。而手机建站作为一种新兴技术&#xff0c;在这一领域也有着广泛的应用。本文将为大家介绍手机建站的概念、优势和应用。 什么是手机建站&#xff1f; 手机建站是指将传统的网络…

【启明智显彩屏应用】Model3A 7寸触摸屏在真空包装机上的应用解决方案

一、项目背景与需求 随着工业自动化水平的提升&#xff0c;对真空包装机的操作界面和控制精度要求也越来越高。为满足这一需求&#xff0c;我们提出了基于Model3A工业级HMI&#xff08;人机界面&#xff09;芯片方案的7寸触摸屏解决方案&#xff0c;旨在提高真空包装机的操作便…

VSCode中使用LaTeX编辑文章

工欲善其事必先利其器&#xff0c;成功在VSCode中使用LaTeX&#xff0c;遂做记录。 1. 先准备VScode的安装 下载地址&#xff1a;VScode地址 正常安装即可&#xff0c;一路next安装下去即可。 2. 准备安装latex 国内使用清华源&#xff0c;下载地址:https://mirrors.tuna.t…

Django学习三:views业务层中通过models对实体对象进行的增、删、改、查操作。

文章目录 前言一、Django ORM介绍二、项目快速搭建三、操作1、view.pya、增加操作b、删除操作c、修改操作d、查询操作 2、urls.py 前言 上接博文&#xff1a;Django学习二&#xff1a;配置mysql&#xff0c;创建model实例&#xff0c;自动创建数据库表&#xff0c;对mysql数据…

FreeRTOS基础(十一):消息队列

本文将详细全方位的讲解FreeRTOS的消息队列&#xff0c;其实在FreeRTOS中消息队列的重要性也不言而喻&#xff0c;与FreeRTOS任务调度同等重要&#xff0c;因为后面的各种信号量基本都是基于消息队列的。 目录 一、消息队列的简介 1.1 产生的原因 1.2 消息队列的解决办法 …

Django 部署指南

部署 Django 应用程序涉及将我们的应用程序从开发环境部署到生产环境&#xff0c;并确保它可以在生产服务器上安全运行和扩展。其实了解几种部署方案&#xff0c;相信你对将来的项目更得心应手。 1、问题背景 Django 是一款流行的 Python Web 框架&#xff0c;但对于新手来说&…

618网购节,电商能挡住恶意网络爬虫的攻击吗?

目录 爬虫盗取电商数据的步骤 电商平台如何发现网络爬虫&#xff1f; 如何拦截违法网络爬虫 2023年&#xff0c;杭州中院审结了两起涉及“搬店软件”的不正当竞争案件。本案的原告是国内某大型知名电子商务平台的运营主体&#xff0c;而被告则是开发了一款名为“某搬家快速商品…

【数据结构】——线性表(顺序表)——内有代码详解

目录 一、引言 二、线性表 2.1 定义 2.2 特点 三、顺序表 3.1 顺序表的概念 3.2 顺序表的特点 3.3 顺序表的定义 3.3.1 静态定义 3.3.2 动态定义 3.4 顺序表的初始化 3.4.1 静态初始化 3.4.2 动态初始化 3.5 顺序表的销毁 3.6 顺序表元素的打印 3.7 顺序表的插入…

百度AI大底座

“百度AI大底座”是源自百度多年产业深度实践积累、结合AI全栈技术科研成果打造的国内首个全栈自研的AI基础设施&#xff0c; 面向企业和产业AI开发与应用提供端到端自主可控、自我进化的解决方案&#xff0c;能够快捷、低成本地实现“AI能力的随用随 取”。AI大底座可助力企业…

Python 学习flask创建项目

1、使用pycharm创建flask项目 2、运行访问地址 3、可以看到访问地址内容 4、可以增加路由&#xff0c;尝试访问获取参数

树莓派4B_OpenCv学习笔记4:测试摄像头_imread加载显示图像_imwrite保存图片

今日继续学习树莓派4B 4G&#xff1a;&#xff08;Raspberry Pi&#xff0c;简称RPi或RasPi&#xff09; 本人所用树莓派4B 装载的系统与版本如下: 版本可用命令 (lsb_release -a) 查询: Opencv 版本是4.5.1&#xff1a; 今日对之前的测试CSI摄像头函数进行一些理解说明&#x…

Shell脚本文本处理三剑客(grep、awk、sed)和正则表达式

一、正则表达式 1.正则表达式基础 正则表达式&#xff08;regular expression&#xff09;描述了一种字符串匹配的模式&#xff08;pattern&#xff09;&#xff0c;可以用来检查一个串是否含有某种子串&#xff0c;将匹配的子串替换或者从某个串中取出符号某个条件的子串等&…

【微信小程序】页面事件

下拉刷新 上拉触底 上拉触底距离指的是触发上拉触底事件时&#xff0c;滚动条距离页面底部的距离。 可以在全局或页面的json配置文件中&#xff0c;通过onReachBottomDistance属性来配置上拉触底的距离。 小程序默认的触底距离是50x,在实际开发中&#xff0c;可以根据自己的需…

【C++】─篇文章带你熟练掌握 map 与 set 的使用

目录 一、关联式容器二、键值对三、pair3.1 pair的常用接口说明3.1.1 [无参构造函数](https://legacy.cplusplus.com/reference/utility/pair/pair/)3.1.2 [有参构造函数 / 拷贝构造函数](https://legacy.cplusplus.com/reference/utility/pair/pair/)3.1.3 [有参构造函数](htt…

vue3 基于el-tree增加、删除节点(非TypeScript 写法)

话不多说&#xff0c;直接贴代码 <template><div class"custom-tree-container"><!-- <p>Using render-content</p><el-tree style"max-width: 600px" :data"dataSource" show-checkbox node-key"id" …

智能网联汽车信息安全风险识别与应对策略研究综述

摘要&#xff1a;随着智能网联汽车技术的飞速发展&#xff0c;其信息安全问题逐渐成为公众关注的焦点。本文概述了智能网联汽车技术的发展背景和信息安全风险的来源&#xff0c;采用STRIDE威胁分析方法对智能网联汽车的四层模型进行风险识别&#xff0c;进一步探讨了抗女巫攻击…

Renesas MCU之FreeRTOS的应用

目录 概述 1 FSP配置FreeRTOS 1.1 软件版本信息 1.2 配置FreeRTOS 2 FreeRTOS的Task 2.1 FSP下的项目结构 2.2 Task代码 2.2.1 Task测试案例配置 2.2.2 测试代码实现 3 自定义Task 3.1 编写代码 3.2 测试函数 4 测试 4.1 Task断点测试 4.2 板卡运行测试 概述 …

spring boot sso

代码&#xff1a;https://gitee.com/forgot940629/ssov2 授权服务 登录成功后&#xff0c;session中会存储UsernamePasswordAuthenticationToken&#xff0c;之后每次请求code时都会用UsernamePasswordAuthenticationToken生成OAuth2Authentication&#xff0c;并将OAuth2Aut…