libGDX: Часть 5. Работа с сенсорным экраном и управление

В играх на 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.

libGDX: Часть 5. Работа с сенсорным экраном и управление: 12 комментариев

  1. артур

    а возможно ли двигать персонажа в зависимости от угла отклонения джостика, тоесть ели угол 37 градусов, и персонаж двигается с таким же углом?

  2. XoXoX

    Подскажите плиз,как обойтись без метода hit,тоесть чтобы управление джойстиком было сразу,а не после нажатия на персонажа?

    1. Suvitruf Автор записи

      Программно самому вызвать этот метод, или сразу в методе touchDown у Stage менять координаты персонажа.

      1. XoXoX

        Можна плиз поподробнее где вызывать метод?или как менять координаты в Стейдж…

  3. Леха

    Все понятно, кроме одного. Как Вы из спрайта выбираете картинки ?

    В книге про Андроид Марио Цехнера-это понятно и просто-
    …..arrow=new TextureRegion(items, 0,64.64.64);
    Берется по координатами и все.

    А как у Вас-ничего не понял.

    1. Suvitruf Автор записи

      Ну, объясню.
      //грузим атлас
      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]);

  4. Уведомление: Как я писал Bomberman'а на Android | Suvitruf's Blog

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *