3
\$\begingroup\$

I'm making a simple HTML5 RPG in pure JavaScript likened after Nox, Diablo or Titan Quest, with the character centered on the screen and the map moving around it.

TL;DR: My character, when colliding with the map edge, can still move outside the map in some cases. How do I quell this inconsistency?

Explanation: I have simple rectangles for the map, character, and one enemy, and I'm using bounding-box collision detection for them. When the character pushes against the map's edge, it stops moving, but the enemy box moves opposite of my character's trajectory of motion. When the enemy hits the wall, it stops, but the collision detection for my centered character fails and I'm flung into the void.

Here is a screenshot demonstrating this (map=white, character=blue, enemy=red): The enemy even moves while my character is outside of the map!

Here are some bits of relevant code that might help:

Box object:

function Box() {
    this.x = 0;
    this.y = 0;
    this.width  = 50;
    this.height = 50;

    this.init = function(x, y, w, h, c) {
        ...
    };

    this.move = function(dir, amt, reverse) {
        ...
    };

    // stops 'obj' from leaving 'this'
    this.constrain = function(obj) {
        if(this.x > obj.x) {
            this.x = obj.x;}
        if(this.y > obj.y) {
            this.y = obj.y;}
        if(this.x + this.width  < obj.x + obj.width) {
            this.x = obj.x + obj.width  - this.width;}
        if(this.y + this.height < obj.y + obj.height) {
            this.y = obj.y + obj.height - this.height;}
    };
    this.draw = function() {...};
}

Main code:

var m = new Box();
var p = new Box();
var e = new Box();

function rand(a, b) {return ((Math.random()* (b - a)) + a);}

// monster moves away from player on player collision with map.
// player moves off map on monster collision with map.
function init() {
    p.init(cn.width/2 - p.width/2, cn.height/2 - p.height/2, 20, 20, "blue");
    m.init(p.x, p.y, 200, 200, "white");
    e.init(rand(m.x, m.x + m.width  - e.width), rand(m.y, m.y + m.height - e.height), 20, 20, "red");
}

function update() {
    if(key.left)  {
        m.move('left',  p.speed, true);
        e.move('left',  p.speed, true);
    }
    ...

    m.constrain(p);
    m.constrain(e);
}

function draw() {
    c.clearRect(0, 0, cn.width, cn.height);
    m.draw();
    p.draw();
    e.draw();
}

How would I make both the monster and the character consistently collide with the map?

I'm sorry in advance if the code/post is too long. I spent a good, long while shortening it up. I'll happily edit it, just ask. Thank you! :)

\$\endgroup\$
1
\$\begingroup\$

Your constrain function seems to be flipped.

You call m.constrain(p); which is going to call:

// stops 'obj' from leaving 'this' (except that you shouldn't be setting variables of *this*)
this.constrain = function(obj) {
    if(this.x > obj.x) {
        this.x = obj.x;}
    if(this.y > obj.y) {
        this.y = obj.y;}
    if(this.x + this.width  < obj.x + obj.width) {
        this.x = obj.x + obj.width  - this.width;}
    if(this.y + this.height < obj.y + obj.height) {
        this.y = obj.y + obj.height - this.height;}
};

But it's not going to stop anything because your assignment operators are setting the map's coordinates instead of your passed object's coordinates

You want:

// stops 'obj' from leaving 'this'
this.constrain = function(obj) {
    if(obj.x < this.x) {
        obj.x = this.x;}
    if(obj.y < this.y) {
        obj.y = this.y;}
    if(obj.x + obj.width  > this.x + this.width) {
        obj.x = this.x + this.width  - obj.width;}
    if(obj.y + obj.height > this.y + this.height) {
        obj.y = this.y + this.height - obj.height;}
};

You have the right idea, but know that = does not set variables on both side

\$\endgroup\$
1
\$\begingroup\$

I finally found the solution!

Draw the enemies with respect to the map's position!

Just draw the map, and then the entities on top of it a certain distance away from the map's top-left corner. This way, no matter how the map's position changes, the entities will stay put because they're drawn based on where the map is. The player is drawn on top of everything else because the player isn't supposed to move at all.

Honeybunch's answer will not work. Reversing this and obj in constrain() moves the player around instead of stopping the map. The player's position never changes, as I said in my very first sentence in the post.

The problem is not with constrain(), but with motion in general. My player may have stopped at the map's edge, but I was still moving the monster around; it hasn't reached the edge yet, so why would it stop? Movement is relative to a point of reference, and this is easily forgotten.

My only point of confusion now is why my player was able to go outside the map at all, but that's inconsequential now.

Here's the code that works:

this.getOffsetsFrom = function(obj) {
    return [obj.x + this.x,  obj.y + this.y];
};

this.draw = function(obj) {
    context.fillRect(
        this.getOffsetsFrom(obj)[0],
        this.getOffsetsFrom(obj)[1],
        this.width, this.height
    );
};
\$\endgroup\$
  • 1
    \$\begingroup\$ For your future edification, the generalized form of this is to separate the concept of world space (where things are in the world) from screen space (where things in world space - including the world itself - are drawn on the screen). This ultimately simplifies both your movement code and your drawing code even more than you already have. \$\endgroup\$ – Sean Middleditch Aug 25 '15 at 16:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.