SceneMan:CastObstacleRay
From Data Realms Wiki
Contents |
Function
This function is used to check for any obstacles along a given vector, from start to end. If an obstacle is detected, the ray terminates, and the function outputs the total length that the ray travelled before hitting something. Should the ray fail to find an obstacle, the ray will output a negative number (typically -1, but go with < 0 to be sure). Note, however, that it can be very slightly inaccurate when outputting length (usually insignificant, but it can potentially mess up equality checks). Also note that the ray does NOT account for the level seam, so you will have to make adjustments for that.
Syntax
SceneMan:CastObstacleRay(Vector1, Vector2, Vector3, Vector4, ID, Team, Material ID, Integer)
Vector1: The absolute starting vector of the ray. In other words, where the ray starts. An input of Vector(300,200) starts the ray at x = 300 and y = 200.
Vector2: The relative vector to trace along. An input of Vector(10,-5) thus casts the ray from (300,200) to (310,195).
Vector3: A vector that will be changed to the position of the first encountered obstacle if one is found, else it will be the ending position of the ray. This preferrably needs to be a pointer to a previously defined vector (as in self.endPos = Vector(0,0)), else you will be unable to access it again.
Vector4: Exactly as above, except the output position is one step before colliding with an obstacle. Supposedly only changes if it actually hits something.
ID: The ID of an object to ignore. The ray will not collide with the object with this ID or any children of this object (setting it to the ID of an actors torso will make the ray ignore all limbs and armor as well), and will pass through unhindered.
Team: An integer defining objects of which team to ignore. Setting this to 1 will have the ray pass through anything belonging to team 1, for example. Do note, however, that Team 1 AKA Red Team in game corresponds to team 0 in the code, so you have to subtract 1 from the in-game team number in order to get the right value. This can also be self.Team or self.IgnoresWhichTeam if it should inherit the team of the object calling the function. Setting a negative value below -1 or a positive value above 3 will make it hit all teams.
Material ID: The ID of a material to ignore hits with, in addition to air (which is always ignored). Grass, for example, has a material ID of 128, so setting this argument to 128 will make the ray pass through grass.
Integer: The number of pixels to skip ahead for every pixel checked. Setting it to 1 will make the ray check every other pixel, 4 will check every 5th pixel and so on. 0 makes it check every single pixel. This is for optimization, since it can take a lot of power if used too frequently.
Useful co-functions
Raycasting doesn't automatically react to changes in rotation or horizontal flipping, so applying the following to Vector2 can be useful:
Vector(X,Y):RadRotate(angle) - Rotates the vector to have an identical magnitude in the given absolute angle. Note that setting this to self.RotAngle makes the ray always be cast in the objects relative front.
Vector(X,Y):GetXFlipped(boolean) - Whether to flip the X component of the vector or not. Since most actors can flip horizontally, setting the boolean to self.HFlipped will make the ray automatically correct itself when the object flips.
If you're seeking to cast a ray that requires both flipping and rotation to be changable (i.e. a laser from a gun held by an AHuman), you can combine the two:
(Vector(X,Y):GetXFlipped(boolean)):RadRotate(angle)
This counts as a single argument, and is much easier than making pre-emptive corrections based on rotation and flipping.
You may also want to get a pointer to the object you've hit rather than just the ID, which is where this function comes in:
MovableMan:GetMOFromID(ID) - Gets a pointer to the object that the ID refers to, similar to "actor" and such.
Since an MORay outputs an ID when it hits something, you can just put the output of the MORay in as the singular argument. However, since an MORay outputs nil if it fails to hit an object with an ID, you may want to check that the variable is not equal to nil before proceeding. An example:
local Vector2 = (Vector(100,0):GetXFlipped(self.HFlipped)):RadRotate(self.RotAngle);
local Vector3 = Vector(0,0);
local Vector4 = Vector(0,0);
self.ray = SceneMan:CastObstacleRay(self.Pos, Vector2, Vector3, Vector4, self.RootID, self.Team, 128, 1);
if self.ray < 0 then
- self.ray = Vector2.Magnitude
end
If we were to append this to a gun held by an actor, it would cast a ray 100 pixels in front of the guns position, which ignores both the actor holding it and his teammates, passes through grass, and skips every other pixel. This ray is automatically corrected in response to the actor aiming or flipping. If the ray then hits something, then self.ray becomes equal to the length between the gun and the obstacle, Vector3 is filled with the coordinates of the first obstructed position, and Vector4 is filled with the last free position. If the ray fails to hit anything, self.ray is set to the total length of Vector2, while Vector3 and Vector4 remain unchanged.
The level seam
As mentioned before, obstacle rays don't account for the level seam (which is where the level wraps around to the other side), which can seriously mess with the length of the ray, since crossing it essentially means travelling the whole length of the level in an instant. One way to counter this is to not use the length that the ray outputs, but to use the following function:
SceneMan:ShortestDistance(Vector1, Vector2, boolean) - Finds the vector that represents the shortest distance between Vector1 and Vector2. The boolean determines whether to account for level wrapping or not. Mostly, you'll want it to be "true", since the vast majority of levels feature wrapping.
Hence, if we replace the two vectors with the two positions we want to know the distance between, we get this:
SceneMan:ShortestDistance(self.Pos, Vector3, true)
This outputs a vector that directly bridges the gap between self.Pos and Vector3. However, it is NOT a length. To get the length, just append .Magnitude to the end of the function, like so:
local length = SceneMan:ShortestDistance(self.Pos, Vector3, true).Magnitude
Note, however, that this makes it impossible to access that vector without calling the function again. Therefore, you can also do like this:
local gap = SceneMan:ShortestDistance(self.Pos, Vector3, true)
local length = gap.Magnitude
Doing like that lets you keep the vector between self.Pos and Vector3, while still getting an optimal length to work with. To put it all together, here's a final example:
local Vector2 = (Vector(100,0):GetXFlipped(self.HFlipped)):RadRotate(self.RotAngle);
local Vector3 = Vector(0,0);
local Vector4 = Vector(0,0);
self.ray = SceneMan:CastObstacleRay(self.Pos, Vector2, Vector3, Vector4, self.RootID, self.Team, 128, 1);
local gap = SceneMan:ShortestDistance(self.Pos, Vector3, true)
local length = gap.Magnitude
This way, you don't even have to check for the length being smaller than 0, since the ShortestDistance function always outputs the correct vector (and thus, length). It's still possible for the ray to hit in an unexpected manner, but there isn't much to do about that.