В играх на Android очень важно организовать управление удобное. Я пока Bomberman’а делал, перепробовал различные варианты. Рассмотрим в этой статье возможные альтернативы.
За основу для статьи берём исходники из статьи про архитектуру игры на основе scene2d. Сам только сейчас оценил прелесть работы с scene2d.
Определение направление движения в зависимости от координат нажатия по экрану
Такой вариант был реализован в прошлой статье. Направление движения выбиралось в зависимости от того, куда вы кликнули на экране относительно персонажа. За всё, по сути, один метод отвечал в классе Player:
01.
public
void
ChangeNavigation(
float
x,
float
y){
02.
03.
resetWay();
04.
if
(y > getY())
05.
upPressed();
06.
07.
if
(y < getY())
08.
downPressed();
09.
10.
if
( x< getX())
11.
leftPressed();
12.
13.
if
(x> (getPosition().x +SIZE)* world.ppuX)
14.
rightPressed();
15.
16.
processInput();
17.
}
В общем-то, для стратегии это было бы удобно. Но если это какой-нибудь шутер/аркада, то кликать по экрану в разных точках неразумно. Удобнее бы было сделать виртуальный джойстик и при кликах по нему уже выбирать направление.
Управление джойстиком
Модифицируем нашу игру. Для начала надо изменить наш атлас, добавить в него спрайты джойстика. Затем изменить метод loadTextures() по загрузке текстур.
01.
private
void
loadTextures() {
02.
texture =
new
Texture(Gdx.files.internal(
"images/atlas.png"
));
03.
TextureRegion tmpLeftRight[][] = TextureRegion.split(texture, texture.getWidth()/
2
, texture.getHeight()/
2
);
04.
TextureRegion left2[][] = tmpLeftRight[
0
][
0
].split(tmpLeftRight[
0
][
0
].getRegionWidth()/
2
, tmpLeftRight[
0
][
0
].getRegionHeight());
05.
TextureRegion left[][] = left2[
0
][
0
].split(left2[
0
][
0
].getRegionWidth()/
4
, left2[
0
][
0
].getRegionHeight()/
8
);
06.
textureRegions.put(
"player"
, left[
0
][
0
]);
07.
textureRegions.put(
"brick1"
, left[
0
][
1
]);
08.
textureRegions.put(
"brick2"
, left[
1
][
0
]);
09.
textureRegions.put(
"brick3"
, left[
1
][
1
]);
10.
textureRegions.put(
"navigation-arrows"
, tmpLeftRight[
0
][
1
]);
11.
TextureRegion rightbot[][] = tmpLeftRight[
1
][
1
].split(tmpLeftRight[
1
][
1
].getRegionWidth()/
2
,tmpLeftRight[
1
][
1
].getRegionHeight()/
2
);
12.
textureRegions.put(
"khob"
, rightbot[
0
][
1
]);
13.
}
Прелесть scene2d, как я уже говорил ранее, в том, что можно работать с актёрами независимо друг от друга и в относительных координатах. Поэтому создадим класс, который будет олицетворять собой контроллер и унаследуем его от Actor.
001.
public
class
WalkingControl
extends
Actor{
002.
003.
//размер джоя
004.
public
static
float
SIZE = 4f;
005.
//размер движущейся части (khob)
006.
public
static
float
CSIZE = 3f;
007.
008.
public
static
float
CIRCLERADIUS =
1
.5f;
009.
public
static
float
CONTRLRADIUS = 3F;
010.
//public static float Coefficient = 1F;
011.
012.
//угол для определения направления
013.
float
angle;
014.
//public static int Opacity = 1;
015.
World world;
016.
017.
//координаты отклонения khob
018.
protected
Vector2 offsetPosition =
new
Vector2();
019.
020.
protected
Vector2 position =
new
Vector2();
021.
protected
Rectangle bounds =
new
Rectangle();
022.
023.
public
WalkingControl(Vector2 pos, World world){
024.
this
.position = pos;
025.
this
.bounds.width = SIZE;
026.
this
.bounds.height = SIZE;
027.
this
.world = world;
028.
029.
getOffsetPosition().x =
0
;
030.
getOffsetPosition().y =
0
;
031.
032.
setHeight(SIZE*world.ppuY);
033.
setWidth(SIZE*world.ppuX);
034.
setX(position.x*world.ppuX);
035.
setY(position.y*world.ppuY);
036.
037.
addListener(
new
InputListener() {
038.
public
boolean
touchDown (InputEvent event,
float
x,
float
y,
int
pointer,
int
button) {
039.
return
true
;
040.
}
041.
042.
//при перетаскивании
043.
public
void
touchDragged(InputEvent event,
float
x,
float
y,
int
pointer){
044.
045.
withControl(x,y);
046.
}
047.
048.
//убираем палец с экрана
049.
public
void
touchUp (InputEvent event,
float
x,
float
y,
int
pointer,
int
button) {
050.
051.
getOffsetPosition().x =
0
;
052.
getOffsetPosition().y =
0
;
053.
}
054.
055.
});
056.
}
057.
058.
059.
060.
//отрисовка
061.
@Override
062.
public
void
draw(SpriteBatch batch,
float
parentAlfa) {
063.
064.
065.
066.
batch.draw(world.textureRegions.get(
"navigation-arrows"
), getX(), getY(),getWidth(), getHeight());
067.
batch.draw(world.textureRegions.get(
"khob"
),
068.
(
float
)(position.x+WalkingControl.SIZE/
2
-WalkingControl.CSIZE/
2
+getOffsetPosition().x)*world.ppuX,
069.
(
float
)(position.y+WalkingControl.SIZE/
2
-WalkingControl.CSIZE/
2
+getOffsetPosition().y)*world.ppuY,
070.
WalkingControl.CSIZE * world.ppuX, WalkingControl.CSIZE * world.ppuY);
071.
072.
}
073.
074.
075.
public
Actor hit(
float
x,
float
y,
boolean
touchable) {
076.
//Процедура проверки. Если точка в прямоугольнике актёра, возвращаем актёра.
077.
return
x >
0
&& x < getWidth() && y>
0
&& y < getHeight()?
this
:
null
;
078.
}
079.
080.
081.
public
Vector2 getPosition() {
082.
return
position;
083.
}
084.
public
Vector2 getOffsetPosition() {
085.
return
offsetPosition;
086.
}
087.
088.
public
Rectangle getBounds() {
089.
return
bounds;
090.
}
091.
092.
093.
094.
095.
public
void
withControl(
float
x,
float
y){
096.
097.
//точка касания относительно центра джойстика
098.
float
calcX = x/world.ppuX -SIZE/
2
;
099.
float
calcY = y/world.ppuY -SIZE/
2
;
100.
101.
//определяем лежит ли точка касания в окружности джойстика
102.
if
(((calcX*calcX + calcY* calcY)<=WalkingControl.CONTRLRADIUS*WalkingControl.CONTRLRADIUS)
103.
){
104.
105.
world.resetSelected();
106.
107.
//пределяем угол касания
108.
double
angle = Math.atan(calcY/calcX)*
180
/Math.PI;
109.
110.
//угол будет в диапозоне [-90;90]. Удобнее работать, если он в диапозоне [0;360]
111.
//поэтому пошаманим немного
112.
if
(angle>
0
&&calcY<
0
)
113.
angle+=
180
;
114.
if
(angle <
0
)
115.
if
(calcX<
0
)
116.
angle=
180
+angle;
117.
else
118.
angle+=
360
;
119.
120.
//в зависимости от угла указываем направление, куда двигать игрока
121.
if
(angle>
40
&& angle<
140
)
122.
((Player)world.selectedActor).upPressed();
123.
124.
if
(angle>
220
&& angle<
320
)
125.
((Player)world.selectedActor).downPressed();
126.
127.
128.
if
(angle>
130
&& angle<
230
)
129.
((Player)world.selectedActor).leftPressed();
130.
131.
if
(angle<
50
|| angle>
310
)
132.
((Player)world.selectedActor).rightPressed();
133.
134.
135.
//двигаем игрока
136.
((Player)world.selectedActor).processInput();
137.
138.
139.
angle = (
float
)(angle*Math.PI/
180
);
140.
getOffsetPosition().x = (
float
)((calcX*calcX + calcY* calcY>1F)? Math.cos(angle)*
0
.75F: calcX);
141.
getOffsetPosition().y = (
float
)((calcX*calcX + calcY* calcY>1F)? Math.sin(angle)*
0
.75F: calcY);
142.
143.
}
144.
else
{
145.
146.
world.resetSelected();
147.
getOffsetPosition().x =
0
;
148.
getOffsetPosition().y =
0
;
149.
}
150.
151.
}
152.
}
Вся магия, по сути, в методе withControl(). В зависимости от нажатия относительно центра джойстика двигаем персонажа(точка касания должна быть в радиусе джойстика).
Теперь в конструкторе World необходимо добавить наш джойстик как актёра.
1.
//контрол как актёр
2.
addActor(
new
WalkingControl (
new
Vector2(0F,0F),
this
));
Так же необходимо переопределить метод, который срабатывает при движении пальца по экрану:
1.
@Override
2.
public
boolean
touchDragged(
int
x,
int
y,
int
pointer) {
3.
//если предварительно выбран игрок
4.
if
(selectedActor !=
null
)
5.
super
.touchDragged(x, y, pointer);
6.
7.
return
true
;
8.
}
Необходимо изменит метод, который срабатывает при нажатии по экрану (по актёру…в нашем случае по Stage), чтобы запоминать только если кликнули по одному из персов:
01.
public
Actor hit(
float
x,
float
y,
boolean
touchable) {
02.
03.
Actor actor =
super
.hit(x,y,touchable);
04.
//если выбрали актёра
05.
if
(actor !=
null
&& actor
instanceof
Player)
06.
//запоминаем
07.
selectedActor = actor;
08.
return
actor;
09.
}
Управление стрелкам
В Bomberman’е я реализовал возможность выбора между двумя типами управления: джоем и стрелками. По аналогии с методом, описанным мною для управления джоем, можно сделать и управление стрелками. Но тогда WalkingControl будет наследоваться не от Actor, а от Group. Тогда весь класс будет как контейнер для 4 актёров-кнопок. Может быть в одной из статей покажу, как это реализовать.
Собственно всё. Данная реализация позволяет кастомизировать контролы для управления как угодно: менять положение, непрозрачность, размеры. Использование scene2d даёт возможность менять наш джой как угодно, не влияя на остальной код, так как всё инкапсулировано в самом классе контроллера. Можете скачать исходники урока Libgdxtutorial-lesson5.rar.
есть спрайты джойстика отдельно?
Из атласа вырежьте)
а возможно ли двигать персонажа в зависимости от угла отклонения джостика, тоесть ели угол 37 градусов, и персонаж двигается с таким же углом?
Да, использовать угол, определённый в методе
withControl
.Подскажите плиз,как обойтись без метода hit,тоесть чтобы управление джойстиком было сразу,а не после нажатия на персонажа?
Программно самому вызвать этот метод, или сразу в методе
touchDown
уStage
менять координаты персонажа.Можна плиз поподробнее где вызывать метод?или как менять координаты в Стейдж…
Так слёту сложно. На неделе гляну и тогда наверняка скажу)
thx буду ждать:)полазил в gdx ,не понял как работают hit :(
Все понятно, кроме одного. Как Вы из спрайта выбираете картинки ?
В книге про Андроид Марио Цехнера-это понятно и просто-
…..arrow=new TextureRegion(items, 0,64.64.64);
Берется по координатами и все.
А как у Вас-ничего не понял.
Ну, объясню.
//грузим атлас
texture = new Texture(Gdx.files.internal("images/atlas.png"));
//разбиваем на регионы...2 по ширине, 2 по высоте
TextureRegion tmpLeftRight[][] = TextureRegion.split(texture, texture.getWidth()/ 2, texture.getHeight()/2 );
//самый верхний левый регион разбиваем на регионы опять...2 по ширине и 1 по высоте
TextureRegion left2[][] = tmpLeftRight[0][0].split(tmpLeftRight[0][0].getRegionWidth()/2, tmpLeftRight[0][0].getRegionHeight());
//из полученных 2-х регионов, левый разбиваем опять...4 по ширине, 8 по высоте
TextureRegion left[][] = left2[0][0].split(left2[0][0].getRegionWidth()/4, left2[0][0].getRegionHeight()/8);
//выделяем регион игрока из верхнего левого региона
textureRegions.put("player", left[0][0]);
Уведомление: Как я писал Bomberman'а на Android | Suvitruf's Blog