Where does the context live in an OOP Javascript Pong game?
To practice my OOP knowledge I'm making a Pong game in javascript (I know, I know, it's like playing Stairway to Heaven in a guitar shop). I've had several functioning versions of the game by implementing several different techniques including prototypal based OOP and functional style. However I'm not doing this to get a functional game I'm doing it to learn.
I'm using the html5 canvas and plain ol' javascript, no frameworks (ok, a little bit of jQuery for the keyboard capture). I had my Pong object which represented my game. Pong had an attribute ctx
that contained a reference to the canvas.getContext("2d")
context. It also had a player1
, player2
and ball
attribute for holding you know what. When the ball and two players were instantiated the context was passed in to their constructor so that they too could hold a reference to the context for use in their draw(ctx)
methods. Pong had a draw()
method that would get called using a setInterval(this.draw, 10)
. Pong's draw method would call the draw method of the two players and the ball.
It doesn't sit well with me that the two players and ball have the context as an attribute. They don't own the context and therefore it shouldn't be an attribute. However the nature of using javascript and the canvas seems to be that this is the best way. Who or what should own the context in this situation? Ideally I wouldn't want the players and ball objects to have a 开发者_开发知识库draw object at all. I feel like they should have attributes that describe their geometry and position and a dedicated object should be tasked with rendering them to the screen. This way, if in the future I decided I wanted to use <div>'s instead of the canvas I could just change the rendering object and everything else would be oblivious.
I know I'm making a javascript Pong game more complicated than it needs to be but I want to practice the techniques and really grok the concept of OOP but every time I think I've cracked it a completely new problem created by my 'solution' presents itself.
EDIT: If it would help if you had a nosy at my code here is an (almost) fully working version:
library.js - http://mikemccabe.me/tests/pong.archive.14.06.11/library.js
pong.js - http://mikemccabe.me/tests/pong.archive.14.06.11/pong.js
Try it - out http://mikemccabe.me/tests/pong.archive.14.06.11/
I don't see a problem with your use of the context. To meet a design goal to allow the rendering to be technology independent, then you should write a general purpose interface to the rendering methods you need, and create an object that uses context
to implement this interface instead. Then you could substitute another version of that object, for example, to make it work in Internet Explorer <9.
In Javascript, I think it's often convenient to use scope to allow objects in your application to access shared resources directly. Though this is not strictly good OO design, think of it as a singleton. For example:
var Pong = (function() {
var Graphics, graphics, Ball, ball1, ball2, play;
Graphics = function() {
this.context = ...
};
graphics = new Graphics();
Ball = function() {
// do something with graphics
};
ball1 = new Ball();
ball2 = new Ball();
// ball1 and ball2 will both be able to access graphics
play =function() {
// play the game!
};
return {
play: play
}
}());
To generalize this you can just make the Graphics
object have general purpose methods instead of providing access to context
directly, and have more than one version of it, instantiating the correct one depending on browser. There aren't really any downsides compared to explicitly assigning graphics
to both ball1
and ball2
except purism. The upsides become substantial when you are dealing with hundreds of objects (e.g. representing DOM elements) rather than just a few.
You are making things more complicated than they're worth.
a dedicated object should be tasked with rendering them to the screen...
When you have a task that needs doing it's weird to say that "an object is tasked" with doing the thing. Functions do things. Objects encapsulate data. A function should be tasked with rendering them to the screen.
Who or what should own the context in this situation?
Nothing "owns" the context. Or, if you like, the canvas owns the context, but I don't know why you would take the context and put it somewhere; I would expect that to be more confusing than just passing it around.
EDIT: Glancing at your code, you've made the Pong closure "own" (have a reference to, that is) the context, which seems reasonable enough. I don't really see what problems you anticipate with that approach. I don't agree, however, that you should pass it into the constructor of Ball
etc. I think it's a lot more straightforward to pass it to the various draw()
methods.
Not specific to pong, but this online book
http://eloquentjavascript.net/chapter8.html
Has an example of building a small game. It's slightly different, but it does a good job of showing how to use Object Oriented Principles.
You can code things in many ways, and it's easy to go a little far with design patterns. As with anything, use moderation. I've always used Nibbles/Snake as my goto game when working in new language.
This might be a great case in which to apply MVC (model-view-controller) principles.
You can have a model to track the state of each game element. These would be things like Player, Paddle, Ball, etc. Then, you can have renderers (the views) which contain only the logic for drawing the current state. The views could be named something like BallCanvasRenderer and PaddleCanvasRenderer.
Since the view would be well encapsulated, it might make sense to have it keep a reference to the canvas context, but you could also just pass in the appropriate context and model for each time you render.
The controller would be responsible for responding to game events and updating the models.
Note that this pattern also gives you the option of maintaining multiple renderers for any type of model. You could also have renderers that output text, ASCII art, or OpenGL.
精彩评论