📜 ⬆️ ⬇️

How to write PingPong using LibCanvas


Good afternoon. In this topic, I will tell you how to make a ping-pong using LibCanvas. I significantly simplified it, leaving only the most important part, since the goal of the topic is not to create the game ping-pong, but to explain the basics of LibCanvas.

So, in the topic, step by step instructions on how to create a ping-pong using LibCanvas (without optimizations).

So, ping pong - two boards, from which the ball bounces. In general, you all know. The first thing we need is to create an initial html file. It is quite simple - a single canvas element, links to AtomJS and LibCanvas, and links to application files:

<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>LibCanvas :: ping-pong</title> <link href="/styles.css" rel="stylesheet" /> <script src="/lib/atom.js"></script> <script src="/lib/libcanvas.js"></script> </head> <body> <canvas></canvas> <!-- Game engine --> <script src="js/init.js"></script> <script src="js/controller.js"></script> <script src="js/field.js"></script> <script src="js/unit.js"></script> <script src="js/ball.js"></script> </body> </html> 

')

Initialization


It all starts with an initialization file.

In it, I call LibCanvas.extract in order to be able to use global names. By default, all classes are stored in their namespaces: LibCanvas.Shapes.Circle . After extract they can be used in abbreviated form: Circle

The second step I declare the namespace for my toy. All classes will be stored in it.

The last step is to create the controller when the dom starts.

 LibCanvas.extract(); window.Pong = {}; atom.dom(function () { new Pong.Controller('canvas'); }); 


Initialization may differ depending on the application, but, in general, it is similar among them. One of my comrades likes an interesting approach - a minimal html file with all the logic (even creating an element and connecting scripts) in JavaScript. And yes, this code is valid!

 <!DOCTYPE html> <title>LibCanvas :: ping-pong</title> <script src="js/load.ls"></script> 


Controller


The next step is to create a controller. In it, we will create a LibCanvas object and game elements. I will create all game classes using atom.Class , where initialize is a constructor.

 Pong.Controller = atom.Class({ initialize: function (canvas) { this.libcanvas = new LibCanvas(canvas, { preloadImages: { elems : 'im/elems.png' } }) .listenKeyboard([ 'aup', 'adown', 'w', 's' ]) .addEvent('ready', this.start.bind(this)) .start(); }, start: function () { var libcanvas = this.libcanvas; [...] } }); 


In the constructor, we pass the object to preload the picture. The application does not start until the picture is uploaded. These are two sprites - sticks and a ball.



We inform LibCanvas that we will use the keyboard and it is necessary to avoid default actions for the 'aup', 'adown', 'w' and 's' keys. This will allow for convenient management and, for example, the browser window will not move with arrows.

When the LibCanvas is ready to start drawing, we will start the controller's start method. We will return to it later.

Playing field


The playing field will be responsible for drawing the background, points, some calculations, the creation of units.

 Pong.Field = atom.Class({ Implements: [ Drawable ], width : 800, height: 500, [...] }); 


Create an entity and add it to libcanvas for rendering. Notice how we deftly change the size of the canvas. This is because our object has properties width and height.

 Pong.Controller = atom.Class({ [...] start: function () { var libcanvas = this.libcanvas; var field = new Pond.Field(); libcanvas.size( field, true ); libcanvas.addElement( field ); [...] } }); 


Ball


The logic of the ball is extremely simple - it has a “momentum” - the direction and speed of movement.
Speed ​​is set in pixels / second. Each time during the update we get the time that has passed since the previous update, which we multiply the speed. Due to this, we have a constant speed of movement, regardless of fps
When the ball reaches the top or bottom border - it hits and flies the other way.
appendTo allows you to easily add a ball to the field. It is important for us to know the size of the field for the initial position and wall counting.
Drawing is very simple - we just draw the desired part of the sprite into the current rectangle.
Please note that during the construction of the object property, the libcanvas is not there yet, so you need to wait for the libcanvasSet event and only then wield with libcanvas

 Pong.Ball = atom.Class({ Implements: [ Drawable ], impulse: null, initialize: function (controller) { this.impulse = new Point( Number.random(325, 375), Number.random(325, 375) ); this.addEvent('libcanvasSet', function () { this.image = this.libcanvas.getImage('elems').sprite( 23, 0, 26, 26 ); }); }, move: function (time) { this.shape.move( this.impulse.clone().mul(time / 1000) ); }, update: function (time) { this.move(time); var from = this.shape.from, to = this.shape.to; //      if ( (this.impulse.y < 0 && from.y < 0) || (this.impulse.y > 0 && to.y > this.field.height) ) this.impulse.y *= -1; }, appendTo: function (field) { this.shape = new Rectangle( 40, field.height / 2, 24, 24 ); this.field = field; return this; }, draw: function () { this.libcanvas.ctx.drawImage(this.image, this.shape); } }); 


Add his call to the controller. We need to update the position of the ball every frame, because we subscribe to the update with addFunc
 Pong.Controller = atom.Class({ [...] start: function () { [...] ball = new Pong.Ball(); libcanvas [...] .addElement( ball.appendTo( field ) ) .addFunc(function (time) { ball.update( time ); libcanvas.update(); }); } }); 


Create units


The next thing we need is rackets. They will be controlled using the keyboard (ws for the left and up and down for the right).
This class will be responsible for controls, movement, contact with the ball.
Note that the “speed” property is static, that is, added to the prototype. We will not change it, but only use it.
In controls, we bind to the update of the canvas and check the status of the necessary keys . If necessary, move the object.
An interesting way to move the shape to the desired speed - we just use the move method of our rectangle for this.
fitToField ensures that the element is within acceptable limits and, if it is not, then returns it to its place.
In the draw method, by analogy with Ball, the necessary part of the image is drawn into the current shape.

 Pong.Unit = atom.Class({ Implements: [ Drawable ], size: { width: 20, height: 100, padding: 20 }, speed: new Point( 0, 300 ), score: 0, controls: function (up, down) { this.addEvent('libcanvasSet', function () { var lc = this.libcanvas.addFunc(function (time) { if (lc.getKey(up)) { this.move( -time ); } else if (lc.getKey(down)) { this.move( time ); } }.bind(this)); }); return this; }, appendTo: function (field, number) { var s = this.size; this.field = field; this.number = number; this.shape = new Rectangle({ // field.width, field.height from: [ (number == 2 ? field.width - s.width - s.padding : s.padding), (field.height - s.height) / 2 ], size: s }); return this; }, fitToField: function () { var shape = this.shape; var top = shape.from.y, bottom = shape.to.y - this.field.height; if (top < 0) shape.move(new Point(0, -top)); if (bottom > 0) shape.move(new Point(0, -bottom)); }, move: function (time) { this.shape.move( this.speed.clone().mul( time / 1000 ) ); this.fitToField(); }, draw: function() { this.libcanvas.ctx.drawImage( this.libcanvas.getImage('elems').sprite(0,0,20,100), this.shape ); } }); 


We will create units on the field, where we will give them control and position:
 Pong.Field = atom.Class({ [...] createUnits: function (libcanvas) { this.unit = new Pong.Unit() .controls('w', 's') .appendTo( this, 1 ); this.enemy = new Pong.Unit() .controls('aup', 'adown') .appendTo( this, 2 ); libcanvas .addElement( this.unit ) .addElement( this.enemy ); }, [...] 


Naturally, you need to add a method call to the controller:

 Pong.Controller = atom.Class({ [...], start: function () { [...], field.createUnits( libcanvas ); } }); 


Object Interaction


Now you need to get the ball to interact with extreme boundaries and players. Add a simple method to Ball.
Please note that it is necessary to check the direction of movement of the ball, otherwise it may “get stuck” in the players and the walls.

 Pong.Ball = atom.Class({ [...] checkCollisions: function () { var coll = this.field.collidesUnits( this ), isOut = this.field.isOut( this.shape ); if ( (( coll < 0 || isOut < 0 ) && this.impulse.x < 0) || (( coll > 0 || isOut > 0 ) && this.impulse.x > 0) ) this.impulse.x *= -1; }, update: function (time) { [...] this.checkCollisions(); }, [...] }); 


Check inside Field is very simple.
We place a check on a collision with a unit on the shoulders of the unit, and only return the direction.
Checking for going beyond the right or left border is also very simple. In case of contact with the border, we increase the score of the opposite player.

 Pong.Field = atom.Class({ [...] collidesUnits: function (ball) { return this.unit .collides(ball) ? -1 : this.enemy.collides(ball) ? 1 : 0; }, isOut: function (shape) { if (shape.from.x < 0) { this.enemy.score++; return -1; } else if (shape.to.x > this.width) { this.unit.score++; return 1; } return 0; }, [...] 


Inside the Unit we will use the built-in method Rectangle().intersect , to check the intersection of two rectangles.
 Pong.Unit = atom.Class({ [...] collides: function (ball) { return ball.shape.intersect(this.shape); }, }); 


Account withdrawal


The last step is to display the players score. This can be done easily with ctx.text - it allows you to draw text more closely to css, specify indents, a rectangle into which you want to output text and some additional features.

 Pong.Field = atom.Class({ [...] drawScore: function (unit, align) { this.libcanvas.ctx.text({ text: unit.score, size: 32, padding: [0, 70], color: 'white', align: align }); return this; }, draw: function () { this .drawScore( this.unit , 'left' ) .drawScore( this.enemy, 'right' ); } }); 


Conclusion


That's all. The full code and game you can find at

libcanvas.github.com/games/pingpong/



The game from the topic can be developed further. For example, add a network game with a server to node.js and WebSocket.
Or add a beautiful look, animation. You can also improve the gameplay - add barriers, a different angle of reflection of the ball.
This is all done very easily with the help of LibCanvas. What topics interest you? If you wish - I will describe them.

Another question is whether it is worth describing more basic things, making topics about LibCanvas not so loaded with information, but narrower and describing certain minor aspects or similar full-length articles are perceived quite easily?

It seems to me that sometimes the thoughts were quite confused, so do not hesitate to ask questions in the comments or email shocksilien@gmail.com, if you are not registered on Habré.

Source: https://habr.com/ru/post/119420/


All Articles