Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

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

Anna | 24.06.2014 | нет комментариев

Решил написать данный топик на тему скриптов

Что необходимо знать?

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

Отчего писать диалоги игры в .cpp файле было огромный оплошностью

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

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

Решение задачи

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

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

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

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

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

Я готов!

Когда вы осознали превосходства скриптовых языков программирования, самое время начать трудиться!
Скачайте из репозитория на гитхабе (низ топика) 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-й довод начинается с 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 (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 (i);
	cout > 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 (rand()%1000);

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

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

	srand(time(NULL));
	for(int i = 0; i (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.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

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB