Написание компьютерных игр для лечения проблем бинокулярного зрения

Введение

Современное эффективное лечение расстройства бинокулярного зрения многим не по карману. Из-за этого многие дети лечатся, преимущественно отделяя гречку от риса.

Игра “тетрис”, написанная по заказу компании Stimed ( http://42.tut.by/445423 ), с учетом некоторых особенностей человеческого восприятия позволила перейти на новый уровень в решении этой проблемы. Мы решили помочь сделать эффективное лечение доступным для большинства.


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

Вопросы, которые мы изучили, чтобы написать данную игру:

1. Написали техническое задание для создания игры, пригласив специалиста на занятие в “Юни-центр” и все вместе изучили имеющуюся игру (“тетрис”):

https://youtu.be/ImWZHv5MvDM

Результаты:

Для получения терапевтического эффекта важно:

1) чтобы в игре (обучении, составлении сюжета сказки, ...) участвовало два основных цвета, один из которых обязательно должен быть красным, второй, желательно синим (для использования стандартных стереоочков), причем должно быть сбалансированно одинаково красного и синего цвета (50% фигур в тетрисе красного цвета, 50% синего и появляться они должны по-очереди).

2) чтобы персонажи (детали игры, буквы или линии в обучающей программе) мелькали с определенной частотой (12 герц).

3) чтобы можно было в программе изменять яркость, контрастность и оттенок красного и синего цветов (чтобы, смотря через стереоочки, добиться эффекта полного исчезновения раскрашенного объекта ).

2. Разобрали алгоритм мерцания, а также, как должна выглядеть программа в терминах ООП

Герцы — это “разы в секунду”. 12 Гц — это 12 раз в секунду. 0, 08(3) с — это длительность “одного показа”. Нужно добиться того, чтобы картинка показывалась и исчезала 12 раз в секунду, при этом длительность “одного показа” (и “исчезания”) должна быть по 0, 08(3) с.

package game.utils {

import flash.events.Event;

import flash.events.EventDispatcher;

import flash.events.TimerEvent;

import flash.utils.Timer;

import game.GameConfig;

//Глобальный таймер

public class GlobalBlinkTimer extends EventDispatcher {

private static var _instance:GlobalBlinkTimer;

private static var _isInstantiable:Boolean = false;

public static const ON_TIMER_EVENT:String = “on_timer_event”;

// Запускаем таймер с частотой 1000/(2*f) милисекунд

private const _tmr:Timer = new Timer(1000/(GameConfig.MAIN_BLINK_FREQUENCY * 2));

public static function getInstance():GlobalBlinkTimer{

if (!_instance){

_isInstantiable = true;

_instance = new GlobalBlinkTimer();

_isInstantiable = false;

}

return _instance;

}

public function GlobalBlinkTimer() {

if (!_isInstantiable){

throw new Error(“Use getInstance method to call singleton!”);

} else {

init();

}

}

private function init():void {

_tmr.addEventListener(TimerEvent.TIMER, onTimer, false, 0, true);

}

// Генерируем глобальное событие таймера которое получают все фигуры в игре

private function onTimer(evnt:TimerEvent):void {

this.dispatchEvent(new Event(GlobalBlinkTimer.ON_TIMER_EVENT));

}

public function start():void {

if (!_tmr.running){

_tmr.start();

}

}

public function stop():void {

if (_tmr.running){

_tmr.stop();

}

}

public function get isRunning():Boolean {

return _tmr.running;

}

}

}

package game.objects {

import flash.display.Sprite;

import flash.events.Event;

import game.GameConfig;

import game.utils.GlobalBlinkTimer;

//------------------------------------------------------------------

// Базовый класс для всех фигур в игре

//------------------------------------------------------------------

public class Figure extends Sprite {

protected var _figureArray:Array;

protected var _figureColor:int;

public var curX:int = 0;

public var curY:int = 0;

private var _globalTimer:GlobalBlinkTimer;

public function Figure(figureArray:Array, figureColor:int) {

super();

_figureArray = figureArray;

_figureColor = figureColor;

init();

}

private function init():void {

createFigure();

//------------------------------------------------------------------

// Получаем ссылку на глобальный таймер по которому должны мигать все фигуры

//------------------------------------------------------------------

if (GameConfig.MAIN_SHOW_BLINK) {

_globalTimer = GlobalBlinkTimer.getInstance();

this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage, false, 0, true);

}

}

private function onAddedToStage(event:Event):void {

this.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);

this.addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage, false, 0, true);

//------------------------------------------------------------------

// Подписываемся на событие глобального таймера. Событие производится с частой 2*f. Где f - неоходимая

// частота мигания. В резуальтате получаем что фигура находится в видимом состоянии (1/2f) секунд и в

// невидимом состоянии (1/2f) секунд. То есть полное мигание происходит за (1/f) что нам и необходимо.

//------------------------------------------------------------------

_globalTimer.addEventListener(GlobalBlinkTimer.ON_TIMER_EVENT, onGlobalTimer, false, 0, true);

}

//------------------------------------------------------------------

// Функция обработчик события глобального таймера реализующая мигание

//------------------------------------------------------------------

private function onGlobalTimer(event:Event):void {

(this.visible) ? (this.visible = false) : (this.visible = true);

}

private function onRemovedFromStage(event:Event):void {

this.removeEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage);

this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage, false, 0, true);

_globalTimer.removeEventListener(GlobalBlinkTimer.ON_TIMER_EVENT, onGlobalTimer);

}

private function createFigure():void {

var i:int;

var j:int;

var figBase:FigureBase;

if (this.numChildren) {

for (i = this.numChildren; i > 0; i--) {

this.removeChildAt(0);

}

}

for (i = 0; i < _figureArray.length; i++) {

for (j = 0; j < _figureArray[i].length; j++) {

if (_figureArray[i][j] == 1) {

figBase = new FigureBase(_figureColor);

figBase.x = j * GameConfig.BASE_FIGURE_SIZE[GameConfig.FIGURE_CONTAINER_SELECTED_SIZE];

figBase.y = i * GameConfig.BASE_FIGURE_SIZE[GameConfig.FIGURE_CONTAINER_SELECTED_SIZE];

figBase.indI = i;

figBase.indJ = j;

this.addChild(figBase);

}

}

}

}

public function get figureColor():int {

return _figureColor;

}

public function set figureColor(value:int):void {

_figureColor = value;

for (var i:int = 0; i < this.numChildren; i++){

FigureBase(this.getChildAt(i)).color = _figureColor;

}

}

public function rotateLeft():void{

var tmpArray:Array = [];

for (var i:int = 0; i<_figureArray.length; i++){

for (var j:int = 0; j<_figureArray[i].length; j++){

if (!tmpArray[_figureArray[i].length - j - 1]){

tmpArray[_figureArray[i].length - j - 1] = [];

}

tmpArray[_figureArray[i].length - j - 1][i] = _figureArray[i][j];

}

}

_figureArray = tmpArray;

createFigure();

}

public function rotateRight():void{

var tmpArray:Array = [];

for (var i:int = 0; i<_figureArray.length; i++){

for (var j:int = 0; j<_figureArray[i].length; j++){

if (!tmpArray[j]){

tmpArray[j] = [];

}

tmpArray[j][_figureArray.length - i - 1] = _figureArray[i][j];

}

}

_figureArray = tmpArray;

createFigure();

}

public function get curWidth():int{

return _figureArray[0].length;

}

public function get curHeight():int{

return _figureArray.length;

}

public function get figureLength():int{

return this.numChildren;

}

public function getFigureElement(elIndex:int):FigureBase{

if (elIndex >= 0 && elIndex < this.numChildren){

return this.getChildAt(elIndex) as FigureBase;

} else {

return null;

}

}

public function showFIgureArray():void{

var traseStr:String;

for (var i:int = 0; i < this._figureArray.length; i++){

traseStr = ((i<10)?(“0”+i):i)+ “: “;

for (var j:int = 0; j< this._figureArray[i].length; j++){

traseStr += “ “ + ((this._figureArray[i][j])?”1”:”0”);

}

trace(traseStr);

}

}

protected function refreshFigureColor(_clr:int):void {

_figureColor = _clr;

for (var i:int = 0; i < this.numChildren; i++){

var fb:FigureBase = FigureBase(this.getChildAt(i));

fb.color = _clr;

fb.x = fb.indJ * GameConfig.BASE_FIGURE_SIZE[GameConfig.FIGURE_CONTAINER_SELECTED_SIZE];

fb.y = fb.indI * GameConfig.BASE_FIGURE_SIZE[GameConfig.FIGURE_CONTAINER_SELECTED_SIZE];

}

}

public function refresh():void {

refreshFigureColor(GameConfig.FIGURE_COLOR1);

}

}}

3. Разобрали вопрос кроссплатформенности игры

Для начала протестировали имеющуюся игру “тетрис” на разных компьютерах и под разными ОС, по просьбе представителя компании Stimed показали, как запускать программу из консоли и под разными версиями Windows , записав соответсвующее видео.

Попробовали перенести исходники имеющейся игры “тетрис” под MacOS и iOS:

Результаты: Возникли проблемы с разрешением экрана, попаданием в кнопки и отсутствия клавиатуры как таковой (управление фигуркой надо каким-то образом заменить жестами или видимыми на экране кнопками).

Выяснили, чтобы скомпилировать проект, понадобилось запустить проект в среде Intellij IDEA (https://www.jetbrains.com/idea/), установить Apache Flex SDK (http://flex.apache.org/installer.html) и помучаться над настройками проекта.

В итоге нам удалось запустить игру на айфоне:

Image

4. Выяснили предпочтения по внешнему виду и содержания игр у представителя компании Stimed

5. Попробовали добавить мелькание в уже написанные игры.

1) В игру Анастасии Станкевич, написанную на Java FX: (https://youtu.be/vSRLUPTNlp8, https://youtu.be/fkBcy3tTg20 , исходники игры тут: https://drive.google.com/drive/folders/0B4icNOfw8DwUfkh4eldWOThqdy1QbGhKSFJieTFOcnVQNndXaXhLckxsVXdQbDNWeGhucnM

2) в игру Олега Аземши, написанную на Scratch: (https://youtu.be/gHM5e_J4fFE, Исходники и exe-файл тут: https://drive.google.com/folderview?id=0B1Nl3PHlshctZEhUQm0yMm4xY0k&usp=sharing https://drive.google.com/folderview?id=0B1Nl3PHlshctakZvWlRtalBHbmc&usp=sharing

3) в игру Антона Силебина, написанную на Java Swing (https://youtu.be/H92xuM41T5s исходники тут: https://drive.google.com/folderview?id=0B4icNOfw8DwUfjF3dDF5czNERHNqYXRSLU1QSXA1S0EwWFg0UDh1c290amVhSGJHNGRLcXM&usp=sharing)

Результаты:

Мы решили сделать рпг-игру используя конструктор 2-мерных игр Constract2, созданный фирмой Scirra, персонажи в которой раскрашены в синий и красный цвета.


Image

Мы создали TileMap в виде городских улиц с домами, персонажи могут ходить по улицам, проходить перед домами, заходить за дома (исчезая из поля зрения). Могут поднимать разбросанные по улицам города оружие. Если персонаж касается соответсвующего оружия, оно исчезает с места на улице и помещается в слоте инвентаря. Со временем мы планируем расширить инвентарь различными инструментами.

Image

Вот одна из последних версий игры до того, как мы сделали в ней мелькание персонажей: http://csc.minsk.by/beta/games/Ivan_s_game/index.html (исходники тут: https://drive.google.com/folderview?id=0B4icNOfw8DwUenctTUw1X3JxMjQ&usp=sharing)

Мы сделали две человеческие фигурки, красного и синего цвета, которые мелькают с указанной в проекте частотой:

Image


Согласитесь, что этот способ «заставить» персонажей мелькать, гораздо проще и нагляднее приведенного ранее.

Но оказалось не так все просто... Наш столь очевидный и простой код на Сonstract 2 не работал: персонажи упорно отказывались мелькать (на лицензионной версии программы!): они либо пропадали, либо не мерцали вообще, чаще первый вариант вариант.

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

Вот одна из последних версий игры:


http://csc.minsk.by/beta/games/Ivan_s_game/index.html

https://32607f29fc0b549f6e790913d83b1859dabb5694.googledrive.com/host/0B6OpMvmEP-PHQlZzSUtPekpwcDA/index.html

Мы планируем к началу конференции сделать несколько игр с подробным описанием методики их создания.

Вопросы, которые требуют дополнительного исследования:

- Можно ли добиться подобного терапевтического эффекта, не привлекая анимацию: используя, например, настройки стереоскопического режима монитора, стереоочки и настройки, например, Photoshop’a (позволяет изображения разбивать на красное и синее, смещать и проч...) иллюстрируя, например, детские сказки ?

- Исследовать предпочтения людей, которые будут использовать игры для лечения расстройства бинокулярного зрения

Литература:

http://csc.minsk.by/beta/construct-2/

http://csc.minsk.by/beta/