var getTimeNow = function () {
return +new Date();
};
// Game.......................................................................
// This game engine implements a game loop that draws sprites. See sprites.js.
//
// The game engine also has support for:
//
// Time-based motion (game.pixelsPerFrame())
// Pause (game.togglePaused())
// High Scores (game.setHighScore(), game.getHighScores(), game.clearHighScores())
// Sound (game.canPlaySound(), game.playSound())
// Accessing frame rate (game.fps)
// Accessing game time (game.gameTime)
// Key processing (game.addKeyListener())
//
// The game engine's animate() method invokes the following methods,
// in the order listed:
//
// game.startAnimate()
// game.paintUnderSprites()
// game.paintOverSprites()
// game.endAnimate()
//
// Those four methods are implemented by the game engine to do nothing.
// You override those do-nothing implementations to make the game come alive.
var Game = function (gameName, canvasId) {
var canvas = document.getElementById(canvasId),
self = this; // Used by key event handlers below
// General
this.context = canvas.getContext('2d');
this.gameName = gameName;
this.sprites = [];
this.keyListeners = [];
// High scores
this.HIGH_SCORES_SUFFIX = '_highscores';
// Image loading
this.imageLoadingProgressCallback;
this.images = {};
this.imageUrls = [];
this.imagesLoaded = 0;
this.imagesFailedToLoad = 0;
this.imagesIndex = 0;
// Time
this.startTime = 0;
this.lastTime = 0;
this.gameTime = 0;
this.fps = 0;
this.STARTING_FPS = 60;
this.paused = false;
this.startedPauseAt = 0;
this.PAUSE_TIMEOUT = 100;
// Sound
this.soundOn = true;
this.soundChannels = [];
this.audio = new Audio();
this.NUM_SOUND_CHANNELS = 10;
for (var i=0; i < this.NUM_SOUND_CHANNELS; ++i) {
var audio = new Audio();
this.soundChannels.push(audio);
}
// The this object in the following event handlers is the
// DOM window, which is why the functions call
// self.keyPressed() instead of this.keyPressed(e).
window.onkeypress = function (e) { self.keyPressed(e) };
window.onkeydown = function (e) { self.keyPressed(e); };
return this;
};
// Game methods...............................................................
Game.prototype = {
// Given a URL, return the associated image
getImage: function (imageUrl) {
return this.images[imageUrl];
},
// This method is called by loadImage() when
// an image loads successfully.
imageLoadedCallback: function (e) {
this.imagesLoaded++;
},
// This method is called by loadImage() when
// an image does not load successfully.
imageLoadErrorCallback: function (e) {
this.imagesFailedToLoad++;
},
// Loads a particular image
loadImage: function (imageUrl) {
var image = new Image(),
self = this; // load and error event handlers called by DOMWindow
image.src = imageUrl;
image.addEventListener('load',
function (e) {
self.imageLoadedCallback(e);
});
image.addEventListener('error',
function (e) {
self.imageLoadErrorCallback(e);
});
this.images[imageUrl] = image;
},
// You call this method repeatedly to load images that have been
// queued (by calling queueImage()). This method returns the
// percent of the games images that have been processed. When
// the method returns 100, all images are loaded, and you can
// quit calling this method.
loadImages: function () {
// If there are images left to load
if (this.imagesIndex < this.imageUrls.length) {
this.loadImage(this.imageUrls[this.imagesIndex]);
this.imagesIndex++;
}
// Return the percent complete
return (this.imagesLoaded + this.imagesFailedToLoad) /
this.imageUrls.length * 100;
},
// Call this method to add an image to the queue. The image
// will be loaded by loadImages().
queueImage: function (imageUrl) {
this.imageUrls.push(imageUrl);
},
// Game loop..................................................................
// Starts the animation by invoking window.requestNextAnimationFrame().
//
// window.requestNextAnimationFrame() is a polyfill method implemented in
// requestNextAnimationFrame.js. You pass requestNextAnimationFrame() a
// reference to a function that the browser calls when it's time to draw
// the next animation frame.
//
// When it's time to draw the next animation frame, the browser invokes
// the function that you pass to requestNextAnimationFrame(). Because that
// function is invoked by the browser (the window object, to be more exact),
// the this variable in that function will be the window object. We want
// the this variable to be the game instead, so we use JavaScript's built-in
// call() function to call the function, with the game specified as the
// this variable.
start: function () {
var self = this; // The this variable is the game
this.startTime = getTimeNow(); // Record game's startTime (used for pausing)
window.requestNextAnimationFrame(
function (time) {
// The this variable in this function is the window, not the game,
// which is why we do not simply do this: animate.call(time).
self.animate.call(self, time); // self is the game
});
},
// Drives the game's animation. This method is called by the browser when
// it's time for the next animation frame.
//
// If the game is paused, animate() reschedules another call to animate()
// in PAUSE_TIMEOUT (100) ms.
//
// If the game is not paused, animate() paints the next animation frame and
// reschedules another call to animate() when it's time to draw the
// next animation frame.
//
// The implementations of this.startAnimate(), this.paintUnderSprites(),
// this.paintOverSprites(), and this.endAnimate() do nothing. You override
// those methods to create the animation frame.
animate: function (time) {
var self = this; // window.requestNextAnimationFrame() called by DOMWindow
if (this.paused) {
// In PAUSE_TIMEOUT (100) ms, call this method again to see if the game
// is still paused. There's no need to check more frequently.
setTimeout( function () {
window.requestNextAnimationFrame(
function (time) {
self.animate.call(self, time);
});
}, this.PAUSE_TIMEOUT);
}
else { // Game is not paused
this.tick(time); // Update fps, game time
this.clearScreen(); // Clear the screen in preparation for next frame
this.startAnimate(time); // Override as you wish
this.paintUnderSprites(); // Override as you wish
this.updateSprites(time); // Invoke sprite behaviors
this.paintSprites(time); // Paint sprites in the canvas
this.paintOverSprites(); // Override as you wish
this.endAnimate(); // Override as you wish
this.lastTime = time;
// Call this method again when it's time for the next animation frame
window.requestNextAnimationFrame(
function (time) {
self.animate.call(self, time); // The this variable refers to the window
});
}
},
// Update the frame rate, game time, and the last time the application
// drew an animation frame.
tick: function (time) {
this.updateFrameRate(time);
this.gameTime = (getTimeNow()) - this.startTime;
},
// Update the frame rate, based on the amount of time it took
// for the last animation frame only.
updateFrameRate: function (time) {
if (this.lastTime === 0) this.fps = this.STARTING_FPS;
else this.fps = 1000 / (time - this.lastTime);
},
// Clear the entire canvas.
clearScreen: function () {
this.context.clearRect(0, 0,
this.context.canvas.width, this.context.canvas.height);
},
// Update all sprites. The sprite update() method invokes all
// of a sprite's behaviors.
updateSprites: function (time) {
for(var i=0; i < this.sprites.length; ++i) {
var sprite = this.sprites[i];
sprite.update(this.context, time);
};
},
// Paint all visible sprites.
paintSprites: function (time) {
for(var i=0; i < this.sprites.length; ++i) {
var sprite = this.sprites[i];
if (sprite.visible)
sprite.paint(this.context);
};
},
// Toggle the paused state of the game. If, after
// toggling, the paused state is unpaused, the
// application subtracts the time spent during
// the pause from the game's start time. That
// means the game picks up where it left off,
// without a potentially large jump in time.
togglePaused: function () {
var now = getTimeNow();
this.paused = !this.paused;
if (this.paused) {
this.startedPauseAt = now;
}
else { // not paused
// Adjust start time, so game starts where it left off when
// the user paused it.
this.startTime = this.startTime + now - this.startedPauseAt;
this.lastTime = now;
}
},
// Given a velocity of some object, calculate the number of pixels to
// move that object for the current frame.
pixelsPerFrame: function (time, velocity) {
// Sprites move a certain amount of pixels per frame (pixels/frame).
// This methods returns the amount of pixels a sprite should move
// for a given frame. Sprite velocity is measured in pixels / second,
// so: (pixels/second) * (second/frame) = pixels/frame:
return velocity / this.fps; // pixels / frame
},
// High scores................................................................
// Returns an array of high scores from local storage.
getHighScores: function () {
var key = this.gameName + this.HIGH_SCORES_SUFFIX,
highScoresString = localStorage[key];
if (highScoresString == undefined) {
localStorage[key] = JSON.stringify([]);
}
return JSON.parse(localStorage[key]);
},
// Sets the high score in local storage.
setHighScore: function (highScore) {
var key = this.gameName + this.HIGH_SCORES_SUFFIX,
highScoresString = localStorage[key];
highScores.unshift(highScore);
localStorage[key] = JSON.stringify(highScores);
},
// Removes the high scores from local storage.
clearHighScores: function () {
localStorage[this.gameName + this.HIGH_SCORES_SUFFIX] = JSON.stringify([]);
},
// Key listeners..............................................................
// Add a (key, listener) pair to the keyListeners array.
addKeyListener: function (keyAndListener) {
this.keyListeners.push(keyAndListener);
},
// Given a key, return the associated listener.
findKeyListener: function (key) {
var listener = undefined;
for(var i=0; i < this.keyListeners.length; ++i) {
var keyAndListener = this.keyListeners[i],
currentKey = keyAndListener.key;
if (currentKey === key) {
listener = keyAndListener.listener;
}
};
return listener;
},
// This method is the call back for key down and key press
// events.
keyPressed: function (e) {
var listener = undefined,
key = undefined;
switch (e.keyCode) {
// Add more keys as needed
case 32: key = 'space'; break;
case 68: key = 'd'; break;
case 75: key = 'k'; break;
case 83: key = 's'; break;
case 80: key = 'p'; break;
case 37: key = 'left arrow'; break;
case 39: key = 'right arrow'; break;
case 38: key = 'up arrow'; break;
case 40: key = 'down arrow'; break;
}
listener = this.findKeyListener(key);
if (listener) { // listener is a function
listener(); // invoke the listener function
}
},
// Sound......................................................................
// Returns true if the browser can play sounds in the ogg file format.
canPlayOggVorbis: function () {
return "" != this.audio.canPlayType('audio/ogg; codecs="vorbis"');
},
// Returns true if the browser can play sounds in the mp3 file format.
canPlayMp3: function () {
return "" != this.audio.canPlayType('audio/mpeg');
},
// Returns the first available sound channel from the array of sound channels.
getAvailableSoundChannel: function () {
var audio;
for (var i=0; i < this.NUM_SOUND_CHANNELS; ++i) {
audio = this.soundChannels[i];
if (audio.played.length === 0 || audio.ended) {
return audio;
}
}
return undefined; // all channels in use
},
// Given an identifier, play the associated sound.
playSound: function (id) {
var channel = this.getAvailableSoundChannel(),
element = document.getElementById(id);
if (channel && element) {
channel.src = element.src === '' ? element.currentSrc : element.src;
channel.load();
channel.play();
}
},
// Sprites....................................................................
// Add a sprite to the game. The game engine will update the sprite and
// paint it (if it's visible) in the animate() method.
addSprite: function (sprite) {
this.sprites.push(sprite);
},
// It's probably a good idea not to access sprites directly, because
// it's better to write generalized code that deals with all
// sprites, so this method should be used sparingly.
getSprite: function (name) {
for(i in this.sprites) {
if (this.sprites[i].name === name)
return this.sprites[i];
}
return null;
},
// Override the following methods as desired:
startAnimate: function (time) { }, // These methods are called by
paintUnderSprites: function () { }, // animate() in the order they
paintOverSprites: function () { }, // are listed. Override them
endAnimate: function () { } // as you wish.
};
游戏引擎---gameEngine61
原创
©著作权归作者所有:来自51CTO博客作者生而为人我很遗憾的原创作品,请联系作者获取转载授权,否则将追究法律责任
提问和评论都可以,用心的回复会被更多人看到
评论
发布评论
相关文章
-
51c大模型~合集61
大模型
大模型