
Портирование игры для INSTEAD во flash-приложение
В данной статье я хочу рассмотреть процесс портирования игры для движка INSTEAD во flash-приложение. Портировать будем обучающий пример (tutorial3), который поставляется вместе с исходными кодами INSTEAD. В конце статьи я также опишу некоторые проблемы, с которыми пришлось столкнуться при портировании самого движка на flash-платформу.
Для начала несколько слов о движке INSTEAD. INSTEAD — движок для написания текстовых квестов (хотя есть и графические игры типа lines, пасьянс). Основная часть кода написана на lua, что позволяет достаточно просто портировать его на различные платформы.
Репозиторий
При портировании игры нас интересует папка games и файлы SoundManager.as, InsteadPicture.as и FS.as (иногда задействуются файлы InsteadImage.as и InsteadLink.as).
В папке games будут находится файлы нашей игры, включая картинки и звуки.
В файле SoundManager.as мы интегрируем звуки и музыку игры во flash-приложение.
В файле InsteadPicture.as мы интегрируем изображения игры во flash-приложение.
В файле FS.as мы загрузим необходимые stead-модули и файлы игры.
В файлах InsteadImage.as и InsteadLink.as указываются графические файлы, которые будут отображаться в текстовой части игрового экрана.
Теперь обо всем по порядку.
Все звуковые файлы хранятся в объекте mus[] = {}. Встраиваются файлы как обычно. Пример приведен как для mp3 файлов, так и для mod.
Добавление происходит в методе init().
Соответственно для добавления своих треков нужно встроить их во flash-приложение с помощью Embed и добавить в объект mus.
Встраивание файлов производится стандартно, изображения хранятся в объекте gameFiles. Как ключ объекта gameFiles используется строка, с указанием пути к файлу относительно файла main.lua игры.
Если вместе вместе с текстом должны отображаться картинки, они должны быть прописаны в файле InsteadImage.as и InsteadLink.as.
Код InsteadImage.as
Код InsteadLink.as
Как видно мы просто создаем экземпляры изображений в конструкторах InsteadImage и InsteadLink и помещаем их в объект gameFiles.
Встраивание файлов игры и модулей stead производится стандартно с помощью Embed. Передача их в виртуальную файловую систему производится в методе Init — libInitializer.supplyFile(«builtin://_путь_к_файлу_относительно_корневой_папки_игры_», new _имя_класса_встроенного_файла_() as ByteArray).
Интерпретатор lua-alchemy может использовать файлы, переданные ему с помощью функции supplyFile статического объекта LuaAlchemy.libInit. Для этого создадим класс FS со статическим методом Init:
Теперь остается только инициализировать интерпретатор:
и мы можем вызывать file.lua из интерпретатора, например dofile('file.lua').
Список добавленных функций:
Репозиторий lua-alchemy с поддержкой вышеназванных функций на github.
Dennis Kolyako реализовал отличный класс для захвата событий колеса мыши в AS3 проектах путем встраивания AS2 movie в AS3 movie. Это лучшее решение на сегодня.
Всего в паблик было выложено две игры (ссылки на Newgrounds):
Возвращение квантового кота
Побег из туалета
Ссылки не для пиара, а для демонстрации работоспособности проекта.
В игре «Побег из туалета» также используется парсер HTML-like кода, который генерируется движком INSTEAD. Решение использовать собственный парсер связанно с тем, что у TextField ужасная поддержка тэга img.
Автора движка INSTEAD Петра Косых
Автора класса MouseWheel Дениса Коляко
Авторов проекта lua-alchemy
Авторов проекта Flod
Автора класса SoundManager Олега Антипова
Для начала несколько слов о движке INSTEAD. INSTEAD — движок для написания текстовых квестов (хотя есть и графические игры типа lines, пасьянс). Основная часть кода написана на lua, что позволяет достаточно просто портировать его на различные платформы.
Портирование обучающего примера
Рассмотрим структуру нашего проектаРепозиторий
[DIR]InsteadFlash [DIR]adds [DIR]stead // в этой директории содержатся файлы lua-части движка INSTEAD ... flash.lua ... gui_flash.lua ... [DIR]theme [DIR]code Fish.xml PreloaderDum.as [DIR]neoart [DIR]flod // файлы проект FLOD (реализация классов для проигрывания mod файлов во flash) ... ModProcessor.as // модифицированный файл (метод play возвращает объект SoundTransform, добавлен сеттер soundTransform) ... [DIR]com [DIR]fish AMainClass.as i_Button.as [DIR]instead // порт кода INSTEAD на AS3 FS.as Game.as Instead.as InsteadArgs.as InsteadCursor.as InsteadEvent.as InsteadImage.as InsteadLink.as InsteadPicture.as InsteadScroll.as InsteadText.as Main.as SaveSlot.as [DIR]screens // информационные экраны About.as Pause.as [DIR]games [DIR]tutorial3 // код портируемой INSTEAD игры (оригинальные lua-файлы) main-en.lua main-es.lua main-it.lua main-ru.lua main-ua.lua main.lua [DIR]mmg MouseWheel.as // класс для захвата мыши в AS3 проектах (см. Проблемма №3) SoundManager.as // модифицированный класс для проигрывания музыки и звуков Олега Антипова (добавлена поддержка mod файлов)
При портировании игры нас интересует папка games и файлы SoundManager.as, InsteadPicture.as и FS.as (иногда задействуются файлы InsteadImage.as и InsteadLink.as).
В папке games будут находится файлы нашей игры, включая картинки и звуки.
В файле SoundManager.as мы интегрируем звуки и музыку игры во flash-приложение.
В файле InsteadPicture.as мы интегрируем изображения игры во flash-приложение.
В файле FS.as мы загрузим необходимые stead-модули и файлы игры.
В файлах InsteadImage.as и InsteadLink.as указываются графические файлы, которые будут отображаться в текстовой части игрового экрана.
Теперь обо всем по порядку.
Интегрирование музыки и звуков (исходный код файла SoundManager.as приведен не полностью)
package mmg
{
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.media.SoundTransform;
import flash.events.Event;
import flash.events.TimerEvent;
import flash.utils.Timer;
import flash.utils.ByteArray;
import neoart.flod.*;
dynamic public class SoundManager extends Object
{
[Embed(source='../../adds/theme/click.mp3')]
public static const click:Class;
[Embed(source='../games/tutorial3/ramparts.mp3')]
public static const ramparts_mp3:Class;
[Embed(source='../games/tutorial3/ramparts.mod', mimeType = "application/octet-stream")]
public static const ramparts_mod:Class;
private static var rampartsProcessor:ModProcessor = new ModProcessor();
private static var mus:Object; // хэш с музыкой
static public var vol:Number=0.9; // громкость
static public var volFadeSpeed:Number=0.05; //Скорость фейдинга
static public var musEnable:Boolean=true; // включёна ли музыка
static public var curMusName:String=""; // Имя текущего трека
//внутренние переменные
private static var curCh:SoundChannel; //Текущий канал
private static var offCh:SoundChannel; //Затухающий канал
private static var curVol:Number=0;
private static var curPos:Number=0;
private static var offVol:Number=0;
private static var isplayed:Boolean=false;
private static var timerVolFader:Timer= new Timer(10);
private static var t1:SoundTransform = new SoundTransform();
private static var t2:SoundTransform = new SoundTransform();
static public function init():void
{
timerVolFader.stop();
// создадим объект с музыкой
mus = new Object();
//в качестве ключа объекта mus (в квадратных скобках) указываем путь относительно файла main.lua
//имя файла оставляем оригинальным, даже если файл был сконвертирован в другой формат
//Здесть втыкаем мод треки (если используем mod файл)
rampartsProcessor.load(new ramparts_mod as ByteArray);
mus["ramparts.mod"] = rampartsProcessor; // оставить нужно один из вариантов
//Здесь втыкаем свои муз треки (если сконвертировали mod в mp3)
mus["ramparts.mod"] = new ramparts_mp3(); // оставить нужно один из вариантов (этот я закомментировал при сборке)
mus["click"] = new click();
}
...
часть исходного кода класса скрыта
...
}
}
Все звуковые файлы хранятся в объекте mus[] = {}. Встраиваются файлы как обычно. Пример приведен как для mp3 файлов, так и для mod.
Добавление происходит в методе init().
Соответственно для добавления своих треков нужно встроить их во flash-приложение с помощью Embed и добавить в объект mus.
Интегрирование графических файлов (исходный код классов InsteadPicture.as, InsteadImage.as и InsteadLink.as приведен частично)
package com.fish.instead
{
import flash.display.Sprite;
import flash.display.Bitmap;
import flash.events.Event;
public class InsteadPicture extends Sprite
{
// graphic files
[Embed(source='../../../games/tutorial3/instead.png')]
public static const instead:Class;
[Embed(source='../../../games/tutorial3/ru.png')]
public static const ru:Class;
[Embed(source='../../../games/tutorial3/ua.png')]
public static const ua:Class;
[Embed(source='../../../games/tutorial3/es.png')]
public static const es:Class;
[Embed(source='../../../games/tutorial3/gb.png')]
public static const gb:Class;
[Embed(source='../../../games/tutorial3/it.png')]
public static const it:Class;
// graphic files
public static var gameFiles:Object = new Object();
public var containerMid:Number = 0;
private var hiding:Array = new Array;
private var showing:Array = new Array;
public var fading:Number = 0;
public function InsteadPicture()
{
gameFiles["instead.png"] = new instead;
gameFiles["ru.png"] = new ru;
gameFiles["ua.png"] = new ua;
gameFiles["es.png"] = new es;
gameFiles["gb.png"] = new gb;
gameFiles["it.png"] = new it;
for each (var value:Bitmap in gameFiles)
{
// iterates through each value
addChild(value);
value.visible = false;
value.alpha = 0;
}
this.height = 0;
addEventListener(Event.ENTER_FRAME, DispatchPictures);
}
...
часть исходного кода класса скрыта
...
}
}
Встраивание файлов производится стандартно, изображения хранятся в объекте gameFiles. Как ключ объекта gameFiles используется строка, с указанием пути к файлу относительно файла main.lua игры.
Если вместе вместе с текстом должны отображаться картинки, они должны быть прописаны в файле InsteadImage.as и InsteadLink.as.
Код InsteadImage.as
...
public function InsteadImage(imgStr:String)
{
gameFiles["ru.png"] = new InsteadPicture.ru;
gameFiles["ua.png"] = new InsteadPicture.ua;
gameFiles["es.png"] = new InsteadPicture.es;
gameFiles["gb.png"] = new InsteadPicture.gb;
gameFiles["it.png"] = new InsteadPicture.it;
...
Код InsteadLink.as
...
public function InsteadLink(linkStr:String, isImage:Boolean = false, parent:Sprite = null)
{
gameFiles["ru.png"] = new InsteadPicture.ru;
gameFiles["ua.png"] = new InsteadPicture.ua;
gameFiles["es.png"] = new InsteadPicture.es;
gameFiles["gb.png"] = new InsteadPicture.gb;
gameFiles["it.png"] = new InsteadPicture.it;
...
Как видно мы просто создаем экземпляры изображений в конструкторах InsteadImage и InsteadLink и помещаем их в объект gameFiles.
Интегрирование игровых файлов и модулей stead (исходный код файла FS.as)
package com.fish.instead
{
import flash.utils.ByteArray;
import flash.display.Bitmap;
public class FS
{
[Embed(source='../../../../adds/stead/stead.lua', mimeType="application/octet-stream")]
public static const stead_lua:Class;
[Embed(source='../../../../adds/stead/gui.lua', mimeType="application/octet-stream")]
public static const gui_lua:Class;
[Embed(source='../../../../adds/stead/flash.lua', mimeType="application/octet-stream")]
public static const flash_lua:Class;
[Embed(source='../../../../adds/stead/goto.lua', mimeType="application/octet-stream")]
public static const goto_lua:Class;
[Embed(source='../../../../adds/stead/format.lua', mimeType="application/octet-stream")]
public static const format_lua:Class;
[Embed(source='../../../../adds/stead/vars.lua', mimeType="application/octet-stream")]
public static const vars_lua:Class;
[Embed(source='../../../../adds/stead/object.lua', mimeType="application/octet-stream")]
public static const object_lua:Class;
[Embed(source='../../../../adds/stead/dash.lua', mimeType="application/octet-stream")]
public static const dash_lua:Class;
[Embed(source='../../../../adds/stead/para.lua', mimeType="application/octet-stream")]
public static const para_lua:Class;
[Embed(source='../../../../adds/stead/quotes.lua', mimeType="application/octet-stream")]
public static const quotes_lua:Class;
[Embed(source='../../../../adds/stead/xact.lua', mimeType="application/octet-stream")]
public static const xact_lua:Class;
[Embed(source='../../../../adds/stead/timer.lua', mimeType="application/octet-stream")]
public static const timer_lua:Class;
// lua game files
[Embed(source='../../../games/tutorial3/main.lua', mimeType="application/octet-stream")]
public static const tutorial_main_lua:Class;
[Embed(source='../../../games/tutorial3/main-en.lua', mimeType="application/octet-stream")]
public static const tutorial_main_en_lua:Class;
[Embed(source='../../../games/tutorial3/main-es.lua', mimeType="application/octet-stream")]
public static const tutorial_main_es_lua:Class;
[Embed(source='../../../games/tutorial3/main-it.lua', mimeType="application/octet-stream")]
public static const tutorial_main_it_lua:Class;
[Embed(source='../../../games/tutorial3/main-ru.lua', mimeType="application/octet-stream")]
public static const tutorial_main_ru_lua:Class;
[Embed(source='../../../games/tutorial3/main-ua.lua', mimeType="application/octet-stream")]
public static const tutorial_main_ua_lua:Class;
public static var gameFiles:Object = new Object();
private static var _filesystemRoot:String;
public static function filesystemRoot():String
{
return _filesystemRoot;
}
public static function Init(libInitializer:*):void
{
// game files
libInitializer.supplyFile("builtin://main.lua", new tutorial_main_lua() as ByteArray);
libInitializer.supplyFile("builtin://main-en.lua", new tutorial_main_en_lua() as ByteArray);
libInitializer.supplyFile("builtin://main-es.lua", new tutorial_main_es_lua() as ByteArray);
libInitializer.supplyFile("builtin://main-it.lua", new tutorial_main_it_lua() as ByteArray);
libInitializer.supplyFile("builtin://main-ru.lua", new tutorial_main_ru_lua() as ByteArray);
libInitializer.supplyFile("builtin://main-ua.lua", new tutorial_main_ua_lua() as ByteArray);
// stead modules
libInitializer.supplyFile("builtin://goto.lua", new goto_lua() as ByteArray);
libInitializer.supplyFile("builtin://format.lua", new format_lua() as ByteArray);
libInitializer.supplyFile("builtin://vars.lua", new vars_lua() as ByteArray);
libInitializer.supplyFile("builtin://object.lua", new object_lua() as ByteArray);
libInitializer.supplyFile("builtin://dash.lua", new dash_lua() as ByteArray);
libInitializer.supplyFile("builtin://para.lua", new para_lua() as ByteArray);
libInitializer.supplyFile("builtin://quotes.lua", new quotes_lua() as ByteArray);
libInitializer.supplyFile("builtin://xact.lua", new xact_lua() as ByteArray);
libInitializer.supplyFile("builtin://timer.lua", new timer_lua() as ByteArray);
_filesystemRoot = "builtin://";
}
}
}
Встраивание файлов игры и модулей stead производится стандартно с помощью Embed. Передача их в виртуальную файловую систему производится в методе Init — libInitializer.supplyFile(«builtin://_путь_к_файлу_относительно_корневой_папки_игры_», new _имя_класса_встроенного_файла_() as ByteArray).
Проблемы, возникшие при портировании движка на flash-платформу
Проблема №1. Lua-alchemy и встраиваемые файлы
На момент написания я не нашел примера использования встроенных файлов в lua-alchemy, тем более на русском языке. Это я и постараюсь сейчас исправить.Интерпретатор lua-alchemy может использовать файлы, переданные ему с помощью функции supplyFile статического объекта LuaAlchemy.libInit. Для этого создадим класс FS со статическим методом Init:
package
{
import flash.utils.ByteArray;
import flash.display.Bitmap;
public class FS
{
[Embed(source='file.lua', mimeType="application/octet-stream")]
public static const file_lua:Class;
private static var _filesystemRoot:String;
public static function filesystemRoot():String
{
return _filesystemRoot;
}
public static function Init(libInitializer:*):void
{
libInitializer.supplyFile("builtin://file.lua",
new file_lua() as ByteArray);
_filesystemRoot = "builtin://";
}
}
}
Теперь остается только инициализировать интерпретатор:
FS.Init(LuaAlchemy.libInit);
lua = new LuaAlchemy(FS.filesystemRoot());
и мы можем вызывать file.lua из интерпретатора, например dofile('file.lua').
Проблема №2. Отсутствующие core-функции lua-интерпретатора в lua-alchemy
Изначально lua-alchemy не поддерживает вызов некоторых функций lua-интерпретатора из ActionScript.Список добавленных функций:
- public function luaPop(n:uint):void
- public function luaGetTop():int
- public function luaGetGlobal(name:String):void
- public function luaGetField(index:int, name:String):void
- public function luaRemove(index:int):void
- public function luaPCall(nargs:int, nresults:int, errfunc:int):int
- public function luaToBoolean(index:int):Boolean
- public function luaToString(index:int):String
- public function luaPushNil():void
- public function luaPushNumber(value:int):void
- public function luaPushBoolean(value:int):void
- public function luaPushString(value:String):void
Репозиторий lua-alchemy с поддержкой вышеназванных функций на github.
Проблема №3. Прокручивание всей страницы при использовании колесика мыши во Flash-приложении
Эта проблема думаю известна многим, но способ борьбы с ней я нашел не сразу.Dennis Kolyako реализовал отличный класс для захвата событий колеса мыши в AS3 проектах путем встраивания AS2 movie в AS3 movie. Это лучшее решение на сегодня.
Всего в паблик было выложено две игры (ссылки на Newgrounds):
Возвращение квантового кота
Побег из туалета
Ссылки не для пиара, а для демонстрации работоспособности проекта.
В игре «Побег из туалета» также используется парсер HTML-like кода, который генерируется движком INSTEAD. Решение использовать собственный парсер связанно с тем, что у TextField ужасная поддержка тэга img.
Благодарности
Хотелось бы поблагодарить:Автора движка INSTEAD Петра Косых
Автора класса MouseWheel Дениса Коляко
Авторов проекта lua-alchemy
Авторов проекта Flod
Автора класса SoundManager Олега Антипова
- +8
- zaynyatyi
Комментарии (12)
ох бляимхо лучше на флеше писатьИ если есть, то немогли бы дать ссылку?)
Это правда не квест, а новелла, но тем не менее.
Жаль, что я ингилиш не шарю)))
А то что такие игры не популярны — это жаль.
Хотя в декстопной игре «Космические рейнжеры» были классно оформлены некоторые мисии, именно в виде текстовых квестов.