Ozan Ertürk Ozan Ertürk - 5 days ago 5
Javascript Question

Canvas Game Architecture: Access parent function in JavaScript Object

I am trying to understand how can I create a custom game object in javascript.I am working on canvas and I thought that I can create a general game object that includes information about objects position,shape etc. and functions like "move", "fire", "display" etc.

I created a function called "create(canvas)". That gets the canvas and initializes the local variables then calls the display function in "onload" function.
But I can not access display function of an object.
Additionally, I always use "this" syntax to access my local variables or functions. It makes me feel that there is something going wrong.

You can see the code below

<script>


var gameObject = {

x: 0,
y: 0,
velocity: 5,
width: 40,
height: 40,
triggerObject:null,
imgUrl: "/assets/minionframe/1.png",
img:null,
canvas:null,
context:null,
display:function(){
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);

this.context.drawImage(this.img,this.x,this.y,this.width,this.height);
},
move: function (direction) {
if (direction == "right")
x += this.velocity;
else if (direction == "left")
x -= this.velocity;
else if (direction == "up")
y -= this.velocity;
else if (direction == "down")
y += this.velocity;
this.display();
},
fire: function () {

},
create: function (canvas) {
this.canvas = canvas;
this.context = canvas.getContext('2d');
img = new Image();
img.src = this.imgUrl;
img.onload = function () {
this.display()//i cant doing this :(
}
return this;
}
}

</script>
<canvas id="scene" width="800" height="600"></canvas>
<script>

var scene = document.getElementById('scene');
var obj = gameObject.create(scene);


</script>


thanks a lot.

Answer

I'd advise not to use this, and to separate logic and state/data instead.

Conceptually, your game could be structured as follows:

  • state: An object describing the current state of the world, i.e. objects with their positions, velocities, colors, ...
  • update function: This function is called with every game step. It takes the current state of the world and the user input, and returns a new state.
  • render function: It takes the state of the world, and draws it to the screen.

Your game logic can then be summarized like this:

// initialize state
const state = { /*...*/ }

// set up game loop
window.requestAnimationFrame(function() {
  userInput = getUserInput();
  state = update(state, userInput);
  render(state);
});

Why? The different parts of your game like core logic, rendering and reading user input should know as less as possible about each other - that way you can develop them independently from each other.

Also separating the game state from it's logic helps a lot, not only to develop a clear "mental model" of your game. Just imagine you want to implement a saving/loading feature - all you'd have to do is serialize/deserialize your game state.

I tried to apply this concept to your code:

var state = {
  x: 0,
  y: 0,
  velocity: 5,
  width: 40,
  height: 40,
  triggerObject: null,
  imgUrl: "/assets/minionframe/1.png"
  img: null
}

function display(context, state) {
  context.clearRect(0, 0, this.canvas.width, this.canvas.height);
  context.drawImage(state.img, state.x, state.y, state.width, state.height);
}

function move(direction, state) {
  if (direction == "right")
    state.x += state.velocity;
  else if (direction == "left")
    state.x -= state.velocity;
  else if (direction == "up")
    state.y -= state.velocity;
  else if (direction == "down")
    state.y += state.velocity;
  
  return state;
}

function loadImage(state) {
  var img = new Image();
  img.src = state.imgUrl;
  state.img = img
}

var context = document.getElementById('scene').getContext('2d');
loadImage(state);

// some kind of game loop
window.requestAnimationFrame(function() {
    var direction = "right"; // TODO read from user input
    state = move(direction, state);
    display(context, state)
})

However, this is still not perfectly separated:

  • It would be ideal if display (our render function) would not need the argument context. Maybe it should initialize the canvas itself, when it is first run?
  • img and imgUrl are redundant. Maybe it would make sense to drop imgUrl and set img during initialization?

I hope this helps to get you started anyway.

Comments