How to use Raycasts

Get help with JavaScript coding questions and share your snippets with the community.
Post Reply
User avatar
swordmaster
Site Admin
Posts: 53
Joined: Tue May 23, 2017 11:22 pm

How to use Raycasts

Post by swordmaster » Tue Sep 26, 2017 7:26 pm

As requested by rpggamer17 I've created this brief topic to cover the raycast functionality in more detail, I'm going to cover the scenario where you fire a raycast from the player's/character's current location, in a N, S, E, W direction for a fixed range of 22 pixels. All you need to do to achieve this is call the rpgcode functions getCharacterDirection(), and getCharacterLocation(), and then provide rpgcode.fireRayCast with the point of origin, directional vector, and the range you want it to travel up to(hint: use -1 for infinity). Raycasts are useful for implementing things such as projectiles or sword slashing etc.

You can use the below vector values for each direction, I've simplified the diagonal directions for this case:

Code: Select all

case "NORTH":
 vector = { x: 0, y: -1}
 
case "SOUTH":
 vector = { x: 0, y: 1}
 
case "NORTH_EAST":
 case "SOUTH_EAST":
case "EAST":
 vector = { x: 1, y: 0 }

case "WEST":
case "NORTH_WEST":
case "SOUTH_WEST":
 vector = { x: -1, y: 0 }
You'll find a complete code example below using the vector values mentioned, fireRaycast will return an object containing 2 arrays accessed by key. These arrays will be empty if nothing is hit:

Code: Select all

      // Fire a raycast from the character's current location, using
      // the supplied direction and range in pixels.
      var hits = rpgcode.fireRaycast({
            _x: location.x,
            _y: location.y
         }, vector, attackRangePx);

      // Check to see if we hit any enemies.
      if (hits["enemies"].length > 0) {
      	// Hit at least 1 enemy.
      	rpgcode.log(hits["enemies"][0]);
      }
      if (hits["npcs"].length > 0) { 
      	// Hit at least 1 npc.
      	rpgcode.log(hits["npcs"][0]);		
      }
      if (hits["solids"].length > 0) {
      	// Hit at least 1 solids.
      	rpgcode.log(hits["solids"][0]);
      }
At the minute it can only be used to detect enemies or NPCs, it cannot detect solid or passable vectors, if requested it can be introduced of course. If you want a more detailed example you'll find my implementation of sword slashing for The Last Sword Master. The below code is activated when the player presses the mapped key (space in my game's case):

Code: Select all

var attackPushTime = 250;
var attackVelocity = 32;
var attackRangePx = 22;
var attackDamage = 1;
var vector = {};

// Can't slash our sword if we are in a hit state.
if (!rpgcode.getCharacter("Hero").isHit) {
  // Gather some of the details we need.
   var direction = rpgcode.getCharacterDirection();
   var location = rpgcode.getCharacterLocation();
   
   var vector = {};

  // This is called after the slashing animation has finished playing.
   var callback = function() {
      // Fire a raycast from the character's current location, using
      // the supplied direction and range in pixels.
      var hits = rpgcode.fireRaycast({
            _x: location.x,
            _y: location.y
         }, vector, attackRangePx);

      // Check to see if we hit any enemies.
      if (hits["enemies"].length > 0) {
         // Try is something in the array, simply attack the first enemy.
         var sprite = hits["enemies"][0]
         var spriteDirection = rpgcode.getSpriteDirection(sprite.id);
         var defendAnimationId;
         switch (spriteDirection) {
            case "NORTH":
               defendAnimationId = "DEFEND_NORTH";
               break;
            case "SOUTH":
               defendAnimationId = "DEFEND_SOUTH";
               break;
            case "NORTH_EAST":
            case "SOUTH_EAST":
            case "EAST":
               defendAnimationId = "DEFEND_EAST";
               break;
            case "NORTH_WEST":
            case "SOUTH_WEST":
            case "WEST":
               defendAnimationId = "DEFEND_WEST";
               break;
         }
         if (sprite.id === "boss") {
            var bossState = rpgcode.getGlobal("boss");
            if (bossState.vunerable) {
               hitBoss();
            }
         } else {
            var details = {
               "spriteId": sprite.id,
               "attackDamage": attackDamage,
               "direction": direction, 
               "attackVelocity": attackVelocity, 
               "attackPushTime": attackPushTime,
               "defendAnimationId": defendAnimationId,
               "attackSound": "hurtEnemy"
            };
            ARPG.attackEnemy(details, function(details, result) {
               if (result.dead) {
                  ARPG.dropItem("apple.npc", result.location);
               }
            });
         }

      }
      rpgcode.endProgram();
   };

   // Figure out the normalised vector (x, y) values to use based on 
   // the character's current direction.
   switch (direction) {
      case "NORTH":
         vector = {
            x: 0,
            y: -1,
         }
         rpgcode.animateCharacter("Hero", "ATTACK_NORTH", callback);
         break;
      case "SOUTH":
         vector = {
            x: 0,
            y: 1,
         }
         rpgcode.animateCharacter("Hero", "ATTACK_SOUTH", callback);
         break;
      case "NORTH_EAST":
      case "SOUTH_EAST":
      case "EAST":
         vector = {
            x: 1,
            y: 0,
         }
         rpgcode.animateCharacter("Hero", "ATTACK_EAST", callback);
         break;
      case "NORTH_WEST":
      case "SOUTH_WEST":
      case "WEST":
         vector = {
            x: -1,
            y: 0,
         }
         rpgcode.animateCharacter("Hero", "ATTACK_WEST", callback);
         break;
   }

   rpgcode.playSound("sword", false);
} else {
   rpgcode.endProgram();
}

rpggamer17
Posts: 45
Joined: Wed Jun 07, 2017 2:14 am

Re: How to use Raycasts

Post by rpggamer17 » Tue Jan 09, 2018 3:45 am

From looking at the issue tractor the solid vectors have now been introduced in 1.2 if I'm correct. Can you update this?

User avatar
swordmaster
Site Admin
Posts: 53
Joined: Tue May 23, 2017 11:22 pm

Re: How to use Raycasts

Post by swordmaster » Tue Jan 09, 2018 1:26 pm

You access them the same way just using the "solids" key, I've updated the example above here are the lines I added:

Code: Select all

	if (hits["solids"].length > 0) {
		// Hit at least 1 solids.
      		rpgcode.log(hits["solids"][0]);
      		rpgcode.log(hits["solids"][0].distance);
      }
You should be able to access the distance from the raycast origin to the solid using .distance, and the location with .x/.y:

rpggamer17
Posts: 45
Joined: Wed Jun 07, 2017 2:14 am

Re: How to use Raycasts

Post by rpggamer17 » Tue Jan 09, 2018 6:04 pm

Thank you. I'm hoping to attempt a sort of pathfinder of sorts soon. I suck at logic though.

User avatar
DFroGGotten1
Posts: 16
Joined: Sun Oct 29, 2017 2:31 am

Re: How to use Raycasts

Post by DFroGGotten1 » Mon Jan 29, 2018 3:18 am

could this be implemented for a 'sense' based detection for enemies, like if a PC gets in line of sight of a enemy in the field to a certain range?
"People fear the unknown, but they also fear obtaining the knowledge to understand it, for understanding the unknown means learning forsaken truths"

User avatar
swordmaster
Site Admin
Posts: 53
Joined: Tue May 23, 2017 11:22 pm

Re: How to use Raycasts

Post by swordmaster » Mon Jan 29, 2018 8:59 pm

A raycast is more like a focused beam, probably wouldn't be great for field of view unless your enemy has a laser for an eye ;) Or you fire a load of beams maybe across a 114 degree plane for a humanoid.

You could also do something as simple as getting the distance between the enemy and the player and calculating the angle between them. Then based on the direction of the enemy you could say they see the player. Although that doesn't account for obstacles that could obscure the FOV.

Short answer: yes you could use it, you just need to fire lots of them :P

Post Reply