Growler

NPC Healthbar with DOM element

Recommended Posts

I am creating a healthbar for NPCs and minions. I wasn't sure how to do it quickly with Melon (on Melon 2.1.1), so I'm doing it with the DOM.

For this, I thought it'd be easy enough to append a healthbar element for each instantiated minion entity, then update the healthbar <div> as the entity moves.

The problem is, the healthbar's location is initially set just fine, but as the main player moves, the healthbars tend to follow oddly with the player, moving away from the minion entities.

I need the healthbar to stay consistently over the heads of the minion entities. How can I do this?

let $healthBar = $(`<div class="healthbar_${this.id}">`);

let updateLocation = () => {
            var coords = me.game.viewport.worldToLocal(this.pos.x, this.pos.y);
            $healthBar.css({top: `${coords.y}px`, left: `${coords.x}px`});

             ...

 

Share this post


Link to post
Share on other sites

Looks like you are using an isometric map, so what you are probably missing is to convert back the position to an orthogonal projection (using http://melonjs.github.io/melonJS/docs/me.Vector2d.html#to2d) before calling the WorldToLocal function.

 

So in pseudo code, something like this :

1. localVector = copy(player.pos)

2 . LocalVector.to2d()

3. coords = worldToLocal (LocalVector)

 

Share this post


Link to post
Share on other sites

oh I see :)

is it not an issue with you updateLocation method and how it access the minion current position ? I see `this.pos.x` there, so is updateLocation a method of the minion class ?

Share this post


Link to post
Share on other sites

@obiot code snippet provided below of the minion's Entity class.

healthbar:function() {

         ...
         var updateLocation = () => {
			var coords = me.game.viewport.worldToLocal(this.pos.x, this.pos.y);
			$healthBar.css({top: `${coords.y}px`, left: `${coords.x}px`});
			if (!this.inViewport) {
				hideHealthbar();
			} else {
				showHealthbar();
			}
		};

        return {
           ...
           updateLoc: updateLocation,
        }
    },

update: function(delta) {
        ...
        this.body.update(delta);

        if(!me.collision.check(this)) {
			if (this.nearNPC) this.nearNPC = false;
		}

		return (this._super(me.Entity, 'update', [delta]) || this.body.vel.x !== 0 || this.body.vel.y !== 0);
	},

onCollision : function (response, collidedObject) {
		if (collidedObject.type === 'npcs') {
			return false;
		}
		if (collidedObject.type === 'PlayerEntity' ) {
			if (!this.nearNPC) this.nearNPC = true;
			return false;
		}
		if (collidedObject.type === 'AttackObject') {
			if (this.nearNPC) this.nearNPC = false;
			return false;
		}
		if (collidedObject.type === 'collisionShape') {
			if (this.nearNPC) this.nearNPC = false;
			return false;
		}
		if (this.nearNPC) this.nearNPC = false;

		// Make all other objects solid
		return false;
	}

 

Share this post


Link to post
Share on other sites

I think you might be having problems with the DOM element position because the canvas is scaled. If you disable the video scaling mode, you should see the element position align with the entity. You can try this to validate the hypothesis that video scaling is the issue. If this identifies the cause, then you can fix it by scaling the position by the same factor as the video scaling. You might have to compute the scaling factor from the initial resolution and actual canvas dimensions...

Share this post


Link to post
Share on other sites

 

@Parasyte

Not sure how to "disable" scaling as me.video's scale method takes a string or number, not a boolean. I assume changing to 1.0 "disables" scaling.

Also, you can see that the healthbar DOM element "moves" (updates) as I move my player. This makes sense from a logic perspective because the enemy entity's location is changing respective to the player in the viewport, but it shouldn't update the healthbar's location over the minion.

Changes shown below to game.js' scaleMethod:

                wrapper : "screen",
                renderer : me.video.CANVAS,
                scale : "auto",  // changed to scale : 1.0
                scaleMethod : "flex-width",
                doubleBuffering : true,
                transparent : true,
                antiAlias : true

This makes my canvas tiny but doesn't fix the DOM elements. Am I misunderstanding what you're saying?

 

Screen Shot 2017-09-05 at 12.42.01 PM.png

Share this post


Link to post
Share on other sites

sorry but i'm out of idea myself.... all I can say is that it does not seems an issue on melonJS side, so I'd rather look at your page composition (HTML/CSS) to see if something there is acting weird.

did you try to output the actual returned coords in the console, and mornitor the ouput correspondingly to the player position ? at least it would allow eliminate a part of the equation, and to be fixed on either the coordinates returned by melonJS are correct or not.

On a side note, you should also use a local vector object in your entity and pass it to the `localToWorld ` function, this will avoid instantiating a vector object at every frame and will make the GC more happy :)

https://github.com/melonjs/melonJS/blob/master/src/renderable/viewport.js#L486

Share this post


Link to post
Share on other sites

Ok, the test was successful. It means the scaling is what's causing the issue. You can fix it by applying the same transformation to the DOM element position. You can get the transformation by reading the canvas width and height, and dividing by the width and height that is passed to me.video.init(). This will give you two values; one for width and one for height. When you go to move the DOM elements, multiply the x and y coordinates with the two scaling values you got from the last step.

Share this post


Link to post
Share on other sites

Hi @Parasyte - thanks for this. The health bars align over the NPCs' heads, but still don't stay perfectly over them. They still seem affected by the player's movement.

Code:

		var updateLocation = () => {
			let canvasW = $("#myCanvas").width();
			let canvasH =	$("#myCanvas").height();
			console.log('Canvas W/H: ', canvasW, canvasH); // [1375.5, 867]

			let ratioW = globals.settings.aspectratio[game.data.ratio][0];
			let ratioH = globals.settings.aspectratio[game.data.ratio][1];
			console.log('Aspect Ratio:', ratioW, ratioH); // [800, 600]

			let divX = canvasW / ratioW;
			let divY = canvasH / ratioH;

			let coords = me.game.viewport.worldToLocal(this.pos.x, this.pos.y);

			let newCoordsX = coords.x * divX;
			let newCoordsY = coords.y * divY;

			$healthBar.css({top: `${newCoordsY}px`, left: `${newCoordsX}px`});

Pictures: If I move the player left, it moves the healthbar opposite. If I move the player right, it moves the healthbar over the NPC's head.

 

Screen Shot 2017-09-12 at 11.03.41 AM.png

Screen Shot 2017-09-12 at 11.03.47 AM.png

Screen Shot 2017-09-12 at 11.03.55 AM.png

Share this post


Link to post
Share on other sites

I just noticed you're using the "flex-width" scaling method, which means the canvas aspect ratio is flexible along the horizontal axis. The vertical aspect ratio can be used to scale both dimensions equally. In your example code:

let divY = canvasH / ratioH;

This is the ratio you want to scale both axes by. So if you change the newCoordsX to scale by divY instead, it will all work:

let newCoordsX = coords.x * divY;
let newCoordsY = coords.y * divY;

BTW, all of this scaling logic will have to change if you decide to switch to a different video scaling mode.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now


  • Recently Browsing   0 members

    No registered users viewing this page.