Scripting 14 - RayCast and HitTest
Scripting in SFD assumes you have a fair knowledge of C#.
The following code demonstrates how to perform RayCasts in the world between two points. Available in v.1.3.0.
Code: Select all
// Example script how to RayCast in the world from a player and show some debugging information about the hit objects while testing the map in the editor.
public void OnStartup()
{
Events.UpdateCallback.Start(OnUpdate, 0);
}
public void OnUpdate(float ms)
{
IUser[] users = Game.GetActiveUsers();
IPlayer plr = (users != null && users.Length > 0 ? users[0].GetPlayer() : null); // any player instance
if (plr != null) {
Vector2 worldPos = plr.GetWorldPosition() + Vector2.UnitY * 12f;
Vector2 worldPosEnd = worldPos + plr.AimVector * 100f;
if (Game.IsEditorTest) {
Game.DrawLine(worldPos, worldPosEnd, Color.Red);
}
// RayCastInput offers some filtering capabilities, for now just filter on everything with a set CategoryBit (effectively ignoring background objects).
RayCastInput rci = new RayCastInput() { IncludeOverlap = true, MaskBits = 0xFFFF, FilterOnMaskBits = true };
// You can also filter on specific types. If you only want to raycast players you would add the IPlayer type to the Types array property: rci.Types = new Type[1] { typeof(IPlayer) };
RayCastResult[] results = Game.RayCast(worldPos, worldPosEnd, rci);
foreach(RayCastResult result in results) {
if (Game.IsEditorTest) {
Game.DrawCircle(result.Position, 1f, Color.Yellow);
Game.DrawLine(result.Position, result.Position + result.Normal * 5f, Color.Yellow);
Game.DrawArea(result.HitObject.GetAABB(), Color.Yellow);
Game.DrawText(result.HitObject.UniqueID.ToString(), result.Position, Color.Yellow);
}
// Destroy nearby glass that the player is looking at
if (result.Fraction < 0.3f && result.HitObject.Name.IndexOf("glass", StringComparison.OrdinalIgnoreCase) >= 0) {
result.HitObject.Destroy();
}
}
}
}
To perform a HitTest on any IObject simply use the bool IObject.HitTest(Vector2 position) function. You can also perform RayCasts on individual IObjects.
Code: Select all
IObject obj = ... // any IObject instance
if (obj.HitTest(Vector2.Zero)) {
// Object overlaps the center of the world
}
Vector2 pA = Vector2.Zero;
Vector2 pB = Vector2.UnitX * 100f;
if (obj.RayCast(pA, pB, true).Hit) {
// Object hit between pA and pB
}
ScriptAPI Implementation for RayCast and HitTest:
► Show Spoiler
Code: Select all
In the Game class:
/// <summary>
/// Performs a ray cast in the world.
/// Use with care as it can get CPU intense depending on your raycast distance and how many you perform each update!
/// Does not work with far background objects.
/// </summary>
/// <param name="start">Start position</param>
/// <param name="end">End position</param>
/// <param name="input">Input parameters</param>
/// <returns>RayCastResult[] hit results based on RayCastInput data.</returns>
public abstract RayCastResult[] RayCast(Vector2 start, Vector2 end, RayCastInput input);
In the IObject class:
/// <summary>
/// Performs a ray cast on this specific object, returning the hit result.
/// Always returns a false hit if the object is disposed or removed.
/// </summary>
/// <param name="start">Start world position</param>
/// <param name="end">End world position</param>
/// <param name="value">RayCastResult</param>
public abstract RayCastResult RayCast(Vector2 start, Vector2 end);
/// <summary>
/// Performs a ray cast on this specific object, returning the hit result.
/// Always returns a false hit if the object is disposed or removed.
/// </summary>
/// <param name="start">Start world position</param>
/// <param name="end">End world position</param>
/// <param name="includeOverlap">If overlapping objects at the start of the RayCast should be included.</param>
/// <param name="value">RayCastResult</param>
public abstract RayCastResult RayCast(Vector2 start, Vector2 end, bool includeOverlap);
/// <summary>
/// Performs a hittest on this object.
/// </summary>
/// <param name="position">World position</param>
/// <returns>True if position overlaps the object, false otherwise.</returns>
public abstract bool HitTest(Vector2 position);
Help structures for in and out data:
/// <summary>
/// RayCastFilterMode for the RayCastInput filters.
/// </summary>
public enum RayCastFilterMode
{
/// <summary>
/// 0, Any (True or False)
/// </summary>
Any = 0,
/// <summary>
/// 1, True
/// </summary>
True = 1,
/// <summary>
/// 2, False
/// </summary>
False = 2
}
/// <summary>
/// RayCastInput
/// </summary>
[Serializable()]
public struct RayCastInput
{
/// <summary>
/// Types that the RayCast would accept for collision.
/// </summary>
public Type[] Types;
/// <summary>
/// The collision mask bits. This states the categories that this RayCast would accept for collision if FilterOnMaskBits is set to true.
/// </summary>
public UInt16 MaskBits;
/// <summary>
/// If MaskBits should be used for filtering.
/// </summary>
public bool FilterOnMaskBits;
/// <summary>
/// If melee attacks are blocked by this object or not.
/// </summary>
public RayCastFilterMode BlockMelee;
/// <summary>
/// If projectiles hit this object or not.
/// </summary>
public RayCastFilterMode ProjectileHit;
/// <summary>
/// If explosions are blocked by this object or not.
/// </summary>
public RayCastFilterMode BlockExplosions;
/// <summary>
/// If fire is blocked by this object or not.
/// </summary>
public RayCastFilterMode BlockFire;
/// <summary>
/// If overlapping objects at the start of the RayCast should be included.
/// </summary>
public bool IncludeOverlap;
/// <summary>
/// If you only care about the closest result you should enable this as it optimizes the RayCast and always returns one RayCastInput result.
/// </summary>
public bool ClosestHitOnly;
}
/// <summary>
/// RayCastResult
/// </summary>
[Serializable()]
public struct RayCastResult
{
/// <summary>
/// If a hit was registered.
/// </summary>
public readonly bool Hit;
/// <summary>
/// Hit object ID.
/// </summary>
public readonly int ObjectID;
/// <summary>
/// Hit object.
/// </summary>
public readonly IObject HitObject;
/// <summary>
/// If the hit object is a player.
/// </summary>
public readonly bool IsPlayer;
/// <summary>
/// Fraction of the input start/end positions.
/// </summary>
public readonly float Fraction;
/// <summary>
/// Hit position.
/// </summary>
public readonly Vector2 Position;
/// <summary>
/// Hit normal. Is Vector2.Zero for results with fraction 0 where the start position overlaps the object if IncludeOverlap is enabled.
/// </summary>
public readonly Vector2 Normal;
}