Issues with param canvas id for new Engine()

43 views
Skip to first unread message

Noizy

unread,
Apr 18, 2019, 12:47:18 PM4/18/19
to excaliburjs
Hi everyone,


i got stuck with an issue  when i tried to assing the id for canvas i got the next error:


this appear only when i add the parameter canvasElementId


this is my code:


Constants:
engine: {
displayMode: {
Fixed: ex.DisplayMode.Fixed, // Show the game as a fixed size
Position: ex.DisplayMode.Position, // Allow the game to be positioned with the (EngineOption.pos) option
FullScreen: ex.DisplayMode.FullScreen, // Show the game as full screen
Container: ex.DisplayMode.Container // Scale the game to the parent DOM
},
controls: {
pointerScope: ex.Input.PointerScope.Document
},
canvas: {
width: 150,
height: 150,
canvasElementId: 'game'
}
}

Creating Engine:
const game = new ex.Engine({
width: Settings.engine.canvas.width,
height: Settings.engine.canvas.height,
displayMode: Settings.engine.displayMode.Fixed,
// canvasElementId: Settings.engine.canvas.canvasElementId,
pointerScope: Settings.engine.controls.pointerScope
});

Canvas(html):
<canvas id={Settings.engine.canvas.canvasElementId} width={Settings.engine.canvas.width} height={Settings.engine.canvas.height} />

Im using excalibur(edge) + react + typescript
 

thanks for this awesome framework.



Erik Onarheim

unread,
Apr 19, 2019, 11:04:36 PM4/19/19
to excaliburjs
Hi Noizy,

I think this may be a timing issue between react (or other jsx style UI library) and excalibur. If I understand properly, react operates on the DOM asynchronously to set attributes.

It's possible that the canvas element does not yet have it's `id` bound by react at the time the excalibur engine is created, so when excalibur looks for the canvas by that id, it cannot be found at the time of new ex.Engine().

if (options.canvasElementId) {
this._logger.debug('Using Canvas element specified: ' + options.canvasElementId);
this.canvas = <HTMLCanvasElement>document.getElementById(options.canvasElementId);

What happens when you hard code the id to something like this?
<canvas id="my-canvas-id" width={Settings.engine.canvas.width} height={Settings.engine.canvas.height} />


const game = new ex.Engine({
width: Settings.engine.canvas.width,
height: Settings.engine.canvas.height,
displayMode: Settings.engine.displayMode.Fixed,
canvasElementId: "my-canvas-id",
pointerScope: Settings.engine.controls.pointerScope
});


Unfortunately I don't know enough about react to offer a good solution there. I've requested some assistance some team members that have experience with react style libraries.

jedeen

unread,
Apr 19, 2019, 11:38:12 PM4/19/19
to excaliburjs
It looks like this might be an issue with the ordering of the code. In order for Excalibur to render, the canvas needs to exist in the browser's DOM. If your game object is being instantiated before your React render() call, the canvas will not exist in the DOM yet, which will result in the error you are seeing.

You should be able to fix this error by moving the declaration of your game object to after React's render() method.

var Settings = {
engine: {
displayMode: {
Fixed: ex.DisplayMode.Fixed, // Show the game as a fixed size
Position: ex.DisplayMode.Position, // Allow the game to be positioned with the (EngineOption.pos) option
FullScreen: ex.DisplayMode.FullScreen, // Show the game as full screen
Container: ex.DisplayMode.Container // Scale the game to the parent DOM
},
controls: {
pointerScope: ex.Input.PointerScope.Document
},
canvas: {
width: 150,
height: 150,
canvasElementId: 'game'
}
}
}

// defining game object before React render() call throws the "Cannot set property 'width' of null" error
// const game = new ex.Engine({
// width: Settings.engine.canvas.width,
// height: Settings.engine.canvas.height,
// displayMode: Settings.engine.displayMode.Fixed,
// canvasElementId: Settings.engine.canvas.canvasElementId,
// pointerScope: Settings.engine.controls.pointerScope
// });

ReactDOM.render(
<canvas id={Settings.engine.canvas.canvasElementId} width={Settings.engine.canvas.width} height={Settings.engine.canvas.height} />, document.getElementById('root')
)

// define game object after React render() call does not result in the error
const game = new ex.Engine({
width: Settings.engine.canvas.width,
height: Settings.engine.canvas.height,
displayMode: Settings.engine.displayMode.Fixed,
canvasElementId: Settings.engine.canvas.canvasElementId,
pointerScope: Settings.engine.controls.pointerScope
});

Kamran Ayub

unread,
Apr 19, 2019, 11:42:20 PM4/19/19
to excaliburjs
On jedeen's approach, try that but if it still doesn't work, ReactDOM.render provides a callback that is invoked after React is done rendering the DOM:


See where I log "start game now" the DOM has been loaded fully by React.
Message has been deleted

Noizy

unread,
Apr 23, 2019, 12:12:57 PM4/23/19
to excaliburjs
Thanks for the help, i resolved the issue.

Like how say Erik, is about the render timing of React, so i 'encapsulated' the creation of engine and call it when the web is loaded (included the canvas) and work perfectly.


this is how i fix it:

principal file:
const startt = () => {
startGame();
};
return (
<div>
<button onClick={startt}>Start Game</button>
<canvas
id={Settings.engine.canvas.canvasElementId}
width={Settings.engine.canvas.width}
height={Settings.engine.canvas.height}
/>
</div>
);

startgame() file:

function startGame() {
const game = new ex.Engine({
width: Settings.engine.canvas.width,
height: Settings.engine.canvas.height,
displayMode: Settings.engine.displayMode.Fixed,
canvasElementId: Settings.engine.canvas.canvasElementId,
pointerScope: Settings.engine.controls.pointerScope
});
const world = new ex.Scene(game);

game.add('world', world);
game.goToScene('world');
game.start().then(function() {
console.warn('started');
hiddenMenu = React.createContext('hidden');
});
return {};
}
Reply all
Reply to author
Forward
0 new messages