Обзоры игр, статьи, программы, обои, игры, общение...

  

Статьи

.: НАВИГАЦИЯ 3D

--- 3D Touch ---

 

.: СЧЁТЧИКИ

Рейтинг@Mail.ru

 

.: БАННЕРЫ

Rambler's Top100

 

.: РЕКЛАМА

По вопросам размещения Вашей рекламы обращаться ко мне.

 

.: ПРИКОЛЬНОЕ ФОТО

Реализация консоли!!!
Реализация консоли

Не знаю как кому, но меня всегда завораживала эта штука. Вот так нажимаешь на тильду, а тут сверху откуда не возьмись сверху выползает такая штука в которой написана всякая малопонятная информация и в которой можно написать какую-нибудь команду и в худшем случае получить сообщение "Unknown command: 'godmode'". С самых первых попыток написания игр первым что я пытался реализовать это консоль. И правда, это ведь так удобно! Зачем каждый раз перекомпилировать программу чтобы посмотреть что же получится если поменять вот такое-то значение. Да и вообще это дико модная фишка 8). Первые реализации были далеки от совершенства. Создание статического массива из "стрингов" и проверка какую именно команду из этих написал юзер не отличалось ни быстродействием, ни гибкостью. В итоге я пришёл к решению, которое отвечает выше указанным требованием.

Итак, начнем.

Принцип

Принцип, как мне кажется, прост. Имеем некую структуру, содержащую имя команды (то, что вводит юзер) и ссылку на что-то в память с чем нужно произвести некоторые действия.

Реализация

В этой статье я не собираюсь описывать то, как выводить ту самую выползающую сверху панель, как выводить шрифты в том же OpenGL API (мною любимом 8)), я просто расскажу про то, как можно выполнять введенные юзером команды.

Итак, приступим.

Для начала опишем структуру команды. Например так:

Command = record
  name : string[64];
  typ : byte;
  p : Pointer;
end;

Думаю с эти все понятно, но я поясню:

name - Это собственно команда. То что введет юзер с параметрами.

typ - Важный момент! Это тип команды. То есть в зависимости от значения этого поля, грубо говоря, решаем на какой тип указывает указатель.

p - Собственно указатель на нечто в памяти, определенное полем typ. (для сишников: Pointer это аналог void* в C/C++)

Вот так. Ничего сложного!

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

TConsole = class
  cmds:array of Command;
  procedure ExecCmd(s:string);
  procedure AddCmd(name : string; tip : byte; p : pointer);
end;

!!! ВАЖНО

В данной статье я буду пользоваться динамическим массивом (array of ...). Но это только для примера. Лично я придпочитаю использовать списки. Возможно я плохо разбираюсь в организации динамической памяти. Но логика подсказывает мне что список будет работать быстрее чем массив.

И так вернемся к нашему классу. Принцип работы такой: Имеется некий контейнер (в данном случае динамический массив), в который в любой момент выполнения программы возможно добавление (и удаление опционально) элементов. Имеется некая процедура которая обрабатывает введенную пользователем строку.

Еще один важный момент. Для определения типов я бы создал несколько констант:

const
  TYPE_INT = 0;
  TYPE_FLOAT = 1;
  TYPE_STRING = 2;
  TYPE_CODE = 3;

Думаю для начала хватит... На последний тип нужно будет обратить особое внимание. Этот тип будет указывать на то, что данная команда должна выполнять некую функцию из памяти. На эту функцию будет указывать наш указатель. В принципе можно сделать возможность выполнения любой функции из памяти, но это слишком мудрёно. Нужно будет определять сколько функция требует параметров и помещать эти параметры в стек. Но мы поступим проще. Наша консоль сможет вызывать только функции с одним параметром типа string. Таким образом мы избавляемся от разбора введенного пользователем параметра. Мы просто разделим введенную пользователем строку на собственно команду и все остальное. Все остальное просто передадим в указанную функцию, пусть она все, что ей надо и разбирает 8). Кстати для простоты разделами будут считаться пробелы (как в CS 8)). И так опишем тип функции поддерживаемой консолью для вызова...

type
  TConsoleCmd = function(param : string):string;

Что будет возвращать функция, в принципе, не важно. Можно было было сделать и процедуру. Но в таком случае приятно возвращать строку типа "Ошибка: Не хватает параметров!" или "Ошибка: 'штангенциркуль' - значение не подходит пот целочисленный тип" 8)

Может я понаписал всяких непонятных слов, но думаю сейчас, когда я расскажу про собственную процедуру выполнения, все станет ясно 8). Итак, план выполнения этой функции.

- Разделим строку на команду и параметр.

- Проходимся по всем имеющимся командам и сверяем с командой введенной юзером.

- Вах! Нашли нужную команду!

- Выполняем ее.

Вот так бы я это написал:

procedure TConsole.ExecCmd(s:string);
var

  i:integer; //Управляющая переменная цикла
  cmd,par:string; //Команда, параметр(ы)
  cc:TConsoleCmd; //Функция
  e:boolean; //Если true значит команда нашлась.
begin
  e := false; // По умолчанию команда не нашлась.
  //Разделение s на cmd и par. cmd - то что до пробела, par все что после. Если пробела нет, то par = ''
  if pos(' ', s)<>0 then
  begin
    cmd := copy(s, 1, pos(' ', s)-1);
    Delete(s, 1, pos(' ', s));
    par := s;
  end else
  begin
    cmd := s;
    par := '';
  end;
  for i := 0 to length(cmds)-1 do // Проходим по всем имеющимся командам
  begin
    if cmds[i].name = cmd then // А не вот эту ли команду ввел юзер?
    begin
      e := true; // Ура! Мы нашли команду!
      case cmds[i].typ of // А какой у команды тип?
      TYPE_INT : integer(cmds[i].p^) := StrToInt(pars); // меняем значение по адресу p
      TYPE_FLOAT : glFloat(cmds[i].p^) := StrToFloat(pars); // -------- '' ---------
      TYPE_STRING : string(cmds[i].p^) := pars; // ------- '' ---------
      TYPE_CODE : begin
      @cc := cmds[i].p; // Переменную cc "передвигаем" на место нужной функции.
      cc(pars); // Выполняем функцию.
    end;
  end;
  break; // прервать цыкл. Больше не надо!
end;
end;
if not e then ShowMessage('Unknow command: ' + cmd); // Такой команды не нашлось. Вот обидно 8(
end;

Вот вроде бы все просто! На самом деле это все-таки упрощенный код. В этом случае если пользователь введет пустой параметр для команды типа integer функция StrToInt обязательно вызовет исключительную ситуацию. Нужно будет добавить проверку, а еще лучше самому напасать подобную функцию для увеличения быстродействия. Также результат выполнения "некой функции" никуда не девается. Да и ShowMessage никуда не годится 8)

Ну еще на всякий случай опишу процедуру добавления команды.

procedure TConsole.AddCmd(name : string; tip : byte; p : pointer);
var

  nc:Command;
begin
  nc.name := name;
  nc.typ := tip;
  nc.p := p;
  SetLength(cmds, length(cmds)+1);
  cmds[length(cmds)-1] := nc;
end;

Вот теперь как все это применить... Например:

Console := TConsole;
<...>
Console := TConsole.Create;
<...>
TMyCoolCar = class(TCar)
<...>
speed : glfloat;
<...>
constructor Create;
end;
<...>
constructor TMyCoolCar.Create;
begin
inherited Create;

  Console.AddCmd('car_speed', TYPE_FLOAT, @speed);
end;

Вот примерно так... В обобщенном виде 8)

Вот так, дорогой читатель, я объяснил тебе как я реализовал бы консоль. Теперь на тебя возлагается переработка этого текста, добавления всяких фишек и написание вылезающий по тильде панели 8) Вперед!

Автор: Ke0I0uP   e-mail: reel_geek[друг человека]km.ru 

 

 

 

 Worlds3D by Nicolaev Oleg Copyright © 2005

© 2002-2005 , Nic Soft Arts All Rights Reserved

© 2005 , Dream Software All Rights Reserved

Дизайн: Студия web-mast www.igropad.narod.ru

Hosted by uCoz