Оптимизация местоположения кнопок и спрайтов

Добрый день!

Прошел уже почти год с релиза моей первой флеш-игры. С тех пор я выпустил уже вторую игру, и вот теперь разрабатываю игру для IOS.

При разработке первой игры я очень много времени потратил на оптимизацию расположения кнопок и спрайтов в информационных экранах игры, я имею в виду различные меню и окна типа «Credits» и «Level completed!». Я расскажу, как я оптимизировал свою работу.

Так же много времени тратил на размещение рекламных баннеров в сайтлоках, когда например надо подвинуть какую-нибудь кнопку или группу кнопок или спрайтов. Часто возникала путаница, из-за множества координат этих самых кнопок и картинок:


Чтобы избавиться от путаницы, у меня возникла идея создать мнимые горизонтальные и вертикальные линии, к которым бы привязывались объекты.

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

Я сделал 3 класса: UILine, UILink и UIManager. В конце статьи находится код этих трех классов.
При создании класса UILine, необходимо указать его родителя – то же экзмпляр данного класса, именно от него будет считаться смещение.

Я их использую следующим образом:
Все экземпляры класса UILine храню в отдельном классе, привожу его начальную часть:

 //предопределенные линии
        public static var XLeft:UILine = new UILine(null, 0, UILine.TYPE_X);
        public static var X14:UILine = new UILine(null, CMain.SC_WIDTH / 4, UILine.TYPE_X);
        public static var XCenter:UILine = new UILine(null, CMain.SC_WIDTH / 2, UILine.TYPE_X);
        public static var X34:UILine = new UILine(null, CMain.SC_WIDTH * 3 / 4, UILine.TYPE_X);
        public static var XRight:UILine = new UILine(null, CMain.SC_WIDTH, UILine.TYPE_X);
                
        public static var YLeft:UILine = new UILine(null, 0, UILine.TYPE_Y);
        public static var YCenter:UILine = new UILine(null, CMain.SC_HEIGHT / 2, UILine.TYPE_Y);
        public static var YRight:UILine = new UILine(null, CMain.SC_HEIGHT, UILine.TYPE_Y);
                
//Верх
        public static var ylineButtonsUp:UILine = new UILine(YLeft, 3);
        public static var xlineSound:UILine = new UILine(XRight, -25);

        public static var xlineMenu:UILine = new UILine(XLeft, 10);
        public static var xlineSpeed:UILine = new UILine(xlineMenu, 10); //здесь указывается расстояние между кнопками, т. к. для родителей этих линий задается параметр spr в страницах
        public static var xlineClear:UILine = new UILine(xlineSpeed, 10); //здесь указывается расстояние между кнопками, т. к. для родителей этих линий задается параметр spr в страницах

Кстати, если экземпляру UILine задать свойство spr:DisplayObject, то к указанному смещению будет прибавляться ширина данного объекта. Это может пригодиться если надпись на кнопке на разных языках будет разной длины.

А вот пример привязки конкретных объектов к линиям и установка их координат на основе этих линий:

public class subpageBtnMainMenu extends BasePage
{
        var bMainMenu:GameButton;
        var lMan:UIManager = new UIManager();           

        public function subpageBtnMainMenu()    
        {
                var tsp = new spBackIcon();
                bMainMenu = new GameButton(Translate.translate("Back"), tsp, 2,false, 5, -1);                 
                
                //Привязка координат
                lMan.setlink(bMainMenu, CUIMain.XLeft, UILink.ALIGN_BEGIN, 10);
                lMan.setlink(bMainMenu, CUIMain.YRight, UILink.ALIGN_END, -6);
                        
                //Установка координат кнопки bMainMenu
                lMan.setCoords();
        }
}

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

UILine.as:
package UI 
{
        import flash.display.DisplayObject;

        public class UILine 
        {
                public static const TYPE_X:int = 0;
                public static const TYPE_Y:int = 1;
                
                public static const     REL:int = 0;
                public static const     ABS:int = 1;
                
                public static const     X_ABS:Number = 1;
                public static const     Y_ABS:Number = 1;
                public static const     X_REL:Number = CMain.SC_WIDTH / CMain.SC_ETWIDTH;
                public static const     Y_REL:Number = CMain.SC_HEIGHT / CMain.SC_ETHEIGHT;
                
                public var parent:UILine;
                var type:int;
                var type_ar:int=REL;
                var k:Number;
                var val:Number;
                public var spr:DisplayObject;//если задается, то всегда желательно использоват левое выравнивание
                
                public var list:Vector.<UILine>;
                public var active:Boolean = true; //если false, то пропускается в списке
                public var stretch:Boolean = false; //растягивать если элементы в списке не активны
                
                public function UILine(tparent:UILine, tval:Number, ttype:int=undefined, ttype_ar:int = REL) 
                {
                        if (tparent != null) {
                                parent = tparent;
                                type = parent.type;
                        } else {
                                type = ttype;
                        }
                        type_ar = ttype_ar;
                        k = getK(ttype, type_ar);
                        val = tval;
                }
                
                public function getVal():Number {
                        var pval:Number;
                        var tk:Number = 0;
                        var sprval:Number = 0;
                        
                        if (parent === null) {
                                pval = 0;
                                tk = k;
                        } else {
                                
                                pval = parent.getVal();
                                
                                if (parent.spr != null) { //если у родителя spr не равен null, то его ширину тоже прибавляем
                                        if (type===TYPE_X) {
                                                pval += parent.spr.width;
                                        } else if (type===TYPE_Y) {
                                                pval += parent.spr.height;
                                        }
                                }
                                
                                if (parent.active === true) {
                                        tk = k;
                                } else {
                                        tk = 0;
                                }
                        }
                        
                        return (tk * val + pval);
                }
                
                static public function getK(ttype, type_ar):Number {
                        var k;
                        
                        if ((ttype = TYPE_X) && (type_ar = ABS)) {
                                k = X_ABS;
                        } else if ((ttype = TYPE_Y) && (type_ar = ABS)) {
                                k = Y_ABS;
                        } else if ((ttype = TYPE_X) && (type_ar = REL)) {
                                k = X_REL;
                        } else if ((ttype = TYPE_Y) && (type_ar = REL)) {
                                k = Y_REL;
                        }
                        
                        return k;
                }

                //создает список линий и возвращает его
                static public function newList(tparent:UILine, tn:int, tval0:Number, tval:Number, tStretch:Boolean, ttype:int=undefined, ttype_ar:int = REL):Vector.<UILine> {
                                        
                        var tlist:Vector.<UILine> = new Vector.<UILine>;
                        var tline:UILine;
                        var stparent:UILine;
                        
                        tline = new UILine(tparent, tval0, ttype, ttype_ar);
                        tline.list = tlist;
                        tline.stretch = tStretch;
                        stparent = tline;
                        tlist.push(tline);
                        for (var i:int = 1; i <= tn - 1; i++ ) {
                                tline = new UILine(stparent, tval, ttype, ttype_ar);
                                tline.list = tlist;
                                tline.stretch = tStretch;
                                stparent = tline;
                                tlist.push(tline);
                        }
                        
                        return tlist;
                }
        }
}


UILink.as:
package UI 
{
        import flash.display.DisplayObject;
        public class UILink 
        {
                public static const ALIGN_BEGIN:int = 0;
                public static const ALIGN_CENTER:int = 1;
                public static const ALIGN_END:int = 2;

                public static const     REL:int = 0;
                public static const     ABS:int = 1;
                public static const     RELSP:int = 2;
                
                public var sp:DisplayObject;
                
                public var line:UILine;
                public var align:int = ALIGN_BEGIN;
                
                public var delta:Number = 0;
                public var dType:int = ABS;
                
                public function UILink(tsp:DisplayObject, tline:UILine, talign:int = ALIGN_BEGIN, tdelta:Number = 0, tdType = ABS)
                {
                        sp = tsp;
                        line = tline;
                        align = talign;
                        delta = tdelta;
                        dType = tdType;
                }
        }
}


UIManager.as:
package UI 
{
        import flash.display.DisplayObject;
        import flash.display.Sprite;
        import flash.geom.Rectangle;

        public class UIManager 
        {
                public var tekAlignX:int = UILink.ALIGN_BEGIN;
                public var tekAlignY:int = UILink.ALIGN_END;
                
                var links:Vector.<UILink> = new Vector.<UILink>;
                
                public function UIManager() 
                {
                        
                }
                
                public function setlink(tsp:DisplayObject, tline:UILine, talign:int = -1, tdelta:Number = 0, tdType:int= 0):void {
                        var link:UILink;
                        
                        if (talign === -1) {
                                if (tline.type === UILine.TYPE_X) {
                                        talign = tekAlignX;
                                } else if (tline.type === UILine.TYPE_Y) {
                                        talign = tekAlignY;
                                }
                        }
                        
                        //находим link по link.sp и link.line.type
                        for (var i:int = 0; i <= links.length - 1; i++ ) {
                                link = links[i];
                                if ((link.sp === tsp) && (link.line.type === tline.type)) {
                                        link.line = tline;
                                        link.align = talign;
                                        link.delta = tdelta;
                                        link.dType = tdType;
                                        return;
                                }
                        }
                        
                        link = new UILink(tsp, tline, talign, tdelta, tdType);
                        links.push(link);
                }
                
                //создает список линий и возвращает его, также создает связи для этих линий и объектов
                public function setCoords():void {
                        var link:UILink;
                        //var tDl:Number;
                        var t_clipContentsBound:Rectangle;
                        
                        for (var i:int = 0; i <= links.length - 1; i++ ) {
                                link = links[i];
                                setCoordsForLink(link);
                        }
                        
                        //CMain.basesprite.addChild(debug);
                        
                }
                
                //создает список линий и возвращает его, также создает связи для этих линий и объектов
                public function setCoordsForSp(tsp:DisplayObject):void {
                        var link:UILink;
                        var tDl:Number;
                        var t_clipContentsBound:Rectangle;
                        
                        for (var i:int = 0; i <= links.length - 1; i++ ) {
                                link = links[i];
                                if (link.sp===tsp) {
                                        setCoordsForLink(link);
                                }
                        }
                        
                        //CMain.basesprite.addChild(debug);
                        
                }
                
                public function setCoordsForLink(link:UILink):void {
                        var t_clipContentsBound:Rectangle;
                        
                        t_clipContentsBound = link.sp.getBounds(link.sp);
                        if (link.line.type === UILine.TYPE_X) {
                                link.sp.x = calculateVal(link.line.getVal(), link.sp.width, link.align, link.delta, link.dType, UILine.X_REL);
                                link.sp.x -= t_clipContentsBound.x;
                        } else if (link.line.type = UILine.TYPE_Y) {
                                link.sp.y = calculateVal(link.line.getVal(), link.sp.height, link.align, link.delta, link.dType, UILine.Y_REL);
                                link.sp.y -= t_clipContentsBound.y;
                        }
                        
                }
                
                public static function calculateVal(val0:Number, width:Number, align:int, delta:Number, dType:Number, k:Number):Number {
                        var res:Number;
                        
                        if (align === UILink.ALIGN_BEGIN) {
                                res = val0;
                        } else if (align === UILink.ALIGN_CENTER) {
                                res = val0 - 0.5 * width;
                        } if (align === UILink.ALIGN_END) {
                                res = val0 - width;
                        }
                        
                        if (dType === UILink.ABS) {
                                res += delta;
                        } else if (dType === UILink.REL) {
                                res += delta * k;
                        } else if (dType === UILink.RELSP) {
                                res += delta * width;
                        }
                        
                        return res;
                }
        }
}
  • +3

Комментарии (7)

+1
А почему бы вручную не расставить это все в редакторе?
+1
Один из вариантов ответа: а как же мобильные приложения!
0
А какой редактор имеется в виду? (использую FlashDevelop).
Когда все это в коде — получается гибко корректировать расстояния между объектами в зависимости от размера экрана и разной длины слов в разных локализациях.
Плюс, я перенес этот код на objective c, и так же использую его в ios — проекте.
Можно и вручную расставить, но для того, чтоб изменять местоположение объектов в процессе работы программы, ведь все-равно придется код писать, или я ошибаюсь?
+2
Собираете под айос? А попробовать собирать в визуалке, а потом xml с координатами экспортировать?

А вообще меня всегда радуют хардкодеры, собирающие интерфейсы в коде. Долго, дорого, неудобно. И кстати, подкину идейку. Можно интерфейсы попиксельно рисовать ^^
0
Под айос я собираю в xcode.
Там есть визуальный редактор элементов пользовательского интерфейса ОС. Но игра на Cocos2d.
Если есть какой-то удобный инструмент для создания интерфейсов с элементами Cocos2d, то скажите как он называется, тогда я буду его использовать.
0
cocosbuilder.com/, правда сам пользовался чисто в ознакомительных целях, в реальных проектах не использовал
+2
Рисуем интерфейсы, расставляем во флэш иде.
Пишем немного кода.
На выходе получаем ассеты в PNG и XML с названиями ассетов и координатами, а так же поведением при наведении, клике и т.п.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.