Рисование с физикой на HaxeFlixel

haxeflixelЯ люблю собирать прототипы и пробывать разные варианты управления для устройств с touch экраном. Это постоянный поиск чего-то более нативного и привычного, чем джойстик отрисованный поверх геймплея.

Один из таких вариантов — рисование. Но что бы было интереснее я покажу пример рисования с физикой.

Опишу в двух словах, как мы будем это делать:

  1. Создаем спрайт размером с экран и заполняем его прозрачностью.
  2. Добавляем к нему пустое физическое тело, в которое потом будем добавлять shape созданные по мотивам нашего рисования
  3. Ловим в update событие нажатия мыши и записываем координаты нажатия стартовой точки
  4. Каждый последующий вызов update, если мышь все еще нажата (или палец все еще на экране) считаем расстояние от нашей стартовой точки, до текущей и ждем пока эта величина не станет больше минимально допустимого значения.
  5. Создаем shape прямоугольной формы с шириной равной отрисованному расстоянию, а высотой 1
  6. Считаем угол, на который нужно повернуть shape, что бы он был вдоль отрисованной линии и перемещаем тело в координаты стартовой точки
  7. Добавляем  shape к физическому телу созданному в пункте 2.
  8. Записываем текущие координаты, как координаты стартовой точки

За отрисовку отвечает  FlxSpriteUtil и там все просто. Поэтому перейдем к коду:

import flixel.addons.nape.FlxNapeSprite;
import flixel.addons.nape.FlxNapeState;
import flixel.FlxCamera;
import flixel.FlxG;
import flixel.ui.FlxButton;
import flixel.util.FlxColor;
import flixel.util.FlxMath;
import flixel.util.FlxPoint;
import flixel.util.FlxSpriteUtil;
import Math;
import nape.constraint.MotorJoint;
import nape.constraint.PivotJoint;
import nape.geom.Vec2;
import nape.phys.Body;
import nape.phys.BodyType;
import nape.phys.Material;
import nape.shape.Polygon;

class DrawState extends FlxNapeState
{
    var _canvas:FlxNapeSprite;
    var _distance:Float;
    var _angle:Float;
    var _startPoint:FlxPoint;
	var _circles:Array<FlxNapeSprite>;
	
	static var MINIMUM_LINE_LENGHT:Float = 10;

    override public function create():Void
    {
        super.create();
        //Создаем спрайт, в котором будем рисовать
		_canvas = new FlxNapeSprite(); 
		 // Задаем ему размеры равные размеру экрана
        _canvas.makeGraphic(FlxG.width, FlxG.height, FlxColor.TRANSPARENT);
		// выставляем точку привязки в 0,0
		_canvas.origin.set(0, 0); 
		//добавляем на сцену
        add(_canvas);
		//создаем физическое тело
        var body = new Body(BodyType.KINEMATIC);
		//Добавляем физическое тело к нашему спрайту
		_canvas.addPremadeBody(body);

		_circles = new Array<FlxNapeSprite>(); 
		_startPoint = new FlxPoint();
		
        FlxG.mouse.useSystemCursor = true;

		createUI(); 
    }
	
	function rainOfCircles()
	{
		if (FlxNapeState.space.gravity.y != 500)
			FlxNapeState.space.gravity.setxy(0, 500);
			
		var circle:FlxNapeSprite;
		var RADIUS:Float = 10;
		napeDebugEnabled = true;
		for (i in 0...30)
		{
			circle = new FlxNapeSprite(i * RADIUS*2 + RADIUS, 0);
			circle.createCircularBody(RADIUS);
			circle.makeGraphic(20, 20, FlxColor.TRANSPARENT);
			FlxSpriteUtil.drawCircle(circle, 10, 10, 10);
			add(circle);
			_circles.push(circle);
		}
	}

    override public function update():Void
    {
       super.update();
	    //если кнопка была только что нажата то...
       if (FlxG.mouse.justPressed)
        {
			//копируем координаты нажатия
            _startPoint.copyFrom(FlxG.mouse); 
			return;
        }
		//проверяем нажата ли кнопка мыши
		if (FlxG.mouse.pressed) 
		{
			// считаем растояние от места нажатия 
			_distance = FlxMath.getDistance(_startPoint, FlxG.mouse); 
			//отсекаем незначительные движения мыши проверяя растояния от точки нажатия
			if(_distance > MINIMUM_LINE_LENGHT) 
			{
				//рисуем линию
				FlxSpriteUtil.drawLine(_canvas, _startPoint.x, _startPoint.y, FlxG.mouse.x, FlxG.mouse.y, { thickness: 4, color: FlxColor.RED });
				//создаем физический прямоугольник
				var shape:Polygon = new Polygon(Polygon.rect(0, 0, _distance, 1 ));
				//высчитываем угол поворота прямоугольника
				_angle = Math.atan2(FlxG.mouse.y - _startPoint.y, FlxG.mouse.x - _startPoint.x);
				shape.rotate( _angle);
				//устанавливаем необходимые координаты
				shape.translate(Vec2.weak(_startPoint.x, _startPoint.y));
				//добавляем наш прямоугольник к физическому телу в котором мы рисуем
				_canvas.body.shapes.add(shape);
				// запоминаем последние координаты отрисовки, что бы следующий прямоугольник рисовать уже в нужном месте
				_startPoint.copyFrom(FlxG.mouse);
			}
		}
		for (circle in _circles)
		{
			if (!circle.isOnScreen(FlxG.camera))
			{
				//удаляем круги вылетевшие за экран
				_circles.remove(circle);
				circle.destroy();
			}
		}
    }

    private function reset():Void
    {
        FlxG.resetState();
    }
		
	function napeDebug() 
	{
		napeDebugEnabled = !napeDebugEnabled;
	}
	
	function createUI() 
	{
		var btn:FlxButton = new FlxButton(0, 0, "Reset", reset);
        add(btn);
		btn = new FlxButton(btn.x + btn.width + 10, 0, "Create Circles", rainOfCircles);
		add(btn);
	}

}

А вот apk для желающих посмотреть на своем Android: [ddownload id=»41″]

Это не самый производительный способ. Но с поставленной задачей справляется на ура.

В скором будущем постараюсь рассказать, как рисовать с физикой в Unity.  А так же  про более хитрую и производительную рисовалку на haxeflixel, для более динамичных игр и с зоной рисования не ограниченной одним экраном.