need help with my field of vision

● ARCHIVED · READ-ONLY
Started by Monkey BizNiz 9 posts View original ↗
  1. Hey guys,

    I made a little event that calculates a triangle field of vision for npcs, but its kinda long and unefficent. So i was hoping to get some help making my code more efficient, shorter, or learning alternative methods of handling this problem(I prefer not to use plugins).

    This is how the code looks, it basically checks, when the event is looking to the right, if the player is on the specific tiles:
    Code:
    ◆If:Script:$gameMap.event(this._eventId).direction() === 6
      ◆If:Script:[$gameMap.event(this._eventId).x+1, $gameMap.event(this._eventId).x+2, $gameMap.event(this._eventId).x+3, $gameMap.event(this._eventId).x+4, $gameMap.event(this._eventId).x+5, $gameMap.event(this._eventId).x+6].contains($gamePlayer.x) && [$gameMap.event(this._eventId).y-1, $gameMap.event(this._eventId).y, $gameMap.event(this._eventId).y+1].contains($gamePlayer.y)
        ◆Jump to Label:jump
        ◆
      :End
      ◆If:Script:[$gameMap.event(this._eventId).x+3, $gameMap.event(this._eventId).x+4, $gameMap.event(this._eventId).x+5, $gameMap.event(this._eventId).x+6].contains($gamePlayer.x) && [$gameMap.event(this._eventId).y-2, $gameMap.event(this._eventId).y+2].contains($gamePlayer.y)
        ◆Jump to Label:jump
        ◆
      :End
      ◆If:Script:[$gameMap.event(this._eventId).x+5, $gameMap.event(this._eventId).x+6].contains($gamePlayer.x) && [$gameMap.event(this._eventId).y-3, $gameMap.event(this._eventId).y+3].contains($gamePlayer.y)
        ◆Label:jump
        ◆Play SE:Bell3 (15, 150, 0)
        ◆
      :End
      ◆
    :End
  2. Code:
    for (var i = 1; i < 7; i++) if ($gameMap.event(this._eventId).x + i == $gamePlayer.x) for (var j = -1; j < 2; j++) if ($gameMap.event(this._eventId).y + j == $gamePlayer.y) jump to label function you can find inside rpg_objects
    Have this in the script field instead of conditional branch. It!s an ugly one liner, but you can hardly simplify from there.
    Then you'd need to do similar thing for the two remaining lines.
  3. Disclaimer: I will be computing performance by counting how many operations are needed to process a given code.
    My calculations are not 100% accurate, especially because JavaScript does lots of stuff behind the scenes which can't be accounted for, so take it with a grain of salt.


    Your code is slow and inefficient, because you're checking each possible coordinate for the player's presence.
    In short, you're doing this:
    is player.x in x, x+1, x+2, [...], x+6?

    So your code is doing 6 comparisons (in this case only, and you have significantly more cases to check for).
    Fortunately, it's not the worst code you could've written. You could've done the same 6 checks for each of the 3 possible y coordinates where they are relevant, totaling 6 * 3 checks (18). Luckily, you're only doing 6 checks + 3 checks (9) instead.

    Your code would be more efficient if you used a bit of maths.
    Basically, you want this:
    is player.x - event.x >= 1 and player.x - event.x <=6?
    or better yet:
    xDistance = player.x - event.x
    is xDistance >= 1 and xDistance <= 6?

    1 subtraction, 2 comparisons. A total of 3 operations (instead of 9). Faster and more efficient.


    Now let's add the y coordinates to the mix.
    The x coordinates you're checking for depend on the y coordinates. Luckily, there is a pattern, and a fairly simple one at that.
    For y = -1 to 1, you check x = 1 to 6.
    For y = -2 or 2, you check x = 3 to 6.
    For y = -3 or 3, you check x = 5 to 6.

    So x increases by 2 when y (the absolute value thereof) increases by 1.
    At this point, we could loop through all 7 possible y values, but that's not very efficient either, because the negative y values behave exactly the same as the positive ones.

    Since we have to check for all y values anyway (unlike with the x values, which depend on y), we'll use our above (pseudo) code for y instead.
    yDistance = abs(player.y - event.y)
    is yDistance <= 3?

    As you can notice, our pseudo code changed a bit.
    First, we use abs (the absolute value), which turns negative numbers into their positive counterparts (and leaves the positive numbers as they are). Like I said, we don't care about negative y, since they behave the same as positive y.
    Next, we no longer check for a minimum, because the lowest value we get from abs is 0, and 0 is a valid value for y.

    1 subtraction, 1 abs, 1 comparison. 3 operations for y.
    Now that we know our player's y coordinate is within range, let's check the x coordinate.
    minX = 1
    if yDistance > 0, minX = 1 + (yDistance - 1) * 2
    xDistance = player.x - event.x
    is xDistance >= minX and xDistance <= 6?

    We determined the lowest x value through yDistance (which we already determined earlier), and used that to determine if our player's x coordinate is within range.

    3 comparisons, 1 addition, 2 subtractions, 1 multiplication, 1 abs. 8 operations for x.
    Put together, we have 11 operations for the entire process.

    Your original code does 6 + 3 comparisons, 4 + 2, and 2 + 2. A total of 19 comparisons.
    In actuality, your code is even slower than that, because
    • 18 out of 19 comparisons need an addition (+18 operations)
    • you're retrieving the same event every time (+1 addition every time you call $gameMap.event(), total 19 additional operations)
    • you're (implicitly) looping through an array (+1 comparison and +2 additions per item in your array).

    But to be fair, with today's CPUs, counting performance in operations is just nitpicking. It only really matters if you're doing these operations millions of times per second. I doubt your original code will truly affect your game's performance much, unless you have lots of events looking for the player.


    Finally, let's turn our pseudo code into actual (JavaScript) code.
    Code:
    var ev = $gameMap.event(this._eventId);
    var yDist = Math.abs($gamePlayer.y - ev.y);
    if (yDist > 3) return false;
    
    var minX = yDist === 0 ? 1 : 1 + (yDist - 1) * 2;
    var xDist = $gamePlayer.x - ev.x;
    return xDist >= minX && xDist <= 6;

    The above code is for when the event is looking right.
    Of course, you'll need to change it a bit for the other cases (for instance, instead of player.x - event.x, you do event.x - player.x when looking left).
    And since Conditional Branch requires one-liners, you can just remove the line breaks from the code. I just left them there because it's easier to read (and change) the code with them.
  4. @Poryg I tried to use your code in the following way
    Code:
    ◆Script:for (var i = 1; i < 7; i++) if ($gameMap.event(this._eventId).x + i == $gamePlayer.x) for (var j = -1; j < 2; j++) if ($gameMap.event(this._eventId).y + j == $gamePlayer.y) this.command119("testlabel");
    ◆Exit Event Processing
    ◆Label:testlabel
    ◆Play SE:Bell3 (15, 150, 0)

    With the jump to label I tried different combinations; this.jumpTo(), this.command119(), but no success so far, any advice?
    Also id love to know the logic behind what ''for (var i = 1; i < 7; i++)'' means, because im unfamiliar with the "for", and the ''++'' things(im a scripting rookie!).

    @Nolonar Wow such an elaborate tutorial, very informative! Although sadly, when i remove the line breaks and put it into a conditional branch
    Code:
    ◆If:Script:var ev = $gameMap.event(this._eventId); var yDist = Math.abs($gamePlayer.y - ev.y); if (yDist > 3) return false; var minX = yDist === 0 ? 1 : 1 + (yDist - 1) * 2; var xDist = $gamePlayer.x - ev.x; return xDist >= minX && xDist <= 6;
      ◆Play SE:Bell3 (90, 100, 0)
      ◆
    :End

    I get the error "Illegal return statement", and I dont know enough about the language, and programming construction to understand what i need to change to make it work. Id love some further advice.
  5. Code:
    for (var i = 1; i < 7; i++) if ($gameMap.event(this._eventId).x + i == $gamePlayer.x) for (var j = -1; j < 2; j++) if ($gameMap.event(this._eventId).y + j == $gamePlayer.y) {
    var labelName = "testlabel";
        for (var i = 0; i < this._list.length; i++) {
            var command = this._list[i];
            if (command.code === 118 && command.parameters[0] == labelName) this.jumpTo(i);
        }
    };

    Monkey BizNiz said:
    Also id love to know the logic behind what ''for (var i = 1; i < 7; i++)'' means, because im unfamiliar with the "for", and the ''++'' things(im a scripting rookie!).
    I will not be explaining basic Javascript to you. There are other sites for it, my favorite is sololearn.com, which is also free.
  6. Monkey BizNiz said:
    I get the error "Illegal return statement", and I dont know enough about the language, and programming construction to understand what i need to change to make it work. Id love some further advice.
    Ah, sorry.
    I'm not used to "eval", which is what Conditional Branch Script is using, so a few things that are normally possible need to be done a bit differently.

    In our case, just put the code inside a "function", like this:
    Code:
    "(function() { insert code here }())"

    Just replace "insert code here" with our code (without line breaks).
    Make sure you keep all the brackets.

    function
    This is a keyword with which we define functions. It is necessary.

    function()
    Inside the round brackets we define the arguments needed for our function. Since it doesn't need arguments, those brackets must be empty.

    function() {}

    The curly brackets is where we put our code. Without them, the function doesn't know where its code ends. The white space between the brackets is not necessary, I just like putting it there.

    function() {}()
    The additional round brackets at the end are here to execute the function using the arguments given inside. Since our function doesn't need arguments, we can leave it empty. Without those brackets at the end, our function won't execute, so it's necessary.

    (function() {}())
    The round brackets around our function (it's getting complicated, I know) is needed because our code will be executed inside an eval.
    Eval doesn't like "function", so we put brackets around it to fool eval into thinking "there is no function". It's dirty, but it should work.

    If it still doesn't, let me know.
  7. @Poryg That works! And thanks for the link, ill see if i can learn anything from there.

    @Nolonar When i put this in an event without the quotation marks:
    Code:
    ◆If:Script:(function() {var ev = $gameMap.event(this._eventId); var yDist = Math.abs($gamePlayer.y - ev.y); if (yDist > 3) return false; var minX = yDist === 0 ? 1 : 1 + (yDist - 1) * 2; var xDist = $gamePlayer.x - ev.x; return xDist >= minX && xDist <= 6;}())
      ◆Play SE:Bell3 (90, 100, 0)
      ◆
    :End

    I sadly still get an error - a different one tho "cannot read property 'y' of undefined" when i start up my game.
    When I do keep the quotation marks however, the game doesnt seem to respond to the event at all.
    Also thanks for the explanation with the function, as someone who likes learning about topics at hand, i can appreciate that!
  8. Monkey BizNiz said:
    I sadly still get an error - a different one tho "cannot read property 'y' of undefined" when i start up my game.
    When I do keep the quotation marks however, the game doesnt seem to respond to the event at all.
    Also thanks for the explanation with the function, as someone who likes learning about topics at hand, i can appreciate that!
    You were right to remove the quotation marks. I forgot that RMMV already puts our code in quote marks, so we don't need to do it ourselves.

    I finally found the mistake.

    this._eventId
    The "this" keyword is different inside our function than outside of it.

    So here's what we need to change:
    We need a variable where we put the "this" we need. I'll go with $this as the variable name.
    $this._eventId

    Next, we need to tell our function what $this is.
    If you remember what I said about function, our function wrapper must now look like this:
    (function($this) { insert code here }(this))

    To summarize: Here's the code we made for right-side detection (fixed version):
    Code:
    var ev = $gameMap.event($this._eventId);
    var yDist = Math.abs($gamePlayer.y - ev.y);
    if (yDist > 3) return false;
    var minX = yDist === 0 ? 1 : 1 + (yDist - 1) * 2;
    var xDist = $gamePlayer.x - ev.x;
    return xDist >= minX && xDist <= 6;

    Here's what you need to paste inside the Conditional Branch (fixed version):
    Code:
    (function($this){var ev = $gameMap.event($this._eventId); var yDist = Math.abs($gamePlayer.y - ev.y); if (yDist > 3) return false; var minX = yDist === 0 ? 1 : 1 + (yDist - 1) * 2; var xDist = $gamePlayer.x - ev.x; return xDist >= minX && xDist <= 6;}(this))
  9. @Nolonar It works perfectly! I cant thank you enough bro. Thanks for going out of your way to explain things aswell, I appreciate it.