【问题标题】:Writing a console within an application在应用程序中编写控制台
【发布时间】:2012-06-15 21:52:35
【问题描述】:

我的应用程序需要在应用程序窗口中嵌入控制台,例如在 autoCAD 之类的程序中,控制台位于窗口底部等待命令。

我需要在我的应用程序中使用控制台,以便我可以更改变量和其他内容,因此控制台不需要是一个完全爆炸的外壳。

目前我的应用程序中有一个简单的控制台,但与我想要的控制台的终端(shell)相比,它看起来非常笨重。

我使用控制台的方式是当用户按下控制台显示的TAB 键时,他们可以输入他们的命令行;一旦按下Return 键,他们输入的字符串就会被解析并处理命令。

我正在使用sf::Text 对象在我的应用程序窗口中打印出文本。 总共使用了 5 个sf::Text 对象,4 个用于先前的命令/错误消息,1 个用于当前命令行。当按下Return 键时,第 4 个 sf::Text 将其当前字符串更改为第 3 个,第 3 个更改为第 2 个,第 2 个更改为第 1 个,第 1 个更改为当前命令字符串,然后当前命令字符串被清除并准备好再次输入。这样就有了 4 个命令和/或错误的“历史”的空间。不是最好的,但它是我能想到的最好的。当然,可以通过添加更多 sf::Text 对象来更改历史记录的数量。 所以最后这就是我将控制台渲染到屏幕上的方式

sf::RectangleShape rectangle;

rectangle.setSize(sf::Vector2f(App->getSize().x, App->getSize().y / 3));
rectangle.setPosition(0, 0);

rectangle.setFillColor(sf::Color::black);

App->draw(rectangle);   // This renders the console looking background rectangle
App->draw(CLine);   // This renders the current command line

for(int i = 4; i >= 0; --i) // This renders the history as described above, their idevidual positions are setup earlier on in the program
{
    CHistory[i].setString(CS[i]);
    App->draw(CHistory[i]);
}

App 只是一个sf::RenderWindow*

我的总体问题是,有没有一种方法可以将控制台嵌入到我的 SFML 窗口中,而不必像上面那样简单地将文本对象的图像渲染成看起来像控制台. 我希望在我的应用程序中有一个实际的控制台/shell/终端。像标准的 bash shell,当然还有我自己的 shell 解释器。

【问题讨论】:

    标签: c++ shell console sfml


    【解决方案1】:

    我在不久前编写的一个 opengl 游戏的控制台中实现了以下内容。这绝不是你问题的明确答案,但它对我有用,你可能会从中得到一些有用的东西。

    这两个文件在这篇文章的底部。该代码不太可能直接运行,因为我不会包含 2 个库头文件中的一个。如果您想要完整的来源,请告诉我。

    基本上,尽管控制台类允许您向它添加可在运行时更改的变量指针。它接受来自 Windows 事件消息的输入。 (输入的实际处理在别处完成)命令解析在 ProcessInput() 方法中完成,变量在 ChangeVariable() 方法中更新。

    一句警告。这种方法本质上使控制台用户可以直接访问单个变量的内存位置。这需要良好的输入验证,以确保用户不会导致应用程序在运行时崩溃。如果我坐下来尝试制作另一个控制台,我可能会做一些稍微不同的事情。不过我希望这能给你一点帮助。

    头文件:

    #ifndef CONSOLE_H
    #define CONSOLE_H
    
    #include <vector>
    #include <map>
    #include <string>
    #include "Singleton.h"
    #include <Windows.h>
    #include "Enumerations.h"
    #include "StringConversion.h"
    
    class Console
    {
    public:
    
        Console();
        ~Console();
    
        void Update(std::vector<WPARAM> pressedKeys);
    
        void AddInt(std::string varName, int *ptrToInt);
        void AddFloat(std::string varName, float *ptrToFloat);
        void AddLong(std::string varName, long *ptrToLong);
        void AddBool(std::string varName, bool *ptrToBool);
    
        const std::string &GetCurrentText();
        const std::vector<std::string> &GetPreviousText();
    
    private:
        std::map<std::string, int *> m_Ints;
        std::map<std::string, float *> m_Floats;
        std::map<std::string, long *> m_Longs;
        std::map<std::string, bool *> m_Bools;
    
        std::map<std::string, std::string> m_Variables;
    
        std::vector<std::string> m_PrevConsoleText;
        std::string m_CurrInput;
    
        int m_PrevSelection;
    
        bool ProcessInput();
        void ChangeVariable(const std::string &varName, const std::string &value);
    };
    
    typedef Singleton<Console> g_Console;
    
    #endif // CONSOLE_H
    

    cpp 文件:

    #include "Console.h"
    
    Console::Console()
    {
        m_PrevSelection = 0;
    }
    
    Console::~Console()
    {
    
    }
    
    void Console::AddInt(std::string varName, int *ptrToInt)
    {
        m_Ints[varName] = ptrToInt;
        m_Variables[varName] = "int";
    }
    
    void Console::AddFloat(std::string varName, float *ptrToFloat)
    {
        m_Floats[varName] = ptrToFloat;
        m_Variables[varName] = "float";
    }
    
    void Console::AddLong(std::string varName, long *ptrToLong)
    {
        m_Longs[varName] = ptrToLong;
        m_Variables[varName] = "long";
    }
    
    void Console::AddBool(std::string varName, bool *ptrToBool)
    {
        m_Bools[varName] = ptrToBool;
        m_Variables[varName] = "bool";
    }
    
    void Console::ChangeVariable(const std::string &varName, const std::string &value)
    {
        //*(m_Bools[varName]) = value;
    
        std::string temp = m_Variables[varName];
    
        if(temp == "int")
        {
            //*(m_Ints[varName]) = fromString<int>(value);
        }
        else if(temp == "float")
        {
            //*(m_Floats[varName]) = fromString<float>(value);
        }
        else if(temp == "long")
        {
            //*(m_Longs[varName]) = fromString<long>(value);
        }
        else if(temp == "bool")
        {
            if(value == "true" || value == "TRUE" || value == "True")
            {
                *(m_Bools[varName]) = true;
            }
            else if(value == "false" || value == "FALSE" || value == "False")
            {
                *(m_Bools[varName]) = false;
            }
        }
    }
    
    const std::string &Console::GetCurrentText()
    {
        return m_CurrInput;
    }
    
    void Console::Update(std::vector<WPARAM> pressedKeys)
    {
        for(int x = 0; x < (int)pressedKeys.size(); x++)
        {
            switch(pressedKeys[x])
            {
            case KEY_A:
                m_CurrInput.push_back('a');
                break;
            case KEY_B:
                m_CurrInput.push_back('b');
                break;
            case KEY_C:
                m_CurrInput.push_back('c');
                break;
            case KEY_D:
                m_CurrInput.push_back('d');
                break;
            case KEY_E:
                m_CurrInput.push_back('e');
                break;
            case KEY_F:
                m_CurrInput.push_back('f');
                break;
            case KEY_G:
                m_CurrInput.push_back('g');
                break;
            case KEY_H:
                m_CurrInput.push_back('h');
                break;
            case KEY_I:
                m_CurrInput.push_back('i');
                break;
            case KEY_J:
                m_CurrInput.push_back('j');
                break;
            case KEY_K:
                m_CurrInput.push_back('k');
                break;
            case KEY_L:
                m_CurrInput.push_back('l');
                break;
            case KEY_M:
                m_CurrInput.push_back('m');
                break;
            case KEY_N:
                m_CurrInput.push_back('n');
                break;
            case KEY_O:
                m_CurrInput.push_back('o');
                break;
            case KEY_P:
                m_CurrInput.push_back('p');
                break;
            case KEY_Q:
                m_CurrInput.push_back('q');
                break;
            case KEY_R:
                m_CurrInput.push_back('r');
                break;
            case KEY_S:
                m_CurrInput.push_back('s');
                break;
            case KEY_T:
                m_CurrInput.push_back('t');
                break;
            case KEY_U:
                m_CurrInput.push_back('u');
                break;
            case KEY_V:
                m_CurrInput.push_back('v');
                break;
            case KEY_W:
                m_CurrInput.push_back('w');
                break;
            case KEY_X:
                m_CurrInput.push_back('x');
                break;
            case KEY_Y:
                m_CurrInput.push_back('y');
                break;
            case KEY_Z:
                m_CurrInput.push_back('z');
                break;
            case KEY_0:
                m_CurrInput.push_back('0');
                break;
            case KEY_1:
                m_CurrInput.push_back('1');
                break;
            case KEY_2:
                m_CurrInput.push_back('2');
                break;
            case KEY_3:
                m_CurrInput.push_back('3');
                break;
            case KEY_4:
                m_CurrInput.push_back('4');
                break;
            case KEY_5:
                m_CurrInput.push_back('5');
                break;
            case KEY_6:
                m_CurrInput.push_back('6');
                break;
            case KEY_7:
                m_CurrInput.push_back('7');
                break;
            case KEY_8:
                m_CurrInput.push_back('8');
                break;
            case KEY_9:
                m_CurrInput.push_back('9');
                break;
            case KEY_QUOTE:
                m_CurrInput.push_back('\"');
                break;
            case KEY_EQUALS:
                m_CurrInput.push_back('=');
                break;
            case KEY_SPACE:
                m_CurrInput.push_back(' ');
                break;
            case KEY_BACKSPACE:
                if(m_CurrInput.size() > 0)
                {
                    m_CurrInput.erase(m_CurrInput.end() - 1, m_CurrInput.end());
                }
                break;
            case KEY_ENTER:
                ProcessInput();
                break;
            case KEY_UP:
                m_PrevSelection--;
                if(m_PrevSelection < 1)
                {
                    m_PrevSelection = m_PrevConsoleText.size() + 1;
                    m_CurrInput = "";
                }
                else
                {
                    m_CurrInput = m_PrevConsoleText[m_PrevSelection - 1];
                }
    
                break;
            case KEY_DOWN:
                if(m_PrevSelection > (int)m_PrevConsoleText.size())
                {
                    m_PrevSelection = 0;
                    m_CurrInput = "";
                }
                else
                {
                    m_CurrInput = m_PrevConsoleText[m_PrevSelection - 1];
                }
                m_PrevSelection++;
                break;
            }
        }
    }
    
    bool Console::ProcessInput()
    {
        int x;
        std::string variable = "NULL", value;
        bool ok = false;
        std::string::iterator it;
    
        //Split up the input from the user.
        //variable will be the variable to change
        //ok will = true if the syntax is correct
        //value will be the value to change variable to.
        for(x = 0; x < (int)m_CurrInput.size(); x++)
        {
            if(m_CurrInput[x] == ' ' && variable == "NULL")
            {
                variable = m_CurrInput.substr(0, x);
            }
            else if(m_CurrInput[x] == '=' && m_CurrInput[x - 1] == ' ' && m_CurrInput[x + 1] == ' ')
            {
                ok = true;
            }
            else if(m_CurrInput[x] == ' ')
            {
                value = m_CurrInput.substr(x + 1, m_CurrInput.size());
            }
        }
    
        if(ok)
        {
            m_PrevConsoleText.push_back(m_CurrInput);
            m_PrevSelection = m_PrevConsoleText.size();
    
            if(m_PrevConsoleText.size() > 10)
            {
                m_PrevConsoleText.erase(m_PrevConsoleText.begin(), m_PrevConsoleText.begin() + 1);
            }
            m_CurrInput.clear();
    
    
            ChangeVariable(variable, value);
        }
        else
        {
            m_PrevConsoleText.push_back("Error invalid console syntax! Use: <variableName> = <value>");
            m_CurrInput.clear();
        }
    
        return ok;
    }
    
    const std::vector<std::string> &Console::GetPreviousText()
    {
        return m_PrevConsoleText;
    }
    

    编辑 1:添加 DrawConsole() 我只是从控制台类中获取文本,渲染一个看起来类似于任何最近的 Valve 游戏中的源引擎控制台窗口的图像,然后在适当的位置绘制文本。

    void View::DrawConsole()
    {
        Square console;
        std::vector<std::string> temp;
        temp = g_Console::Instance().GetPreviousText();
    
        console.top = Vector3f(0.0, 0.0, 1.0);
        console.bottom = Vector3f(640, 480, 1.0);
    
        g_Render::Instance().SetOrthographicProjection();
        g_Render::Instance().PushMatrix();
        g_Render::Instance().LoadIdentity();
    
        g_Render::Instance().BindTexture(m_ConsoleTexture);
        g_Render::Instance().DrawPrimative(console, Vector3f(1.0f, 1.0f, 1.0f));
        g_Render::Instance().DisableTexture();
    
        g_Render::Instance().SetOrthographicProjection();
        //Draw the current console text
        g_Render::Instance().DrawString(g_Console::Instance().GetCurrentText(), 0.6f, 20, 465);
    
        //Draw the previous console text
        for(int x = (int)temp.size(); x > 0; x--)
        {
            g_Render::Instance().DrawString(temp[x-1], 0.6f, 20, (float)(425 - (abs((int)temp.size() - x) * 20)));
        }
    
        g_Render::Instance().SetPerspectiveProjection();
    
        g_Render::Instance().PopMatrix();
        g_Render::Instance().SetPerspectiveProjection();
    }
    

    【讨论】:

    • 请问如何将这个渲染到 OpenGL 窗口?
    • 我在上面的答案中添加了 DrawConsole 函数。请记住,还有很多代码链接到其他地方,比如我的渲染类。
    【解决方案2】:

    这有几件事。首先,您需要某种行编辑支持。有这方面的库,例如 NetBSD 的 editline http://www.thrysoee.dk/editline/

    然后您需要以某种方式处理按键。现在这就是乐趣的开始。我没有尝试直接处理关键事件,而是将它们输入到使用 Windows 上 (POSIX) / CreatePipe 上的pipe 创建的匿名管道中。然后在另一端你可以读取它们,就好像它们来自 stdin。第二个匿名管道将 stdout 的功能加倍,并将其输出显示在游戏内控制台上。我将调用生成的一对 FD consoleinconsoleout。我还会为紧急错误消息添加一个 consoleerr FD;控制台可能会以另一种颜色显示它们或过滤它们。

    这种方法的好处是,您可以使用所有不错的标准库功能与您的控制台对话。可以使用fprintf(consoleout, ...)fscanf(consolein, ...)等;当然,它也适用于 C++ iostream。但更重要的是,您可以直接将其附加到上述 editline 等库中。

    最后,您需要处理用户在控制台中键入的命令。在那里我会走懒惰的路线,只是嵌入一个脚本语言解释器,一个支持交互式操作的解释器。像 Python,或者在整个游戏中非常普遍,Lua。当然,您也可以实现自己的命令解释器。

    【讨论】:

    • 这就是我所追求的,关于如何去做的一些澄清。我对您提到的匿名管道解决方案非常感兴趣,这似乎是正确的方法。我只是不确定如何实现它,如果您能在 POSIX 系统上给出一个小代码示例,我将不胜感激。谢谢。
    【解决方案3】:

    好吧,如果你想让它感觉更像一个控制台,你可能想要的是:

    • 可以一键开启和关闭,大概是~之类的,用的比较多。
    • 为您正在键入的行添加背景颜色,可能是透明的,但至少要确保它不仅仅是漂浮在 RenderWindow 上的文本。如果命令的输出是多行,请确保它们都可见,或者人们至少可以滚动浏览历史记录。
    • 确保命令易于理解且一致。例如,如果我没记错的话,源引擎上的很多游戏都使用cl_ 前缀来表示与渲染相关的任何内容。例如,请参阅cl_showfps 1
    • 传统的终端输入会是一个不错的选择。 Up 向您显示您之前填写的命令。如果您喜欢冒险,请使用 Tab 完成。
    • 如果您还有一些时间,例如通过--help 显示可用命令的方法也不错。当然,这取决于您的游戏有多复杂。

    剩下的,看看其他游戏是如何做到的。您提到了 Quake,它有一个很好的游戏终端示例。我个人认为许多 Source 游戏中的一款也很容易使用(参见《半条命 2》、《反恐精英》、《军团要塞 2》、《Left 4 Dead》等)。我认为没有任何标准库可以解决这个问题,不包括 OGRE 或 IrrLicht 等其他框架。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-12
      • 2012-11-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多