patternjavascriptMinor
JavaScript Game Engine Design
Viewed 0 times
javascriptenginedesigngame
Problem
I recently gutted and redid my game code to be object oriented (before it was just functions and a mass of global variables.
Before I go an make more functionality happen, I would like to take one last step backwards to make sure I have the best foundation I can before proceeding.
Note: the point of this game is to better learn raw JavaScript methods and design, so I do not want any plugins, or somebody telling me how jQuery would make this easier ;)
I VERY much apologize if this script is too long for the site, and I will remove the question if this is an improper question here.
index.html
element object
```
function _elements() {
this.counter;
this.blood;
this.gold;
this.spanHP;
this.divHP;
this.bloodElement;
this.raidElement;
this.msg;
this.goldDiv;
this.day;
this.cycle;
this.goHunting;
this.elm = function(name,props,style) {
var el = document.createElement(name);
for(var prop in props) if(props.hasOwnProperty(prop)) el[prop] = props[prop];
for(var prop in style) if(style.hasOwnProperty(prop)) el.style[prop] = style[prop];
return el;
}
this.showElement = function(id) {
document.getElementById(id).className = "";
}
this.disableElement = function(id,txt) {
document.getElementById(id).disabled = true;
document.getElementById(id).innerHTML = txt;
}
this.alterHTML = function(id,txt) {
document.getElementById(id).innerHTML = txt;
}
this.enableButton = function(id,e,txt) {
var element = document.getElementById(id);
if (engine.player.isDead() && engine.dayStatus != "night")
engine.elements.eventMsg("You are too weak to "+e+" until pure darkness allows it!");
else {
element.disa
Before I go an make more functionality happen, I would like to take one last step backwards to make sure I have the best foundation I can before proceeding.
Note: the point of this game is to better learn raw JavaScript methods and design, so I do not want any plugins, or somebody telling me how jQuery would make this easier ;)
I VERY much apologize if this script is too long for the site, and I will remove the question if this is an improper question here.
index.html
A Vampire's Hunt
You have been dead for 0 hours..
It is currently:
Blood: 0
HP: 20
Gold: 0
element object
```
function _elements() {
this.counter;
this.blood;
this.gold;
this.spanHP;
this.divHP;
this.bloodElement;
this.raidElement;
this.msg;
this.goldDiv;
this.day;
this.cycle;
this.goHunting;
this.elm = function(name,props,style) {
var el = document.createElement(name);
for(var prop in props) if(props.hasOwnProperty(prop)) el[prop] = props[prop];
for(var prop in style) if(style.hasOwnProperty(prop)) el.style[prop] = style[prop];
return el;
}
this.showElement = function(id) {
document.getElementById(id).className = "";
}
this.disableElement = function(id,txt) {
document.getElementById(id).disabled = true;
document.getElementById(id).innerHTML = txt;
}
this.alterHTML = function(id,txt) {
document.getElementById(id).innerHTML = txt;
}
this.enableButton = function(id,e,txt) {
var element = document.getElementById(id);
if (engine.player.isDead() && engine.dayStatus != "night")
engine.elements.eventMsg("You are too weak to "+e+" until pure darkness allows it!");
else {
element.disa
Solution
Use prototypes
I notice you do this:
One issue with this one is that for every instance of
What you can do is use prototypal inheritance. This inheritance model works by "sharing" the prototype of the constructor across the instances. That way, the methods are only declared once, but shared across instances.
Decouple the code
Your code is tightly coupled. That means if I break one part of the code, the other parts break as well.
Take for example this code:
This assumes that an element
As well as this code:
Your code assumes
Decoupling with registration
Registration allows decoupling by registering objects into a system rather than having the system hard-code the required objects. That way, if no objects registered (because they are removed or something), then the code won't break. Here's a simple example with players:
So in the example above we "register" player objects into the engine rather than hard-code it into the engine. As you can see, advantages are apparent:
-
There's no trace of using player constructors in the engine code, which means taking out
-
You can register as many players as you like any time through a registration, which basically just stores players in an array.
Decouple events
I notice you use a clocking mechanism to to launch events with respect to time. However, you are hardcoding events along with the objects:
What you can do this one is some sort of "pub-sub" pattern or event listeners. You listen for events from an object and react accordingly. Basically, the mechanism registers functions into an array and runs them when the time comes to run them. It's very simple to implement, I have made a small library myself.
```
//Engine.js
function Engine(){
this.count = 0;
this.timer = null;
}
//Refer to my library for a simple Event Emitter implementation
Engine.prototype.run = function(){
//Save the context
var instance = this;
this.timer = setTimeout(function(){
// Run registered events
// If they return true, then run the corresponding handlers
},1000);
}
//Main script
var engine = new Engine();
var player = new Player();
// Register an event that determines when it happens
engine.registerEvent('nextDayCycle',function(cycle){
//nextDayCycle event is triggered on this tick when this returns true
return (!(cycle%10) && this.cycleFlag);
});
// Register a handler that runs when an event happens
engine.on('nextDayCycle',function(){
// Runs on every nextDayCy
I notice you do this:
function _player() {
...
this.isDead = function() {
if (this.hp <= 0) return true;
else return false;
}One issue with this one is that for every instance of
_player, you are creating methods per instance of the constructor. This eats up memory.What you can do is use prototypal inheritance. This inheritance model works by "sharing" the prototype of the constructor across the instances. That way, the methods are only declared once, but shared across instances.
function _player(){...}
_player.prototype = {
isDead : function(){...},
revive : function(){...},
...
};
var p1 = new _player();
var p2 = new _player();
// Both _player instances use the same revive method
// but operate on different _player objects
p1.revive();
p2.revive();Decouple the code
Your code is tightly coupled. That means if I break one part of the code, the other parts break as well.
Take for example this code:
this.eventMsg = function(txt) {
this.addBorder("msg");
var temp = document.getElementById("msg");
txt = "-"+txt+""+temp.innerHTML;
temp.innerHTML = txt;
}This assumes that an element
#msg exists. What if I took out that part of the HTML or renamed it by accident? temp would be undefined and accessing temp.innerHTML will throw an error.As well as this code:
this.player = (function(){ return new _player(); }());
this.elements = (function(){ return new _elements(); }());Your code assumes
_player and _elements exist. If I took them out or renamed them, you'd also be looking into this code and renaming it. Not very practical. Also, this assumes that _player and _elements are only one a piece. What if you needed more players? Or elements?Decoupling with registration
Registration allows decoupling by registering objects into a system rather than having the system hard-code the required objects. That way, if no objects registered (because they are removed or something), then the code won't break. Here's a simple example with players:
//Engine.js
function Engine(){
// We can have more than one player
this.players = [];
}
Engine.prototype = {
// A simple register
registerPlayer : function(player){
this.players.push(player)
},
doSomethingWithPlayers : function(){
for(var i = 0; i < this.players.length; i++){
// Do something to registered players
// No players registered means code won't run
}
}
}
//Registration.js
var player1 = new Player();
var player2 = new Player();
var engine = new Engine();
engine.register(player1);
engine.register(player2);
engine.doSomethingWithPlayers(); //Some mass-heal effect?So in the example above we "register" player objects into the engine rather than hard-code it into the engine. As you can see, advantages are apparent:
-
There's no trace of using player constructors in the engine code, which means taking out
Player.js won't break the engine code.-
You can register as many players as you like any time through a registration, which basically just stores players in an array.
Decouple events
I notice you use a clocking mechanism to to launch events with respect to time. However, you are hardcoding events along with the objects:
this.triggers = function(c) {
if (c == 5) this.elements.bloodButton();
if (!(c%1) && c > 5) this.elements.enableButton("bloodButton","hunt","Hunt for Blood");
if (!(c%1) && engine.player.bloodcount > 5) this.elements.enableButton("raidButton","raid","Raid for Gold");
if (engine.player.bloodcount >= 5 && !this.raidFlag) {
this.elements.raidButton();
this.raidFlag = true;
}
if (engine.player.bloodcount >= 10 && !this.cycleFlag) {
this.initDayCycle();
this.cycleFlag = true;
}
if (!(c%10) && this.cycleFlag) this.nextDayCycle();
}What you can do this one is some sort of "pub-sub" pattern or event listeners. You listen for events from an object and react accordingly. Basically, the mechanism registers functions into an array and runs them when the time comes to run them. It's very simple to implement, I have made a small library myself.
```
//Engine.js
function Engine(){
this.count = 0;
this.timer = null;
}
//Refer to my library for a simple Event Emitter implementation
Engine.prototype.run = function(){
//Save the context
var instance = this;
this.timer = setTimeout(function(){
// Run registered events
// If they return true, then run the corresponding handlers
},1000);
}
//Main script
var engine = new Engine();
var player = new Player();
// Register an event that determines when it happens
engine.registerEvent('nextDayCycle',function(cycle){
//nextDayCycle event is triggered on this tick when this returns true
return (!(cycle%10) && this.cycleFlag);
});
// Register a handler that runs when an event happens
engine.on('nextDayCycle',function(){
// Runs on every nextDayCy
Code Snippets
function _player() {
...
this.isDead = function() {
if (this.hp <= 0) return true;
else return false;
}function _player(){...}
_player.prototype = {
isDead : function(){...},
revive : function(){...},
...
};
var p1 = new _player();
var p2 = new _player();
// Both _player instances use the same revive method
// but operate on different _player objects
p1.revive();
p2.revive();this.eventMsg = function(txt) {
this.addBorder("msg");
var temp = document.getElementById("msg");
txt = "-"+txt+"<br />"+temp.innerHTML;
temp.innerHTML = txt;
}this.player = (function(){ return new _player(); }());
this.elements = (function(){ return new _elements(); }());//Engine.js
function Engine(){
// We can have more than one player
this.players = [];
}
Engine.prototype = {
// A simple register
registerPlayer : function(player){
this.players.push(player)
},
doSomethingWithPlayers : function(){
for(var i = 0; i < this.players.length; i++){
// Do something to registered players
// No players registered means code won't run
}
}
}
//Registration.js
var player1 = new Player();
var player2 = new Player();
var engine = new Engine();
engine.register(player1);
engine.register(player2);
engine.doSomethingWithPlayers(); //Some mass-heal effect?Context
StackExchange Code Review Q#35989, answer score: 4
Revisions (0)
No revisions yet.