Написание консоли внутри приложения

Мое приложение требует, чтобы консоль была встроена в окно приложения, например, в такой программе, как AutoCAD, где консоль находится в нижней части окна в ожидании команд.

введите здесь описание изображения

Мне нужна консоль в моем приложении, чтобы я мог изменять переменные и другие вещи, поэтому консоль не должна быть полностью раздутой оболочкой.

На данный момент у меня есть простая консоль в моем приложении, но она кажется очень неуклюжей по сравнению с терминалом (оболочкой), на что я и хочу, чтобы консоль была похожа.

введите здесь описание изображения

То, как я сделал это с консолью, которая у меня есть, заключается в том, что когда пользователь нажимает клавишу 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 без того, чтобы это было просто изображение текстовых объектов, отображаемое так, чтобы оно выглядело как консоль, как я сделал выше. Я бы предпочел иметь настоящую консоль/оболочку/терминал в своем приложении. Как и стандартная оболочка bash, но, конечно же, мой собственный интерпретатор оболочки.


person Elgoog    schedule 12.06.2012    source источник


Ответы (3)


Я реализовал следующее в качестве консоли для игры 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();
}
person Brendan    schedule 13.06.2012
comment
могу я спросить, как вы визуализируете это в окне OpenGL? - person Elgoog; 15.06.2012
comment
Я добавил функцию DrawConsole в свой ответ выше. Имейте в виду, что снова есть довольно много кода, который ссылается на другие места, такие как мой класс рендеринга. - person Brendan; 15.06.2012

Есть несколько моментов. Во-первых, вам нужна какая-то поддержка редактирования строк. Для этого существуют библиотеки, например editline NetBSD http://www.thrysoee.dk/editline/

Тогда вам как-то нужно обрабатывать нажатия клавиш. Теперь здесь начинается самое интересное. Вместо того, чтобы пытаться обрабатывать ключевые события напрямую, я бы передал их в анонимный канал, созданный с использованием pipe в (POSIX) / CreatePipe в Windows. Затем на другом конце вы можете прочитать их, как если бы они пришли из stdin. Второй анонимный канал удваивает функцию stdout и отображает его вывод на внутриигровой консоли. Я бы назвал получившуюся пару FD consolein и consoleout. Я бы также добавил consoleerr FD для срочных сообщений об ошибках; консоль может отображать их другим цветом или фильтровать.

Хорошая вещь в этом подходе заключается в том, что вы можете использовать все хорошие функции стандартной библиотеки для общения с вашей консолью. Вы можете использовать fprintf(consoleout, ...), fscanf(consolein, ...) и так далее; конечно, он также работает с iostreams C++. Но что еще более важно, вы можете напрямую прикрепить его к библиотекам, таким как вышеупомянутая editline.

Наконец, вам нужно обработать команды, введенные пользователем в консоль. Там я бы пошел ленивым путем и просто встроил интерпретатор языка сценариев, который поддерживает интерактивную работу. Например, Python или широко распространенный в играх Lua. Конечно, вы также можете реализовать свой собственный интерпретатор команд.

person datenwolf    schedule 13.06.2012
comment
Это то, что я хотел, некоторые разъяснения о том, как это сделать. Меня очень интересует упомянутое вами решение для анонимных каналов. Кажется, это правильный путь. Я просто не уверен, как это реализовать. Если бы вы могли привести небольшой пример кода в системе POSIX, я был бы очень признателен. Спасибо. - person Elgoog; 13.06.2012

Ну, что вам, вероятно, нужно, если вы хотите, чтобы он больше походил на консоль:

  • Возможность включать и выключать его одним нажатием кнопки, вероятно, что-то вроде ~, которое часто используется.
  • Дайте строке, которую вы печатаете, фоновый цвет, возможно, прозрачный, но, по крайней мере, убедитесь, что это не просто текст, плавающий в RenderWindow. Если вывод команды состоит из нескольких строк, убедитесь, что все они видны или что люди могут хотя бы прокручивать историю.
  • Убедитесь, что команды просты для понимания и последовательны. Например, если я не ошибаюсь, многие игры на исходном движке используют префикс cl_ для всего, что связано с рендерингом. См., например, cl_showfps 1.
  • Традиционный терминальный ввод был бы приятным штрихом. Вверху отображается предыдущая команда, которую вы ввели. Может быть, если вы чувствуете себя авантюрно, используйте Tab для завершения.
  • Если у вас осталось немного времени, было бы неплохо показать доступные команды, например, через --help. Конечно, в зависимости от сложности вашей игры.

В остальном посмотрите, как это сделали другие игры. Вы упомянули Quake, в котором есть отличный пример игрового терминала. Я, например, думаю, что тот, что используется во многих играх Source, также прост в использовании (см. Half Life 2, Counter Strike Source, Team Fortress 2, Left 4 Dead и т. д.). Я не думаю, что для этого существуют какие-либо стандартные библиотеки, не считая других фреймворков, таких как OGRE или IrrLicht.

person RadicalRaid    schedule 13.06.2012