Что такое скрипты и с чем их едят — Lua & C++

  • Tutorial
Добрый день, Хабрахабр!
Решил написать этот топик на тему скриптов

Что нужно знать?


  • С++ на приличном уровне (в уроке будут шаблоны — template)
  • Lua, очень легкий скриптовый язык. Советую этот урок.

Почему писать диалоги игры в .cpp файле было большой ошибкой


Если вы разрабатывали большие проекты (к примеру, масштабные игры), замечали, что с каждой новой сотней строк кода компиляция идет медленней?
В игре создается больше оружия, больше диалогов, больше меню, больше etc.
Одна из самых главных проблем, возникающих в связи с нововведениями — поддерживать бессчетное множество оружия и бейджиков довольно сложное занятие.
В ситуации, когда просьба друга/босса/напарника изменить диалог или добавить новый вид оружия занимает слишком много времени, приходится прибегать к каким-то мерам — например, записи всей этой фигни в отдельные текстовые файлы.
Почти каждый геймдевелопер когда-нибудь делал карту уровней или диалоги в отдельном текстовом файле и потом их считывал. Взять хотя бы простейший вариант — олимпиадные задачи по информатике с файлом ввода

Но есть способ, на голову выше — использование скриптов.

Решение проблемы


«Окей, для таких дел хватает обычного файла с описанием характеристиков игрока. Но что делать, если в бурно развивающемся проекте почти каждый день приходится немножко изменять логику главного игрока, и, следовательно, много раз компилировать проект?»
Хороший вопрос. В этом случае нам на помощь приходят скрипты, держащие именно логику игрока со всеми характеристиками либо какой-либо другой части игры.
Естественно, удобнее всего держать, логику игрока в виде кода какого-нибудь языка программирования.
Первая мысль — написать свой интерпретатор своего скриптового языка, выкидывается из мозга через несколько секунд. Логика игрока определенно не стоит таких жутких затрат.
К счастью, есть специальные библиотеки скриптовых языков для С++, которые принимают на вход текстовый файл и выполняют его.

Об одном таком скриптовом языке Lua пойдет речь.

Как это работает?


Прежде чем начать, важно понимать, как работает скриптовый язык. Дело в том, что в скриптовых языках есть очень мало функций, при наличии конструкций for, while, if, прочих.
В основном это функции вывода текста в консоль, математические функции и функции для работы с файлами.
Как же тогда можно управлять игроком через скрипты?

Мы в С++-программе делаем какие-либо функции, «регистрируем» их под каким-нибудь именем в скрипте и вызываем в скрипте. То есть если мы зарегистрировали функцию SetPos(x,y) для определения позиции игрока в С++-программе, то, встретив эту функцию в скрипте, «интерпретатор» из библиотеки скриптового языка вызывает эту функцию в С++-программе, естественно, с передачей всех методов.
Удивительно, да? :)

UPD: Внимание! Один юзер обратился мне с мейлом, что, когда я заливал код, я не полностью устранил все ошибки — habrahabr.ru/post/196272/#comment_6850016
В коде с позволения хабра проникли жучки
Замените участки кода вроде
	template<class t>
	T MethodName();

На
	template<class T>
	T MethodName();

И еще вместо lua_CFunction проскакивает lua_cfunction
Спасибо!

Я готов!


Когда вы поняли преимущества скриптовых языков программирования, самое время начать работать!
Скачайте из репозитория на гитхабе (низ топика) lib'у и includ'ы Lua, либо возмите их на официальном сайте.

Создаем консольный проект либо Win32 (это неважно) в Visual Studio (у меня стоит версия 2012)

Заходим в Проект->Свойства->Свойства конфигурации->Каталоги VC++ и в «каталоги включения» и «каталоги библиотек» добавьте папку Include и Lib из репозитория соответственно.

Теперь создаем файл main.cpp, пишем в нем:
int main()
{
	return 0;
}

Как вы догадались, у меня консольное приложение.

Теперь переходим к кодингу

Обещаю, что буду тщательно объяснять каждый момент

У нас за скрипты будет отвечать класс Script. Я буду объявлять и одновременно реализовывать функции в Script.h/.cpp
Создаем Script.cpp и пишем в нем
#include "Script.h"

Создаем Script.h и пишем в нем
#ifndef _SCRIPT_H_
#define _SCRIPT_H_

#endif

После 2 строчки и перед #endif мы определяем класс скриптов
Этот код пишется для предотвращения взаимного включения файлов. Допустим, что файл Game.h подключает Script.h, а Script.h подключает Game.h — непорядок! А с таким кодом включение выполняется только 1 раз

Теперь пишем внутри этого кода вот это
#pragma comment(lib,"lua.lib")
extern "C"
{
	#include <lua.h>
	#include <lualib.h>
	#include <lauxlib.h>
}

Первая строчка подключает сам lua.lib из архива.
Для чего нужен extern «C»? Дело в том, что lua написан на С и поэтому такой код необходим для подключения библиотек.

Дальше идет подключение хорошо известных многим файлов для работы с консолью
#include <stdio.h>
#include <iostream>
#include <sstream>
using namespace std;


Теперь приступим к определению класса
class Script
{

Самый главный объект библиотеки Lua для C++ — lua_State, он необходим для выполнения скриптов
private:
    lua_State *lua_state;

Дальше идут публичные функции
public:
	void Create();

Эта функция инициализирует lua_State

Create()
Его определение в Script.cpp

void Script::Create()
{
	lua_state = luaL_newstate();
	
	static const luaL_Reg lualibs[] = 
	{
		{"base", luaopen_base},
		{"io", luaopen_io},
		{NULL, NULL}
	};

	for(const luaL_Reg *lib = lualibs; lib->func != NULL; lib++)
	{
		luaL_requiref(lua_state, lib->name, lib->func, 1);
		lua_settop(lua_state, 0);
	}
}

Первой строчкой мы инициализируем наш lua_State.
Потом мы объявляем список «подключенных библиотек». Дело в том, что в «чистом» виде в луа есть только функция print(). Для математических и прочих функций требуется подключать специальные библиотеки и потом вызывать их как math.foo, base.foo, io.foo. Для подключения других библиотек добавьте в lualibs, например, {«math», luaopen_math}. Все названия библиотек начинаются с luaopen_..., в конце lialibs должен стоять {NULL,NULL}
	void Close();

Эта функция освобождает ресурсы Lua
Close()
Ее определение
void Script::Close()
{
	lua_close(lua_state);
}

Просто используем lua_close()
int DoFile(char* ScriptFileName);

А эта функция выполняет файл. На вход она принимает название файла, например, «C:\\script.lua».
Почему она возвращает int? Просто некоторые скрипты могут содержать return, прерывая работу скрипта и возвращая какое-нибудь значение.

DoFile()
Ее определение
int Script::DoFile(char* ScriptFileName)
{
	luaL_dofile(lua_state,ScriptFileName);

	return lua_tointeger(lua_state, lua_gettop(lua_state));
}

Как вы видите, я выполняю скрипт и возвращаю int. Но возращать функция может не только int, но еще и bool и char*, просто я всегда возвращаю числа (lua_toboolean, lua_tostring)


Теперь мы сделаем функцию, регистрирующую константы (числа, строки, функции)
	template<class t>
	void RegisterConstant(T value, char* constantname);
RegisterConstant()
Мы действуем через шаблоны. Пример вызова функции:
RegisterConstant<int>(13,"goodvalue");

Ее определение
template<>
void Script::RegisterConstant<int>(int value, char* constantname)
{
	lua_pushinteger(lua_state, value);
	lua_setglobal(lua_state,constantname);
}

template<>
void Script::RegisterConstant<double>(double value, char* constantname)
{
	lua_pushnumber(lua_state, value);
	lua_setglobal(lua_state,constantname);
}

template<>
void Script::RegisterConstant<char>(char* value, char* constantname)
{
	lua_pushstring(lua_state, value);
	lua_setglobal(lua_state,constantname);
}

template<>
void Script::RegisterConstant<bool>(bool value, char* constantname)
{
	lua_pushboolean(lua_state, value);
	lua_setglobal(lua_state,constantname);
}

template<>
void Script::RegisterConstant<lua_cfunction>(lua_CFunction value, char* constantname)
{
	lua_pushcfunction(lua_state, value);
	lua_setglobal(lua_state,constantname);
}

Для каждого возможного значения class T мы определяем свои действия.
*Капитан* последнее определение — регистрация функции
Функции, годные для регистрации, выглядят так:
int Foo(lua_State*)
{
   // ...
   return n;
}

Где n — количество возвращаемых значений. Если n = 2, то в Луа можно сделать так:
a, b = Foo()

Читайте мануалы по Луа, если были удивлены тем, что одна функция возвращает несколько значений :)


Следующая функция создает таблицу для Луа. Если непонятно, что это значит, то тамошная таблица все равно что массив
	void Array();
Array()
Ее описание
void Script::Array()
{
    lua_createtable(lua_state, 2, 0);
}


Следующая функция регистрирует элемент в таблице.
	template<class t>
	void RegisterConstantArray(T value, int index);
RegisterConstantArray()
Ее описание
template
void Script::RegisterConstantArray<int>(int value, int index)
{
	lua_pushnumber(lua_state, index);
	lua_pushinteger(lua_state, value);
	lua_settable(lua_state, -3);
}

template
void Script::RegisterConstantArray<double>(double value, int index)
{
	lua_pushnumber(lua_state, index);
	lua_pushnumber(lua_state, value);
	lua_settable(lua_state, -3);
}

template
void Script::RegisterConstantArray<char>(char* value, int index)
{
	lua_pushnumber(lua_state, index);
	lua_pushstring(lua_state, value);
	lua_settable(lua_state, -3);
}

template
void Script::RegisterConstantArray<bool>(bool value, int index)
{
	lua_pushnumber(lua_state, index);
	lua_pushboolean(lua_state, value);
	lua_settable(lua_state, -3);
}

template
void Script::RegisterConstantArray<lua_cfunction>(lua_CFunction value, int index)
{
	lua_pushnumber(lua_state, index);
	lua_pushcfunction(lua_state, value);
	lua_settable(lua_state, -3);
}

Если вы не знаете Lua, вы, наверное, удивлены тем, что в один массив помещается столько типов? :)
На самом деле в элементе таблицы может содержаться еще и таблица, я так никогда не делаю.


Наконец, заполненную таблицу нужно зарегистрировать
	void RegisterArray(char* arrayname);
RegisterArray()
Ее описание
void Script::RegisterArray(char* arrayname)
{
	lua_setglobal(lua_state, arrayname);
}

Ничего особенного нет


Следующие функции предназначены в основном только для функций типа int foo(lua_State*), которые нужны для регистрации в Луа.

Первая из них — получает количество аргументов
	int GetArgumentCount();
Create()
Ее описание
int Script::GetArgumentCount()
{
	return lua_gettop(lua_state);
}

Эта функция нужна, например, для функции Write(), куда можно запихать сколь угодно аргументов, а можно и ни одного
Подобную функцию мы реализуем позже


Следующая функция получает аргумент, переданный функции в скрипте
	template<class t>
	T GetArgument(int index);
GetArgument()
Ее описание
template
int Script::GetArgument<int>(int index)
{
	return lua_tointeger(lua_state,index);
}

template
double Script::GetArgument<double>(int index)
{
	return lua_tonumber(lua_state,index);
}

template
char* Script::GetArgument<char>(int index)
{
	return (char*)lua_tostring(lua_state,index);
}

template
bool Script::GetArgument<bool>(int index)
{
	return lua_toboolean(lua_state,index);
}

Можно получить все типы, описывавшиеся ранее, кроме таблиц и функций
index — это номер аргумента. И первый аргумент начинается с 1.


Наконец, последняя функция, которая возвращает значение в скрипт
	template<class t>
	void Return(T value);
Return()
Ее описание
template<>
void Script::Return<int>(int value)
{
	lua_pushinteger(lua_state,value);
}

template<>
void Script::Return<double>(double value)
{
	lua_pushnumber(lua_state,value);
}

template<>
void Script::Return<char>(char* value)
{
	lua_pushstring(lua_state,value);
}

template<>
void Script::Return<bool>(bool value)
{
	lua_pushboolean(lua_state,value);
}


Боевой код



Пора что-нибудь сделать!
Изменяем main.cpp

#include "Script.h"

int main()
{
	return 0;
}

Компилируем. Теперь можно приступить к тестированию нашего класса

Помните, я обещал сделать функцию Write? :)
Видоизменяем main.cpp
#include "Script.h"

// Нужен для _getch()
#include <conio.h>

// Объект скрипта
Script script;

// Функция Write для текста
int Write(lua_State*)
{
	// Тут мы считываем количество аргументов и каждый аргумент выводим
	for(int i = 1; i < script.GetArgumentCount()+1; i++)
		cout << script.GetArgument<char*>(i);

	// После вывода ставим консоль на паузу
	_getch();

	return 0;
}

int main()
{
	script.Create();
	// Имя у луашной функции такое же, как у сишной
	script.RegisterConstant<lua_cfunction>(Write,"Write");
	script.DoFile("script.lua");
	script.Close();
}

А в папке с проектом создаем файл script.lua
Write(1,2,3,4)


image

Компилируем и запускаем проект.

image

Теперь изменяем script.lua
for i = 1, 4 do
	Write(i, "\n", "Hier kommt die Sonne", "\n")
end

Теперь программа будет выводить по 2 строки ("\n" — создание новой строки), ждать нажатия Enter и снова выводить строки.

image

Экспериментируйте со скриптами!

Вот пример main.cpp с функциями и пример script.lua

#include "Script.h"

#include <conio.h>
#include <Windows.h>
#include <time.h>

Script script;

int Write(lua_State*)
{
	// Тут мы считываем количество аргументов и каждый аргумент выводим
	for(int i = 1; i < script.GetArgumentCount()+1; i++)
		cout << script.GetArgument<char*>(i);
	cout << "\n";

	return 0;
}

int GetString(lua_State*)
{
	// Считываем строку с помощью cin и возвращаем ее, используя методы Script

	char* str = "";
	cin >> str;
	script.Return<char*>(str);

	// Не забудьте! У нас возвращается 1 результат -> return 1
	return 1;
}

int Message(lua_State*)
{
	// Выводим обычное сообщение MessageBox из Windows.h
	// Кстати, вам домашнее задание - сделайте возможность вывода сообщений с несколькими аргументами :)

	char* msg = script.GetArgument<char*>(1);

	MessageBox(0,msg,"Сообщение",MB_OK);

	return 0;
}

int GetTwoRandomNumbers(lua_State*)
{
	// Возвращаем два рандомных числа до 1000

	srand(time(NULL));
	for(int i = 0; i < 2; i++)
		script.Return<int>(rand()%1000);

	// Вовзращаем 2 значения
	return 2;
}

int GetLotOfRandomNumbers(lua_State*)
{
	// Возвращаем много рандомных чисел до 1000

	srand(time(NULL));
	for(int i = 0; i < script.GetArgument<int>(1); i++)
		script.Return<int>(rand()%1000);

	// Вовзращаем столько значений, сколько задано в аргументе
	return script.GetArgument<int>(1);
}

int main()
{
	script.Create();

	script.RegisterConstant<lua_CFunction>(Write,"Write");
	script.RegisterConstant<lua_CFunction>(GetString,"GetString");
	script.RegisterConstant<lua_CFunction>(Message,"Message");
	script.RegisterConstant<lua_CFunction>(GetTwoRandomNumbers,"Rand1");
	script.RegisterConstant<lua_CFunction>(GetLotOfRandomNumbers,"Rand2");

	script.Array();
	script.RegisterConstantArray<int>(1,1);
	script.RegisterConstantArray<int>(2,2);
	script.RegisterConstantArray<int>(3,3);
	script.RegisterConstantArray<int>(4,4);
	script.RegisterArray("mass");

	script.DoFile("script.lua");
	script.Close();

	// Пауза после скрипта
	_getch();
}

for i = 1, 4 do
	Write(i, "\n", "Hier kommt die Sonne", "\n")
end

Write(2*100-1)

Message("Привет!")

a, b = Rand1()
Write(a, "\n", b, "\n")
Write(Rand1(), "\n")

a, b, c, d = Rand2(4)
Write(a, "\n", b, "\n", c, "\n", d, "\n")

return 1


Полезные советы

  • Для класса Script все равно, в каком расширении находится скрипт, хоть в .txt, хоть в .lua, хоть в .bmp, просто .lua открывается множеством редакторов именно ЯП Луа
  • Используйте редакторы Lua кода, очень трудно писать код, можно забыть написать end, do, либо что-нибудь еще. Программа из-за ошибки в луа скрипте не вылетит, но просто не выполнит код
  • Lua может оказаться намного гибче, чем вам могло показаться. К примеру, числа свободно преобразуются в строки, он нетипизирован. Если передать в функцию 100 параметров, а она в С++ считывает только первые 2, то программа не вылетит. Есть еще много подобных допущений.

Вопросы и ответы

  • Вопрос: Почему мы не используем луа стейт, который есть в каждой подобной функции — int foo(lua_State* L)?
    Ответ: За всю программу мы используем только один стейт в Script, где регистрируем функции, инициализируем его и делаем прочие штучки. К тому же просто невыгодно было бы, написав целый класс, опять обращаться начистоту к lua_State через lua_pushboolean и прочие функции.


Полный листинг Script.h и Script.cpp
Script.h
#ifndef _SCRIPT_H_
#define _SCRIPT_H_

#pragma comment(lib,"lua.lib")
extern "C"
{
	#include <lua.h>
	#include <lualib.h>
	#include <lauxlib.h>
}

class Script
{
private:
    lua_State *lua_state;

public:
	void Create();
	void Close();
	int DoFile(char* ScriptFileName);
	template<class t>
	void RegisterConstant(T value, char* constantname);

	void Array();
	template<class t>
	void RegisterConstantArray(T value, int index);
	void RegisterArray(char* arrayname);

	int GetArgumentCount();
	template<class t>
	T GetArgument(int index);
	template<class t>
	void Return(T value);
};

#endif

Я удалил инклуды для работы с консолью
Script.cpp
#include "Script.h"

void Script::Create()
{
	lua_state = luaL_newstate();
	
	static const luaL_Reg lualibs[] = 
	{
		{"base", luaopen_base},
		{"io", luaopen_io},
		{NULL, NULL}
	};

	for(const luaL_Reg *lib = lualibs; lib->func != NULL; lib++)
	{
		luaL_requiref(lua_state, lib->name, lib->func, 1);
		lua_settop(lua_state, 0);
	}
}

void Script::Close()
{
	lua_close(lua_state);
}

int Script::DoFile(char* ScriptFileName)
{
	luaL_dofile(lua_state,ScriptFileName);

	return lua_tointeger(lua_state, lua_gettop(lua_state));
}

template<>
void Script::RegisterConstant<int>(int value, char* constantname)
{
	lua_pushinteger(lua_state, value);
	lua_setglobal(lua_state,constantname);
}

template<>
void Script::RegisterConstant<double>(double value, char* constantname)
{
	lua_pushnumber(lua_state, value);
	lua_setglobal(lua_state,constantname);
}

template<>
void Script::RegisterConstant<char>(char* value, char* constantname)
{
	lua_pushstring(lua_state, value);
	lua_setglobal(lua_state,constantname);
}

template<>
void Script::RegisterConstant<bool>(bool value, char* constantname)
{
	lua_pushboolean(lua_state, value);
	lua_setglobal(lua_state,constantname);
}

template<>
void Script::RegisterConstant<lua_cfunction>(int(*value)(lua_State*), char* constantname)
{
	lua_pushcfunction(lua_state, value);
	lua_setglobal(lua_state,constantname);
}


void Script::Array()
{
    lua_createtable(lua_state, 2, 0);
}


template<>
void Script::RegisterConstantArray<int>(int value, int index)
{
	lua_pushnumber(lua_state, index);
	lua_pushinteger(lua_state, value);
	lua_settable(lua_state, -3);
}

template<>
void Script::RegisterConstantArray<double>(double value, int index)
{
	lua_pushnumber(lua_state, index);
	lua_pushnumber(lua_state, value);
	lua_settable(lua_state, -3);
}

template<>
void Script::RegisterConstantArray<char>(char* value, int index)
{
	lua_pushnumber(lua_state, index);
	lua_pushstring(lua_state, value);
	lua_settable(lua_state, -3);
}

template<>
void Script::RegisterConstantArray<bool>(bool value, int index)
{
	lua_pushnumber(lua_state, index);
	lua_pushboolean(lua_state, value);
	lua_settable(lua_state, -3);
}

template<>
void Script::RegisterConstantArray<lua_cfunction>(lua_CFunction value, int index)
{
	lua_pushnumber(lua_state, index);
	lua_pushcfunction(lua_state, value);
	lua_settable(lua_state, -3);
}

void Script::RegisterArray(char* arrayname)
{
	lua_setglobal(lua_state, arrayname);
}

int Script::GetArgumentCount()
{
	return lua_gettop(lua_state);
}

template<>
int Script::GetArgument<int>(int index)
{
	return lua_tointeger(lua_state,index);
}

template<>
double Script::GetArgument<double>(int index)
{
	return lua_tonumber(lua_state,index);
}

template<>
char* Script::GetArgument<char>(int index)
{
	return (char*)lua_tostring(lua_state,index);
}

template<>
bool Script::GetArgument<bool>(int index)
{
	return lua_toboolean(lua_state,index);
}


template<>
void Script::Return<int>(int value)
{
	lua_pushinteger(lua_state,value);
}

template<>
void Script::Return<double>(double value)
{
	lua_pushnumber(lua_state,value);
}

template<>
void Script::Return<char>(char* value)
{
	lua_pushstring(lua_state,value);
}

template<>
void Script::Return<bool>(bool value)
{
	lua_pushboolean(lua_state,value);
}


Репозиторий с lib'ой и includ'ами: https://github.com/Izaron/LuaForHabr

Все вопросы посылайте мне в ЛС, либо в этот топик, либо, если вам не повезло быть зарегистрированным на хабре — на мейл izarizar@mail.ru

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 33

    0
    Нехорошо-то как! Редактирую пост несколько раз, выпускаю его на публичное осмотрение, а там автозавершение — вот такое было
    	script.DoFile("script.lua");
    	script.Close();
    
    	// Пауза после скрипта
    	_getch();
    }
    </lua_cfunction></lua_cfunction></lua_cfunction></lua_cfunction></lua_cfunction></int></int></char></char></time.h></windows.h></conio.h>

      0
      Хорош Lua, но не по душе мне паскалеподобный синтаксис, в свое время я выбрал язык Squirrel для своих скриптов.
        +3
        Тогда, когда дело касается скорости и потребления памяти — люди выбирают LuaJIT. На самом деле он еще быстрее чем V8. Может синтаксис и не очень, но когда дело касается производительности, то выбирать только из LuaJIT или V8
        Пруф 1(и замечу что там не LuaJIT, а Lua)
        Пруф 2(не связанный с Squirrel, для сравнения Lua и LuaJIT но все равно информативный)
          0
          Часто использую Luajit в веб проектах, скорость работы впечатляет. А к синтаксису можно привыкнуть.
            0
            Если сильно хочется, можно просто переписать лексер. Благо что там исходники очень аккуратные и весьма понятные.
          0
          А как вам мой вариант? habrahabr.ru/post/165765/
          Я писал библиотеку, пытаясь облегчить написание биндингов, и, обходясь только C++11, не используя кодогенераторы.

          А вообще, попробуйте swig, в нем с поддержкой lua, конечно, не всё гладко, но возможности впечатляют.
            0
            Есть либы для биндинга с boost::bind, там все давно работает.
              0
              Есть проекты, в которые, в силу определенных причин, не хочется/нельзя тащить мега_клевый_фреймворк_для_всего, чтобы под ним не подразумевалось, поэтому, по моему, хорошо, что есть много различных решений одной и той же задачи.
            0
            Кажется, автозавершение кода забаговано.
            Иначе куда делись template<>, раз вместо них стоит template? :)
              +1
              Замените @, <, > на мнемоники ( &#64;, &#60;, &#62;).
                0
                Дельный совет
                +1
                Да, какие-то странные артефакты наблюдаются. C++666?

                // Тут мы считываем количество аргументов и каждый аргумент выводим
                for(int i = 1; i (i);
                cout > str;
                  +1
                  Высшим разумом Хабра некоторый код был страшно покоцан. Завтра займусь исправлением этого мракобесия со смещением циклов, строк и даже пары функций (сейчас сижу с телефона)
                    0
                    Ну и тогда вот в этих (и подобных) объявлениях приведите регистр (t/T) к общему знаменателю:

                    template<class t> void Return(T value);
                –4
                Если кроссплаторменность в проекте не требуется (работаем только на Windows), то лучше использовать Windows Scripting Host для таких вещей. VBScript и JScript сразу идут из коробки + много других языков можно подключить, хоть даже Perl.
                  0
                  Видимо требуется кроссплатформенность :)
                    0
                    Судя по #include <conio.h>, таки не требуется.
                  +10
                  Критика.
                  1. Зачем использовать <windows.h> и <cohio.h>? Кроссплатформенность напрочь убивается, да и они здесь ни к чему.
                  2. Почти нет проверок на ошибки. Если что-то не работает, код не обеспечивает безопасное выполнение.
                  3. Script::Return вводит в замешательство. Название функции обещает что-то вернуть, а в итоге ничего не возвращает.
                  4. Вы пишете «С++ на приличном уровне (в уроке будут шаблоны — template)», а потом рассказываете про include guard'ы и прочие банальности. Думаю, что человеку, знающему C++ на «приличном уровне», это давно известно.
                  5. «Если непонятно, что это значит, то тамошная таблица все равно что массив». Таблица в Lua — куда больше, чем просто массив.
                  6. «Используйте редакторы Lua кода». Должно быть «используйте редакторы с подстветкой Lua кода». Многие пишут в них и не жалуются. А ещё можно в repl запускать, чтобы проверить на ошибки до запуска из под C++.

                  А ещё можно долго придираться к стилю и другим неточностям, но хватит пока что и шести пунктов выше.
                    0
                    1. Возможно, windows.h не стоил одного MessageBox, но мне показалось, что так будет эффективней результат. P.S. никогда не делал кроссплатформ в Visual Studio
                    2. Луа сам по себе безопасен, если в скрипте внезапно есть ошибка, он не выполнится в 100% случаев. Разве что стоит добавить проверку на существование скрипта в папке с программой
                    3. Эта функция возвращает значение в луа функцию. А обычный return n; в функции для регистрации всего-то показывает через n количество возвращаемых значений
                    4. Новичку это поможет, а вы потратили 10 секунд на пропуск таких объяснений :)
                    5. Ну да, ну да, я в курсе про мощь таблицы, реализацию ООП в луа и проч. Впрочем, спасибо за замечание, я об этом упомяну
                    6. Думаю, 95% сидящих тут, правильно меня поняли.

                    Без проблем, придирайтесь :)
                    А стиль кода это просто глупо, у всех свои вкусы, и я не собираюсь доказывать, что пишу правильный труЪ код
                      +3
                      1. Код не стоит привязывать к системе, а тем более к IDE, когда это возможно. Можно разрабатывать на том же Visual Studio, но компилировать с помощью других компиляторов под остальные системы.
                      2. Да ладно? Неужели ни разу не видели API panic в Lua? luaL_loadfile, возвращающее bool, lua_pcall не созданы просто так. Да и пользователя ведь надо оповещать о том, что произошло что-то плохое, не стоит надеяться лишь на то, что скрипт не выполнится.
                      3. Тогда нужно придумать название получше. «pushToLua», хотя бы.
                      4. Вы противоречите сами себе. Сначала указываете, что нужно приличное знание языка (некоторые новички статью на этом дальше не читают), а потом вдруг оказывается, что она нацелена на новичков.

                      «у всех свои вкусы»
                      Тут не только дело вкуса, иначе бы не существовало стандартов, и все программировали, как им захочется. Стиль обеспечивает читаемость. Почитайте тот же Code Complete — поймёте, в чём дело.
                        0
                        Понимаете, тут дело не в субъективном стиле, а во вполне объективных граблях. Вещи вроде using namespace в заголовочном файле, Create/Close вместо конструктора и деструктора (и ладно бы еще они коды ошибок возвращали — так они у вас void), использование идентификаторов вида _SCRIPT_H_ (FYI, идентификаторы, начинающиеся с подчеркивания, за которым следует заглавная буква — зарезервированы для компилятора и стандартной библиотеки, причем последняя в VC++ этим активно пользуется) и т.д. Это реально просто плохой C++, и когда такое подается в качестве туториала, пусть и не по плюсам — это плохо, потому что это будут читать новички, и учиться делать так же.
                      0
                      Скриптовый язык это классно, но необходимость общения через виртуальную машину съедает все профиты для среднесложного проекта. Проще написать какой-нибудь редактор XML и свой рудиментарный псевдоязык, который будет уметь десяток простых комманд, чем биндить в VM скриптового языка весь набор нужных классов, функций и объектов.
                      Отчасти поэтому многие не сильно сложные игры пишутся целиком на Lua.
                        0
                        «Проще написать какой-нибудь редактор XML и свой рудиментарный псевдоязык».
                        1. XML не поддерживает и десятой части возможности Lua. Да и вообще их сравнивать нельзя. XML — язык разметки, а Lua — скриптовый язык.
                        2. Если бы все профиты съедались, то такое огромное количество игр не использовали бы Lua.

                        Lua используется в основном на этапе загрузки чего-либо(использовать его в частях программы, где требуется производительность, глупо, конечно), где лучше пожертвовать сотней миллисекунд времени работы программы, чем создавать глюченный и нестабильный собственный язык(в отличие от языка, написанного на чистом C, который разрабатывается уже 20 лет), от которого будет больше проблем, чем профита. Ещё Lua часто используют того, чтобы была возможность писать часть кода на нём, при этом не рекомпилируя каждый раз проект. Когда у вас есть огромный проект, который компилируется несколько часов, то легче уже написать часть кода на Lua, чтобы избежать рекомпиляции.
                          0
                          1. XML не поддерживает и десятой части возможности Lua. Да и вообще их сравнивать нельзя. XML — язык разметки, а Lua — скриптовый язык.

                          Поэтому я и написал — XML и свой рудиментарный псевдоязык. Первый для загрузки данных и всего того что описал автор:
                          В игре создается больше оружия, больше диалогов, больше меню, больше etc.
                          Одна из самых главных проблем, возникающих в связи с нововведениями — поддерживать бессчетное множество оружия и бейджиков довольно сложное занятие.


                          Второй — для собственных минискриптов типа простых реакций на триггеры.

                          Когда у вас есть огромный проект, который компилируется несколько часов,

                          Когда у вас есть огромный проект, действуют совсем другие законы. Я говорил о своей собственной колокольне, о проектах которые пишутся силами 1-2 программиста.
                            0
                            Из собственного опыта, после поддержки подобного велосипеда год-два, он постепенно обрастает свистелками и прочим до такой степени, что все равно получается полноценная VM. Только не в пример более забагованная, чем Lua.
                          –1
                          Не особо то и съедает, когда есть предварительная компиляция в машинный код на этапе загрузки.
                            +1
                            Каким образом компиляция в байткод спасает от необходимости писать 100500 биндеров для игровых классов и функций?
                          0
                          Как можно в 21м веке использовать методы Create и Close вместо конструктора с деструктором?
                            +1
                            В конструкторе лучше не делать всякие вещи в которых возможны ошибки. А методы create ( init и т.п. ) один из методов борьбы с ними.
                              +1
                              А зачем тогда умные указатели придумали? Без конструкторов и деструкторов нет RAII никакого и в случае исключения Close уже никогда и не вызовется.
                                +1
                                В конструкторе можно смело делать всякие вещи, в которых возможны ошибки. Просто при этом надо кидать исключения.
                              +3
                              Первая мысль — написать свой интерпретатор своего скриптового языка, выкидывается из мозга через несколько секунд. Логика игрока определенно не стоит таких жутких затрат.


                              Тут есть подвох. Ядро языка писать несложно, никаких больших затрат там не случается. А вот «вывести концы» собсвтенно объектной модели так, чтобы ей мог с удовольствием пользоваться скриптер, — вот это задача намного более сложная и интеллектоемкая. И она не решается простой прикруткой Луа, Питона или чего угодно еще.

                              Первый мой опыт в этой области касался AngelScript. Ну, это было давно, Луа еще был не на слуху, а вот этот ангельский скриптец казался нам тогда вполне годным. К тому же у него был сиподобный синтаксис, что с нашей программистской точки зрения было несомненным плюсом. Мы прикрутили скриптовый движок и тупо вывели интерфейсы один в один, но не все. Получилось, что у нас есть язык, на котором предполагается писать логику игры, который хуже чем плюсы, имеет меньше возможностей, чем плюсы, но при этом похож на плюсы и чтобы пользоваться им, надо знать плюсы. Скриптеры посмотрели на это дело, сказали: «как-нибудь в другой раз». В итоге сцены на всем этом мы же и писали, потому что больше некому.

                              Следующий опыт касался как раз Луа. Прикрутили Луа, дали скриптерам, они засели и быстро-быстро настрочили тонну говнокода. Им положено, они не программисты, они геймдизайнеры, им по штату положено делать сцены, а не код рефакторить. Естественно, вскоре застряли во всем этом и опять же разгребать все пришлось нам, программистам.

                              А вот в третий раз от меня ненадолго отвернулось начальство, и, пока никто не видит, я внезапно написал собственный язык с блекджеком и шлюхами. Точнее, писал его не я, я только программировал. Сам язык создавал по-сути Женя Гомельский, геймдизайнер, который просто знал, чего хочет и умел это сформулировать. Язык получился местами очень даже ничего. Там были предлоги, похожие на именнованные параметры в Обджектив-Си, местоимение «it», как в Хаскелевском ghci, метапрограммирование в стиле миксинов Ди, и даже некоторые уникальные вещи, как, например, переменная по умолчанию. Ну, можно было, например, написать цикл так: [for {i=0} {i++; i<10}], а можно так: [10x]. Во втором случае счетчиком цикла считалась переменная по умолчанию "?".

                              Мы выпустили с ним пару проектов, вполне между прочим успешных, а потом перешли на новую платформу и забросили скрипты вообще в пользу машины состояний и визуального программирования. И, честно сказать, это было определенное облегчение. Писать языки надо уметь, я не умею. Кроме красивых фичей, он закономерно оброс костылями и велосипедами. Если код на языке был вполне даже читаем, то код самого интерпретатора заболотило по самые осины. Его стало очень сложно поддерживать. Причем, не само ядро, конечно, там-то как раз ничего сложного изначально не было, а вот все эти «концы». Например, в одном проекте меню выезжает справа, в другом — снизу, и в язык попадают, условно говоря, две взаимоисключающие функции для красивого выезда меню. И вот так вот все.

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

                              А туториал годный, кстати.
                                +2
                                Плохая статья! Автор, для кого была написана эта статья? Если для тех к то не знаком с Lua, то она вообще ничего не объясняет и только запутает. Если для тех кто знаком с Lua, то тут вообще ничего полезного кроме как боле менее удобный способ получение аргументов функции (удобнее чем просто из стека руками вытаскивать), который приводится в любой статье про Lua + C++.

                                Давайте честно, если вы собираетесь вытащить бизнес логику в скрипты то наверно вам как минимум нужно перебросить на ту сторону игровые объекты, что бы из скриптов можно было бы работать именно с объектами а не свободными функциями. Я бы мог продолжать дальше, но это будет слишком долгий список.

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

                                Only users with full accounts can post comments. Log in, please.