В играх на 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;//координаты отклонения khobprotected 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;}});} //отрисовка@Overridepublic 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;elseangle+=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));
Так же необходимо переопределить метод, который срабатывает при движении пальца по экрану:
@Overridepublic 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