<!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <link rel="stylesheet" href="css/main.css" type="text/css" /> </head> <body> <div id="container"> <div id="bg"></div> <canvas id="canvas" width="900" height="600"></canvas> </div> <script src="js/LAB.min.js" type="text/javascript"></script> <script type="text/javascript"> $LAB .script("js/engine/CustomEvent.js").wait() .script("js/engine/Body.js").wait() .script("js/engine/World.js") .script("js/Bonus.js") .script("js/EnemyCloud.js") .script("js/Hero.js") .script("js/mainApp.js"); </script> </body> </html>
Why not in the head? As soon as the browser sees the script tag, it blocks the further construction of the page until the moment when the script is loaded. That is, before you will be just a white screen (and do not need here about the Gigabit Internet).
We use LAB.js (or else you can, for example, LazyLoad.js, etc.). It allows us to load script files in parallel (as you remember, it has already been mentioned above that construction stops at each insert script tag, that is, all scripts are loaded in turn in a row) plus we can control the load (for example, the wait function). We, for example, cannot use, say, a jQuery plugin, when jQuery itself has not loaded yet, of course, we put a pause on jQuery, and as soon as it loads, everything will go further.
(function() { var game = { images: [ { url: 'images/en_blue.png' }, { url: 'images/en_white.png' }, { url: 'images/en_red.png' } ], loadCount: 0, load: function() { var self = this, max = this.images.length; for (var i = max; i--;) { this.images[i].img = new Image(); this.images[i].img.onload = function () { self.loadCount++; if (self.loadCount == max) { self.loadComplete(); } } this.images[i].img.src = this.images[i].url; } }, loadComplete: function() { this.initControll().initGame().startLoop(); }, canvas: document.getElementById('canvas'), config: { fps: 1000/30, stageW: 900, stageH: 600 }, bodies: [], enemies: [], bonuses: [], initControll: function() { var handlers = this.controllHandlers(); if (document.documentElement.addEventListener) { document.body.addEventListener('keydown', handlers.keyDown); document.body.addEventListener('keyup', handlers.keyUp); } else { document.body.attachEvent('keydown', handlers.keyDown); document.body.attachEvent('keyup', handlers.keyUp); } return this; }, controllHandlers: function() { var self = this; return { keyDown: function(e) { self.keyDownControll(e.keyCode); }, keyUp: function(e) { self.keyUpControll(e.keyCode); } } }, initGame: function() { //-- world this.world = new World(this.canvas, this.config.stageW, this.config.stageH); //-- bonuses this.initBonuses(); //-- enemies this.initEnemies(); //-- Hero this.hero = new Hero(); this.world.addChild(this.hero.body); this.hero.initTales(this.world); return this; }, initBonuses: function() { var self = this, all = 20, good = 10, offset = 20; for (var i = 0; i < all; i++) { if (i < good) { this.bonuses.push(new Bonus({ x: offset + Math.random()*(self.config.stageW - 2*offset) , y: Math.random()*self.config.stageH, bonuseType: true, radius: Math.random()*1+2 })); } else { this.bonuses.push(new Bonus({ x: offset + Math.random()*(self.config.stageW - 2*offset) , y: Math.random()*self.config.stageH, bonuseType: false, radius: Math.random()*1+2 })); } this.world.addChild(this.bonuses[i].body); } }, initEnemies: function() { var enemiesData = [ { x: 10, y: 470, speed: 3 }, { x: 10, y: 300, speed: 2 }, { x: 10, y: 130, speed: 1 } ]; for (var i=0, max = enemiesData.length; i < max; i++) { this.enemies.push(new EnemyCloud({ x: enemiesData[i].x, y: enemiesData[i].y })); this.enemies[i].body.speed.default_x = enemiesData[i].speed; this.world.addChild(this.enemies[i].body); //--bonuses this.enemies[i].bonuses = this.bonuses; this.enemies[i].world = this.world; } }, keyDownControll: function(_keyCode) { switch (_keyCode) { case 37: this.hero.controll.left = true; break; case 39: this.hero.controll.right = true; break; default: break; } }, keyUpControll: function(_keyCode) { switch (_keyCode) { case 37: this.hero.controll.left = false; break; case 39: this.hero.controll.right = false; break; default: break; } }, startLoop: function() { var self = this; this.enterFrame = setInterval(function() { self.loop(); }, this.config.fps); }, loop: function() { var hero = this.hero, enemies = this.enemies, bonuses = this.bonuses; this.world.update(); //-- controll hero if (hero.controll.left) { hero.speed.angle -= hero.speed.rotation; } if (hero.controll.right) { hero.speed.angle += hero.speed.rotation; } hero.body.speed.x = hero.speed.x*Math.cos(Math.PI*hero.speed.angle/180); hero.body.speed.y = hero.speed.y*Math.sin(Math.PI*hero.speed.angle/180); for (var i = 0, maxI = enemies.length; i<maxI; i++) { enemies[i].body.position.x += enemies[i].body.speed.default_x; if (enemies[i].body.position.x > this.config.stageW - enemies[i].body.config.width) { enemies[i].body.speed.default_x = -enemies[i].body.speed.default_x; } if (enemies[i].body.position.x < 0) { enemies[i].body.speed.default_x = -enemies[i].body.speed.default_x; } } for (var j = 0, maxJ = bonuses.length; j<maxJ; j++) { if (bonuses[j].body.userData.bonuseType) { bonuses[j].body.position.y += bonuses[j].speed.y; if (bonuses[j].body.position.y > this.config.stageH - 5) { bonuses[j].body.position.y = 5; } } } hero.enterFrame(); } }; game.load(); })();
The scope in JavaScript is determined by the function. The variable defined in the function will not be visible outside of it (the global namespace will not be littered). Plus, if you don’t yet define a variable, use it immediately, for example:
function myFunc() { a = 123; }
it becomes global (a property of a global object), this is the same thing that you would write
function myFunc() { window.a = 123; }
that, as you know, at some point will lead to a conflict of names.
game.load();
var self = this, max = this.images.length; for (var i = max; i--;) {
This is determined at the time of the function call. Recall the same call, aplly, which explicitly defines the context of the call. The context, that is, the object pointed to by this. In our case, we see theonload
function; if we turn to this, it will refer to the HTMLImageElement (the images that loaded).
for (var i = max; i--;) {
Slowest cycle(I think it’s clear why), plus he iterates over the properties in a random order, which can often lead to logical errors, so we use it only in cases where it is not necessary to obtain all (and not known) properties of the object. Next is the view loop.
for in
for (var i = 0; i<this.images.length; i++;) {
here, for some reason, each iteration we recalculate the length of the array (although most people don’t bother it for some reason, and they write like this all the time, although (in my experience, when sorting an already finished array) only in a very small part of cases, this length could change iteration time).
Next (logical) is
for (var i = 0, max = this.images.length; i<max; i++;) {
we get the length only once.
Well, in the end (as it turned out, iterating through the array from the end faster than from the beginning), ok, we do:
for (var i = this.images.length; i>0; i--;) {
and more beautiful:
for (var i = this.images.length; i--;) {
this.images[i].img.src = this.images[i].url;
If you change the order of the given strings (everything seems to be fine, the picture will not have time to load until the interpreter reaches the next row) everything will work. Almost everywhere. Yes, you guessed it, IE8 already tells you everything he thinks about you. Therefore, follow this.
loadComplete: function() { this.initControll().initGame().startLoop(); }
canvas: document.getElementById('canvas'), config: { fps: 1000/30, stageW: 900, stageH: 600 }
DOM operations are the slowest. What happens if I go to the DOM in every frame and look for the canvas I need? And if also (which we all love) with jQuery, $ ('# canvas') - and this is in a loop, the creation of jQuery objects is also added. In general, we all understand that this is evil.
initControll: function() { var handlers = this.controllHandlers(); if (document.documentElement.addEventListener) { document.body.addEventListener('keydown', handlers.keyDown); document.body.addEventListener('keyup', handlers.keyUp); } else { document.body.attachEvent('keydown', handlers.keyDown); document.body.attachEvent('keyup', handlers.keyUp); } return this; }, controllHandlers: function() { var self = this; return { keyDown: function(e) { self.keyDownControll(e.keyCode); }, keyUp: function(e) { self.keyUpControll(e.keyCode); } } }, keyDownControll: function(_keyCode) { switch (_keyCode) { case 37: this.hero.controll.left = true; break; case 39: this.hero.controll.right = true; break; default: break; } }, keyUpControll: function(_keyCode) { switch (_keyCode) { case 37: this.hero.controll.left = false; break; case 39: this.hero.controll.right = false; break; default: break; } }
<body onkeydown="eventHandler">
We can talk about this a lot, let's start with Viky. In a nutshell, the point is that everything should be divided.
if (navigator.userAgent.indexOf(' MSIE') ! == -1) {
addEventListener
method ,then only view verification
if (document.documentElement.addEventListener) {
we can definitely say something.
Look at the line:self.keyDownControll(e.keyCode);
Here I don’t transfer the reference to the event, but
.keyCode
, that is, if suddenly I need to “manually” simulate actions on keyboard events, I’ll just write, for example, game.keyDownControll (37);
initGame: function() { this.world = new World(this.canvas, this.config.stageW, this.config.stageH); this.hero = new Hero(); this.world.addChild(this.hero.body); },
this.world.addChild(this.hero.body);
this.body = Body.create('Circle', { x: 20, y: 20, radius: 18, gravity: 0.1, bgColor: "#eeeeee", fade: { x: 0.999, y: 0.999 } });
Always transfer the object (unless of course the number of parameters passed is planned to be more than 2), then you will not have to do very popular (among beginners) things, like:
myFunc(null, null, null, null, null, 123, null, 'string_lol');
(so, I did not miss the order of the parameter anywhere?) And this is also all clear.
startLoop: function() { var self = this; this.enterFrame = setInterval(function() { self.loop(); }, this.config.fps); }, loop: function() { var hero = this.hero, enemies = this.enemies, bonuses = this.bonuses; this.world.update(); //...
You can always cope with one timer, if it seems to you that there is not, and that you have unique objects of some class there, that each object should have its own timer - most likely you need to revise the architecture of your application. They can be added and removed, but the monotonous actions that go on throughout the game cycle are done on a single timer.
this.world.update();
each frame, it updates the whole world gives the results of the collisions and so on. hero = this.hero, enemies = this.enemies, bonuses = this.bonuses;
When the interpreter encounters a variable, it looks at how it is initialized, starting from the deepest level (that is, where the code is currently running, for example, a function) and to the top, that is, if for example we are accessing a global variable, we will have to go through all levels until you can find her. If you need to access global variables from some deep nesting more than once, you need to reassign them to local ones.
Hero.prototype.initEvents = function() { var self = this; this.body.addHitListener('bodies', function(e){ self.hitAction(e.data); }); /*this.body.addHitListener('limits', function(e) { });*/ }
e.data
stores a reference to the object that this.body
. Cool. All as we wanted.CustomEvent
. It can be used in any other place where you need it. function CustomEvent(_eventName, _target, _handler) { this.eventName = _eventName; if (_target && _handler) { this.eventListener(_target, _handler); } } CustomEvent.prototype.eventListener = function(_target, _handler) { if (typeof _target == 'string') { this.target = document.getElementById(_target); } else { this.target = _target; } this.handler = _handler; if (this.target.addEventListener) { this.target.addEventListener(this.eventName, this.handler, false); } else if (this.target.attachEvent) { this.target.attachEvent(this.eventName, this.handler); } } CustomEvent.prototype.eventRemove = function() { if (this.target.removeEventListener) { this.target.removeEventListener(this.eventName, this.handler, false); } else if (this.target.detachEvent) { this.target.detachEvent(this.eventName, this.handler); } } CustomEvent.prototype.eventDispatch = function(_data) { if (document.createEvent) { var e = document.createEvent('Events'); e.initEvent(this.eventName, true, false); } else if (document.createEventObject) { var e = document.createEventObject(); } else { return } e.data = _data; if (this.target.dispatchEvent) { this.target.dispatchEvent(e); } else if (this.target.fireEvent) { this.target.fireEvent(this.eventName, e); } }
this.tales[j].moveTo(this.tales[j - 1].position.x, this.tales[j - 1].position.y, 5);
moveTo(_x, _y, _speed)
. Which as we see it just moves from the current point to the point with the given coordinates. How to do? Just get the coordinates between the two points by coordinates, divide by the number of transitions (_speed) and we will get this step in one transition. Everything. Body.devMode.usePhysLimits = true; Body.devMode.display = true; Body.devMode.bodyId = this.body.id; Body.devMode.use = true; Body.useGravity = false;
this.body
, and assigned by id
(yes, each body has its own identifier).false
.Source: https://habr.com/ru/post/167007/
All Articles