在应用程序中编写控制台

时间:2012-06-12 23:48:36

标签: c++ shell console sfml

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

enter image description here

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

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

enter image description here

我用控制台完成它的方式是当用户按下控制台显示的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解释器。

3 个答案:

答案 0 :(得分:4)

我实现了以下作为我刚才写的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() 我只是从控制台类中获取文本,渲染一个看起来类似于最近的阀门游戏中找到的源引擎控制台窗口的图像,然后在适当的位置绘制文本。

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();
}

答案 1 :(得分:2)

有一些事情要做。首先,您需要某种行编辑支持。有这样的库,例如NetBSD的 editline http://www.thrysoee.dk/editline/

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

这种方法的好处是,您可以使用所有不错的标准库功能与您的控制台进行通信。您可以使用fprintf(consoleout, ...)fscanf(consolein, ...)等等;当然,它也适用于C ++ iostreams。但更重要的是,您可以直接将其附加到库,如前面提到的 editline

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

答案 2 :(得分:0)

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

  • 只需按一下按钮即可打开和关闭它,可能是~之类的东西,它被大量使用。
  • 将您输入的线条设置为背景颜色,可能是透明的,但至少要确保它不仅仅是浮动在RenderWindow上的文本。如果命令的输出是多行,请确保它们全部可见或人们至少可以滚动历史记录。
  • 确保命令易于理解和一致。例如,如果我没有弄错的话,源引擎上的很多游戏都使用cl_前缀来表示相关的任何渲染。例如,请参阅cl_showfps 1
  • 传统的终端输入将是一个很好的接触。向上显示您填写的上一个命令。也许如果您有冒险精神,请使用Tab完成。
  • 如果你还有一些时间,例如通过--help显示可用命令的方法也会很好。当然,这取决于你的游戏有多复杂。

其他的,看看其他游戏是如何做到这一点的。你提到了Quake,它有一个很好的游戏终端的例子。我认为很多源游戏中的那个也很容易使用(参见Half Life 2,Counter Strike Source,Team Fortress 2,Left 4 Dead等)。我认为没有任何标准库,不包括OGRE或IrrLicht等其他框架。