В играх на Android очень важно организовать управление удобное. Я пока Bomberman’а делал, перепробовал различные варианты. Рассмотрим в этой статье возможные альтернативы.
За основу для статьи берём исходники из статьи про архитектуру игры на основе scene2d. Сам только сейчас оценил прелесть работы с scene2d.
Определение направление движения в зависимости от координат нажатия по экрану
Такой вариант был реализован в прошлой статье. Направление движения выбиралось в зависимости от того, куда вы кликнули на экране относительно персонажа. За всё, по сути, один метод отвечал в классе Player:
public void ChangeNavigation(float x, float y){
resetWay();
if(y > getY())
upPressed();
if(y < getY())
downPressed();
if ( x< getX())
leftPressed();
if (x> (getPosition().x +SIZE)* world.ppuX)
rightPressed();
processInput();
}
В общем-то, для стратегии это было бы удобно. Но если это какой-нибудь шутер/аркада, то кликать по экрану в разных точках неразумно. Удобнее бы было сделать виртуальный джойстик и при кликах по нему уже выбирать направление.
Управление джойстиком
Модифицируем нашу игру. Для начала надо изменить наш атлас, добавить в него спрайты джойстика. Затем изменить метод loadTextures() по загрузке текстур.
private void loadTextures() {
texture = new Texture(Gdx.files.internal("images/atlas.png"));
TextureRegion tmpLeftRight[][] = TextureRegion.split(texture, texture.getWidth()/ 2, texture.getHeight()/2 );
TextureRegion left2[][] = tmpLeftRight[0][0].split(tmpLeftRight[0][0].getRegionWidth()/2, tmpLeftRight[0][0].getRegionHeight());
TextureRegion left[][] = left2[0][0].split(left2[0][0].getRegionWidth()/4, left2[0][0].getRegionHeight()/8);
textureRegions.put("player", left[0][0]);
textureRegions.put("brick1", left[0][1]);
textureRegions.put("brick2", left[1][0]);
textureRegions.put("brick3", left[1][1]);
textureRegions.put("navigation-arrows", tmpLeftRight[0][1]);
TextureRegion rightbot[][] = tmpLeftRight[1][1].split(tmpLeftRight[1][1].getRegionWidth()/2,tmpLeftRight[1][1].getRegionHeight()/2);
textureRegions.put("khob", rightbot[0][1]);
}
Прелесть scene2d, как я уже говорил ранее, в том, что можно работать с актёрами независимо друг от друга и в относительных координатах. Поэтому создадим класс, который будет олицетворять собой контроллер и унаследуем его от Actor.
public class WalkingControl extends Actor{
//размер джоя
public static float SIZE = 4f;
//размер движущейся части (khob)
public static float CSIZE = 3f;
public static float CIRCLERADIUS = 1.5f;
public static float CONTRLRADIUS = 3F;
//public static float Coefficient = 1F;
//угол для определения направления
float angle;
//public static int Opacity = 1;
World world;
//координаты отклонения khob
protected Vector2 offsetPosition = new Vector2();
protected Vector2 position = new Vector2();
protected Rectangle bounds = new Rectangle();
public WalkingControl(Vector2 pos, World world){
this.position = pos;
this.bounds.width = SIZE;
this.bounds.height = SIZE;
this.world = world;
getOffsetPosition().x = 0;
getOffsetPosition().y = 0;
setHeight(SIZE*world.ppuY);
setWidth(SIZE*world.ppuX);
setX(position.x*world.ppuX);
setY(position.y*world.ppuY);
addListener(new InputListener() {
public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
return true;
}
//при перетаскивании
public void touchDragged(InputEvent event, float x, float y, int pointer){
withControl(x,y);
}
//убираем палец с экрана
public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
getOffsetPosition().x = 0;
getOffsetPosition().y = 0;
}
});
}
//отрисовка
@Override
public void draw(SpriteBatch batch, float parentAlfa) {
batch.draw(world.textureRegions.get("navigation-arrows"), getX(), getY(),getWidth(), getHeight());
batch.draw(world.textureRegions.get("khob"),
(float)(position.x+WalkingControl.SIZE/2-WalkingControl.CSIZE/2+getOffsetPosition().x)*world.ppuX,
(float)(position.y+WalkingControl.SIZE/2-WalkingControl.CSIZE/2+getOffsetPosition().y)*world.ppuY,
WalkingControl.CSIZE * world.ppuX, WalkingControl.CSIZE * world.ppuY);
}
public Actor hit(float x, float y, boolean touchable) {
//Процедура проверки. Если точка в прямоугольнике актёра, возвращаем актёра.
return x > 0 && x < getWidth() && y> 0 && y < getHeight()?this:null;
}
public Vector2 getPosition() {
return position;
}
public Vector2 getOffsetPosition() {
return offsetPosition;
}
public Rectangle getBounds() {
return bounds;
}
public void withControl(float x, float y){
//точка касания относительно центра джойстика
float calcX = x/world.ppuX -SIZE/2;
float calcY = y/world.ppuY -SIZE/2;
//определяем лежит ли точка касания в окружности джойстика
if(((calcX*calcX + calcY* calcY)<=WalkingControl.CONTRLRADIUS*WalkingControl.CONTRLRADIUS)
){
world.resetSelected();
//пределяем угол касания
double angle = Math.atan(calcY/calcX)*180/Math.PI;
//угол будет в диапозоне [-90;90]. Удобнее работать, если он в диапозоне [0;360]
//поэтому пошаманим немного
if(angle>0 &&calcY<0)
angle+=180;
if(angle <0)
if(calcX<0)
angle=180+angle;
else
angle+=360;
//в зависимости от угла указываем направление, куда двигать игрока
if(angle>40 && angle<140)
((Player)world.selectedActor).upPressed();
if(angle>220 && angle<320)
((Player)world.selectedActor).downPressed();
if(angle>130 && angle<230)
((Player)world.selectedActor).leftPressed();
if(angle<50 || angle>310)
((Player)world.selectedActor).rightPressed();
//двигаем игрока
((Player)world.selectedActor).processInput();
angle = (float)(angle*Math.PI/180);
getOffsetPosition().x = (float)((calcX*calcX + calcY* calcY>1F)? Math.cos(angle)*0.75F: calcX);
getOffsetPosition().y = (float)((calcX*calcX + calcY* calcY>1F)? Math.sin(angle)*0.75F: calcY);
}
else{
world.resetSelected();
getOffsetPosition().x = 0;
getOffsetPosition().y = 0;
}
}
}
Вся магия, по сути, в методе withControl(). В зависимости от нажатия относительно центра джойстика двигаем персонажа(точка касания должна быть в радиусе джойстика).
Теперь в конструкторе World необходимо добавить наш джойстик как актёра.
//контрол как актёр addActor(new WalkingControl (new Vector2(0F,0F),this));
Так же необходимо переопределить метод, который срабатывает при движении пальца по экрану:
@Override
public boolean touchDragged(int x, int y, int pointer) {
//если предварительно выбран игрок
if(selectedActor != null)
super.touchDragged(x, y, pointer);
return true;
}
Необходимо изменит метод, который срабатывает при нажатии по экрану (по актёру…в нашем случае по Stage), чтобы запоминать только если кликнули по одному из персов:
public Actor hit(float x, float y, boolean touchable) {
Actor actor = super.hit(x,y,touchable);
//если выбрали актёра
if(actor != null && actor instanceof Player)
//запоминаем
selectedActor = actor;
return actor;
}
Управление стрелкам
В 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