LibCanvas Basics - Theory



    Good afternoon. One of the most common questions about LibCanvas now is “Where to start?”. I agree that the threshold for entering this library is slightly higher than in simpler canvas libraries, therefore in this topic I will reveal the basics of LibCanvas - basic concepts and principles, drawing primitive figures, mouse events, keyboards, animation, extended context, behaviors. I will try to describe all this with many examples and the most accessible language.

    I hope this article answers the questions: What is LibCanvas? Why is it needed and what are its advantages? Where to begin?

    This article will only be a theory, and how to apply this knowledge in practice is disclosed in the next article

    General information


    LibCanvas is a framework for working with Canvas and related technologies, which can be used to develop games and other interactive applications.

    It is based on AtomJS , a lightweight JavaScript framework that is somewhat similar to MooTools and jQuery. There is quite a good English AtomJS documentation, and if you have used MooTools before, then it will be enough for you to justify AtomJS.

    The latest version of LibCanvas can be obtained in the repository , GitHub also has a number of relevant examples from very simple to quite complex. Many principles can be understood by looking at these examples. There is Russian documentation, but many parts of LibCanvas are not yet covered in it. Over time, it will fill and expand. I hope someone helps me with the translation into English)

    Core


    All code is stored in the LibCanvas namespace. This is good because the library does not clutter up the global namespace. However, there is a drawback - the verbose syntax is enough in the end:
    var circle = new LibCanvas.Shapes.Circle( 100, 100, 50 );
    var circle = new LibCanvas.Shapes.Circle( 222, 222, 22 );
    


    This can be fixed using the static LibCanvas.extract () method. It globalizes LibCanvas itself so that you can use short class names in your application:
    LibCanvas.extract();
    var circle = new Circle( 100, 100, 50 );
    var circle = new Circle( 222, 222, 22 );
    


    Another alternative is to use aliases:
    var Circle = LibCanvas.Shapes.Circle;
    var circle1 = new Circle( 100, 100, 50 );
    var circle2 = new Circle( 222, 222, 22 );
    


    LibCanvas.Context2D


    There is a built-in LibCanvas context. Its very simple to call:
    var context = document.getElementsByTagName('canvas')[0].getContext('2d-libcanvas');
    // or:
    var context = atom.dom('canvas').first.getContext('2d-libcanvas');
    


    Please note that the original '2d' context is still accessible and untouched, therefore it can be safely used in your applications:
    var context = atom.dom('canvas').first.getContext('2d');
    


    The '2d-libcanvas' context is backward compatible with the original context (all code written for the '2d' context will work in the '2d-libcanvas' context as well), but it has the following advantages:
    1. Chainable - all methods can be called in a chain. This method became especially popular with the advent of jQuery:
    context
    	.set({
    		  fillStyle: 'black',
    		strokeStyle: 'red'
    	})
    	.  fillRect(20, 20, 40, 40)
    	.strokeRect(50, 50, 100, 100);
    


    2. Named arguments - now you can pass not just a set of characters, but a hash:
    context.drawImage(img, 10, 15, 40, 45, 20, 25, 50, 55);
    // vs
    context.drawImage({
    	image: img,
    	crop : [10, 15, 40, 45],
    	draw : [20, 25, 50, 50]
    });
    


    3. Shapes - you can transfer shapes, not numbers. This is especially convenient when you have a large application with created objects:
    // отрисовка картинки:
    	context.drawImage( image, rect.from.x, rect.from.y, rect.width, rect.height );
    	// vs
    	context.drawImage( image, rect );
    // Заливка прямоугольника с сохранением состояния холста:
    	context.save();
    	context.fillStyle = 'red';
    	context.fillRect( rect.from.x, rect.from.y, rect.width, rect.height )
    	context.restore();
    	// vs:
    	context.fill( rect, 'red' );
    


    4. Extension API - here is a whole series of amenities. Firstly, more convenient work with paths, text, images, transformations, etc.:
    // Изображение с центром в определённой точке, повернутое вокруг оси:
    	// original ctx:
    	context.save();
    	context.translate(this.position.x, this.position.y);
    	context.rotate(this.angle);
    	context.translate(-this.image.width/2, -this.image.height/2);
    	context.drawImage(this.image, 0, 0);
    	context.restore();
    	// vs
    	context.drawImage({
    		image : this.image,
    		center: this.position,
    		angle : this.angle
    	});
    // Текст:
    	context.text({
    		text: 'Test string \n with line breaks \n is here'
    		padding: [ 30, 50 ],
    		size: 20,
    		align: 'center'
    	})
    // Крутим холст вокруг оси:
    	context.translate( point.x,  point.y);
    	context.rotate(angle);
    	context.translate(-point.x, -point.y);
    	// vs:
    	context.rotate( angle, point );
    // Рисуем путь
    	context.beginPath( );
    	context.moveTo( mt.x, mt.y );
    	context.lineTo( lt.x, lt.y );
    	context.bezierCurveTo( bc1.x, bc1.y, bc2.x, bc2.y, bc.x, bc.y );
    	context.quadraticCurveTo( qc1.x, qc1.y, qc.x, qc.y );
    	context.closePath();
    	// vs
    	context
    		.beginPath( mt )
    		.lineTo( lt );
    		.curveTo( bc, bc1, bc2 )
    		.curveTo( qc, qc1 )
    		.closePath();
    // Клипаем круг:
    	var circle = new Circle( 130, 120, 50 );
    	context.beginPath();
    	context.arc( circle.center.x, circle.center.y, circle.radius, 0, Math.PI * 2 );
    	context.closePath();
    	context.clip();
    	// vs:
    	context.clip( circle );
    // Очищаем весь холст:
    	context.clear( 0, 0, canvas.width, canvas.height );
    	// vs
    	context.clearAll();
    


    Etc. I think you yourself see the convenience of the built-in context.

    LibCanvas Object


    When constructing LibCanvas, a LibCanvas.Canvas2D object is created. The first argument you should pass a link to the necessary canvas element (css-selector, dom-object, etc). You can transfer additional settings to the second - fps limit, cleaning before redrawing, preloading pictures, and others .
    var libcanvas = new LibCanvas('#my-canvas');
    libcanvas instanceof LibCanvas; // true
    libcanvas instanceof LibCanvas.Canvas2D; // true
    // в свойстве можно получить расширенный контекст:
    libcanvas.ctx instanceof LibCanvas.Context2D; // true
    


    Each frame consists of two stages. The first is data rendering. It is performed every time and is solely responsible for mathematical operations - the movement of objects, collisions, etc. There should be no redrawing in this layer. The second stage is a render. It contains the part that is responsible for redrawing the contents of the screen and it will be performed only in case of any changes on the screen. This can be reported at the rendering stage by calling the method libcanvas.update().

    You can libcanvas.addFunc()add a function to the rendering stage using the method , you can add a function to the rendering stage using the method libcanvas.addRender(). Also, at the stage of rendering, the draw methods of the transferred objects are called. Approximately the code looks like this:

    libcanvas
    	.addFunc(function () {
    		scene.recount();
    		if (scene.somethingChanged()) {
    			libcanvas.update();
    		}
    	})
    	.addRender(function () {
    		// будет вызвано только после вызова libcanvas.update();
    		scene.drawAll();
    	});
    


    A lot of applications - are static most of the time with redrawing only at the moments of user action. This will help to significantly reduce the meaningless load on the processor.

    In practice, it addRenderis rarely used, because it is very convenient to draw objects using the method draw()(more on that below).

    Always redraw something on the screen only if there are changes. In many applications, such a basic mechanism will not be enough, but it is better than nothing.

    Point


    LibCanvas.Point- one of the basic objects. It is used very often, is a component of all figures and is very convenient for use outside of them . It has methods for determining the distance between two points, an angle, multiplying a point, and also getting all the neighbors.

    // Проворачиваем точку A на 60 градусов вокруг точки B:
    	var A = new Point(10, 10),
    	    B = new Point(20, 20);
    	A.rotate( (60).degree(), B );
    // считаем сумму значений всех соседей клетки в матрице:
    	var sum = 0 +
    		matrix[p.y-1][p.x-1] + matrix[p.y-1][p.x] + matrix[p.y-1][p.x+1] +
    		matrix[p.y  ][p.x-1] +                      matrix[p.y  ][p.x+1] +
    		matrix[p.y+1][p.x-1] + matrix[p.y+1][p.x] + matrix[p.y+1][p.x+1] ;
    	// vs
    	var sum = point.neighbours.reduce(function(value, p) { return value + matrix[p.y][p.x]; }, 0);
    


    Figures


    Shapes are contained in the namespace LibCanvas.Shapes.*and globalize to short aliases. The most well-known figures - is Rectangle, Circle, Line. When using LibCanvas - you must realize that the shapes themselves do not have an appearance, they cannot have an appearance - color or shadow. An object that uses a figure is responsible for the appearance, for example, LibCanvas.Ui.Shaper, while the figures themselves contain only mathematical operations — how to go a path, intersections, whether a point is inside the figure, etc. They are an astral, but not a physical body.

    This allows you to separate behavior from appearance. For example, we have a board in an arkanoid. This is actually a picture, but we can perform all actions as with a simple figure:

    var Unit = atom.Class({
    	initialize: function (rectangle, image) {
    		this.shape = rectangle;
    		this.image = image;
    	},
    	collision: function (anotherUnit) {
    		return this.shape.intersect( anotherUnit.shape );
    	},
    	draw: function () {
    		this.libcanvas.ctx.drawImage( this.image, this.shape );
    	}
    });
    


    Rectangle- the most important figure. It is used not only during drawing rectangles and basic mathematical operations, but also in many methods of LibCanvas. This can be, for example, the context.drawImage method, which takes a rectangle or a tile engine with arguments for cutting and drawing, in which each element is a small Rectangle.

    When a method requires a Rectangle-like argument, it can accept any argument that looks like a rectangle. For instance:
    context.drawImage({
    	image: image,
    	crop: {
    		from: { x: 15, y: 10 },
    		size: { width: 50, height: 100 }
    	},
    	draw: [10,20,100,200]
    });
    


    In this case, crop and draw will be reduced internally to the Rectangle (or to another necessary figure), but from the point of view of performance (with multiple redrawing the canvas), as well as from the point of view of the application architecture, the most profitable method is to create all objects during initialization applications. This decision was made specifically in order to encourage good architecture.

    var Item = atom.Class({
    	initialize: function (image) {
    		this.image = image;
    		this.cropRect = new Rectangle(15, 10,  50, 100);
    		this.drawRect = new Rectangle(10, 20, 100, 200);
    	},
    	draw: function () {
    		context.drawImage({
    			image: this.image,
    			crop : this.cropRect,
    			draw : this.drawRect
    		});
    	}
    });
    


    Other shapes are similarly used:
    // Дуга:
    context.arc({
    	circle: new Circle( 100, 100, 50 ),
    	angle : [ (45).degree(), (135).degree() ]
    });
    // Отрисовка линии:
    context.stroke( new Line([13, 13], [42, 42]), 'red' );
    


    Behaviors


    The next part is this LibCanvas.Behaviors.*. Each of them is just an admixture that adds specific functionality or behavior to your class. For example, it Animatableadds a method animatethat allows you to change the properties of an object smoothly, and Drawableallows objects of your class to be added to the LibCanvas object for rendering.

    By the way, it is Drawable that is the basis of rendering in LibCanvas. A mixture of Drawable and Shapes. * Will allow you to draw any shape on the canvas, and adding other behaviors will give this shape additional functionality.
    var Item = atom.Class({
    	Implements: [ Drawable, Draggable ],
    	initialize: function (shape) {
    		this.shape = shape;
    	},
    	draw: function () {
    		this.libcanvas.ctx.stroke( this.shape, 'red' );
    	}
    });
    libcanvas.addElement(
    	new Item( new Rectangle(50, 50, 100, 100) ).draggable()
    );
    


    In fact - a similar pattern for drawing shapes had to be created quite often, because it has already been implemented Ui.Shaper:

    libcanvas.createShaper({
    	shape : new Rectangle(50, 50, 100, 100),
    	stroke: 'red'
    }).draggable();
    


    Keyboard and mouse


    Working with the keyboard is quite simple. It is enough to call the method when initializing the application libcanvas.listenKeyboard()and you can use the method libcanvas.getKey( keyName )if necessary to find out the state of the key:

    update: function () {
    	if( this.libcanvas.getKey('aup') ) {
    		this.move();
    	}
    }
    


    The work with the mouse is worth it. Firstly, if you want to use the mouse in your application, be sure to call the method libcanvas.listenMouse(). In order to optimize, mouse events are not analyzed until it is called, because there are applications that do not need a mouse. After that, you can easily subscribe to mouse events by adding an element to the Mouse object:
    this.libcanvas.mouse.subscribe( element );
    


    It is important that the value of the shape property of the element be one of the shapes ( LibCanvas.Shapes.*), the zIndex property, and it implements the class atom.Class.Events. In practice, all this is hidden behind the behavior, and when you call, for example, the draggable()behavior method, the Draggableobject automatically subscribes to mouse events. If you only need to listen to mouse events, then just implement the behavior MouseListenerand call the method listenMouseon the element. Nevertheless, the main point still remains - the element must have a property Shapewith some shape inside. When mouse events at your object are listened, you can subscribe to any of the following events:

    /*
    	- click
    	- mouseover
    	- mousemove
    	- mouseout
    	- mouseup
    	- mousedown
    	- away:mouseover
    	- away:mousemove
    	- away:mouseout
    	- away:mouseup
    	- away:mousedown
    */ // Например:
    element
    	.listenMouse()
    	.addEvent('click', function () {
    		alert('element clicked');
    	});
    


    Conclusion


    I described here the basics of the theoretical part of development on LibCanvas. It does not reveal many interesting opportunities and principles, but its purpose is to explain the ideology and show the reader where to start.

    The topic of the next article is the practical part of development on LibCanvas .

    Also popular now: