
Оптимизация местоположения кнопок и спрайтов
Добрый день!
Прошел уже почти год с релиза моей первой флеш-игры. С тех пор я выпустил уже вторую игру, и вот теперь разрабатываю игру для IOS.
При разработке первой игры я очень много времени потратил на оптимизацию расположения кнопок и спрайтов в информационных экранах игры, я имею в виду различные меню и окна типа «Credits» и «Level completed!». Я расскажу, как я оптимизировал свою работу.
Так же много времени тратил на размещение рекламных баннеров в сайтлоках, когда например надо подвинуть какую-нибудь кнопку или группу кнопок или спрайтов. Часто возникала путаница, из-за множества координат этих самых кнопок и картинок:

Чтобы избавиться от путаницы, у меня возникла идея создать мнимые горизонтальные и вертикальные линии, к которым бы привязывались объекты.
В результате теперь для того, чтоб подвинуть, например, столбец с числами очков (на скриншоте), я меняю одно число в коде, которое отвечает за смещение относительно предыдущего столбца.
Я сделал 3 класса: UILine, UILink и UIManager. В конце статьи находится код этих трех классов.
При создании класса UILine, необходимо указать его родителя – то же экзмпляр данного класса, именно от него будет считаться смещение.
Я их использую следующим образом:
Все экземпляры класса UILine храню в отдельном классе, привожу его начальную часть:
Кстати, если экземпляру UILine задать свойство spr:DisplayObject, то к указанному смещению будет прибавляться ширина данного объекта. Это может пригодиться если надпись на кнопке на разных языках будет разной длины.
А вот пример привязки конкретных объектов к линиям и установка их координат на основе этих линий:
Я уверен, что существуют библиотеки, реализующие данные функции, но мне такие на глаза не попадались, да я и не искал. Привожу код классов, надеюсь это упростит кому-нибудь разработку:
UILine.as:
UILink.as:
UIManager.as:
Прошел уже почти год с релиза моей первой флеш-игры. С тех пор я выпустил уже вторую игру, и вот теперь разрабатываю игру для 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
- RoKo0
Комментарии (7)
Когда все это в коде — получается гибко корректировать расстояния между объектами в зависимости от размера экрана и разной длины слов в разных локализациях.
Плюс, я перенес этот код на objective c, и так же использую его в ios — проекте.
Можно и вручную расставить, но для того, чтоб изменять местоположение объектов в процессе работы программы, ведь все-равно придется код писать, или я ошибаюсь?
А вообще меня всегда радуют хардкодеры, собирающие интерфейсы в коде. Долго, дорого, неудобно. И кстати, подкину идейку. Можно интерфейсы попиксельно рисовать ^^
Там есть визуальный редактор элементов пользовательского интерфейса ОС. Но игра на Cocos2d.
Если есть какой-то удобный инструмент для создания интерфейсов с элементами Cocos2d, то скажите как он называется, тогда я буду его использовать.
Пишем немного кода.
На выходе получаем ассеты в PNG и XML с названиями ассетов и координатами, а так же поведением при наведении, клике и т.п.