Scripting Weapons: Projectiles and Damage
Scripting Weapons: Projectiles and Damage — Free class in Alife Virtual School
Welcome, creators, to Alife Virtual School! In the ever-expanding metaverse, the ability to create interactive and dynamic experiences is the ultimate currency. Few skills are more impactful than learning how to script weapons. This isn't just about combat; it's about game design, interactive storytelling, and pushing the boundaries of what's possible in a free 3D world. In this advanced tutorial, Scripting Weapons: Projectiles and Damage, we will embark on a deep dive into the LSL scripting functions that power the most engaging roleplay and combat systems across Alife Virtual. You will learn to harness the power of llRezObject for spawning projectiles, master vector calculations for realistic velocity, and implement collision detection to build damage-enabled systems that bring your virtual creations to life.
The Alife Advantage: Build Without Barriers
In many virtual worlds, the dream of building complex systems like a combat-ready weapon is often gated by significant real-world costs. Land fees, upload fees, and marketplace transaction fees create a financial barrier to entry. Alife Virtual was built on a different philosophy: pure, unhindered creation. Here, your only limit is your imagination. Let's compare the cost of developing and deploying the weapon we're about to build.
| Feature / Action | Alife Virtual Cost | Competitors (e.g., Second Life) |
|---|---|---|
| Private Island for Development (65,536 sqm) | $0 (Free, Forever) | ~$300 USD / month |
| Uploading Weapon Mesh | $0 (Free & Unlimited) | L$10+ per upload (varies with complexity) |
| Uploading Projectile Texture | $0 (Free & Unlimited) | L$10 per upload |
| Uploading Firing Sound Effect | $0 (Free & Unlimited) | L$10 per upload |
| Total Cost to Build & Test | $0 | $300+ initial monthly cost, plus per-asset fees |
This stark difference highlights the freedom you have in Alife Virtual. You can experiment, fail, and succeed without spending a dime. This makes our platform the ideal learning environment and a true haven for builders in the Open Simulator ecosystem, especially for those looking for a powerful Second Life alternative with a genuinely free virtual economy.
What You Will Learn
By the end of this master-level class, you will be able to:
- Create a launcher object and a projectile object.
- Use the
llRequestPermissionsandrun_time_permissionsevent to enable object rezzing. - Calculate precise starting positions and velocity vectors for projectiles.
- Master the
llRezObjectfunction to spawn projectiles from your weapon. - Write a projectile script that detects collisions using the
collision_startevent. - Identify collided targets and communicate "damage" to them on a specific channel.
- Implement essential cleanup routines to prevent lag and maintain sim performance.
Prerequisites
This is an Advanced scripting class. Before you begin, you should have a solid understanding of the following LSL concepts:
- Variables (
integer,float,string,vector,rotation,key). - Functions and Events (especially
state_entry,touch_start). - Control Flow (
if/elsestatements). - Basic vector and rotation mathematics.
- Object inventory management (adding scripts and other items to a prim).
- Basic build tools in the Firestorm viewer.
Part 1: Setting the Stage - The Weapon and the Projectile
Every weapon system needs two core components: the launcher (the gun, wand, etc.) and the projectile (the bullet, magic bolt, etc.).
- Create the Launcher: Rez a simple cube prim. This will be our "gun." Name it
Weapon Launcher. - Create the Projectile: Rez a small sphere prim. Make it visually distinct—perhaps bright red. Name this object
Projectile. - Prepare the Inventory:
- In the
Weapon Launcher's inventory, create a new script. Name itweapon_script. - Now, drag the
Projectileobject from the world into theWeapon Launcher's inventory. This places a copy of the projectile inside the launcher, ready to be rezzed. You can now delete the originalProjectilefrom the world.
- In the
Your Weapon Launcher prim should now contain two items: the weapon_script and the Projectile object.
Part 2: The Launcher Script - Firing the Projectile
Open the weapon_script inside your Weapon Launcher. This script's job is to listen for a touch, calculate where the projectile should go, and then rez it with the correct velocity.
Erase the default content and insert the following code. We'll break it down line by line.
weapon_script.lsl
// The name of the projectile object in this prim's inventory.
string gProjectileName = "Projectile";
// How fast the projectile will travel (meters per second).
float gProjectileSpeed = 50.0;
// An offset to make the projectile appear in front of the avatar, not inside them.
vector gFireOffset = <1.0, 0.0, 0.0>;
default
{
state_entry()
{
// When the script starts, it asks for permission to take money.
// This is a legacy requirement for rezzing objects from prim inventory.
// In Alife Virtual's free economy, no money is ever taken.
llRequestPermissions(llGetOwner(), PERMISSION_DEBIT);
}
run_time_permissions(integer perm)
{
// This event triggers after the owner responds to the permission dialog.
if (perm & PERMISSION_DEBIT)
{
llOwnerSay("Weapon armed and ready. Click to fire.");
}
}
touch_start(integer total_number)
{
// Get the avatar who touched the weapon.
key toucher = llDetectedKey(0);
// We only want the owner to be able to fire it.
if (toucher == llGetOwner())
{
// --- Calculation Step ---
// Get the avatar's current rotation.
rotation userRot = llGetAgentInfo(toucher) & AGENT_ROTATION;
// Calculate the forward direction vector based on the avatar's rotation.
vector forwardVec = llRot2Fwd(userRot);
// Calculate the projectile's velocity by multiplying direction by speed.
vector projectileVel = forwardVec * gProjectileSpeed;
// Calculate the starting position. Start at the avatar's position...
vector startPos = llGetAgentInfo(toucher) & AGENT_POSITION;
// ...then add an offset so it appears in front of them. The offset must also be rotated.
startPos += gFireOffset * userRot;
// --- Firing Step ---
// Rez the projectile from inventory!
llRezObject(gProjectileName, startPos, projectileVel, userRot, 0);
}
}
}
Launcher Script Breakdown:
gProjectileName: Must exactly match the name of the projectile object in your launcher's inventory.gProjectileSpeed: A simple float to control how fast the projectile moves. Higher numbers mean faster speed.gFireOffset: Avectorthat pushes the projectile's starting point forward. Without this, the projectile would rez at the avatar's center and likely collide with them immediately.llRequestPermissions(..., PERMISSION_DEBIT): This is a crucial step. To rez an object that is `no-copy`, the script needsPERMISSION_DEBIT. Even though our projectile is copyable and no money is transferred in Alife's free virtual economy, this permission is still required by the LSL scripting engine forllRezObjectto function reliably. The owner only needs to grant this once.run_time_permissions(): This event handles the result of the permission request, giving the user feedback.touch_start(): The trigger event. When the owner clicks the weapon:- It gets the avatar's rotation with
llGetAgentInfo. - It converts this
rotationinto a forward-facingvectorusingllRot2Fwd. This tells us which way the avatar is looking. - It calculates the final velocity (
projectileVel) by scaling the direction vector by our desired speed. - It calculates the starting position (
startPos) by getting the agent's position and adding the rotated offset. This is key: multiplying the offset vector by the user's rotation ensures the projectile always appears in front of them, no matter which way they are facing. llRezObject(...): This is the main event. It takes the projectile name, calculated position, velocity, and rotation, and spawns the object into the world. The final parameter,0, is a start parameter that can be used to send an integer to the projectile's script, which we'll explore later.
- It gets the avatar's rotation with
Pro Tip: Object Permissions
Before proceeding, right-click your
Weapon Launcher, go to Edit -> Content tab. Right-click theProjectileobject in the inventory and select Properties. Ensure the "Next owner" permissions are set to Copy/Modify/Transfer. If the projectile is `no-copy`,llRezObjectwill fail unless you havePERMISSION_DEBITand the object is also transferable.
Part 3: The Projectile Script - Collision and Cleanup
A projectile that flies forever is just space junk. A useful projectile needs to know when it hits something and then clean itself up. Let's create the script for our projectile object.
- Right-click the
Weapon Launcherand go to Edit -> Content. - Double-click the
Projectileobject in the inventory to open it. - In the
Projectile's build window, go to the Content tab and create a new script. Name itprojectile_script.
Now, open projectile_script and insert the following code.
projectile_script.lsl
// A communication channel for damage messages.
integer gDamageChannel = -123456789;
// How many "points" of damage this projectile does.
float gDamageAmount = 10.0;
// How long the projectile lives before disappearing (in seconds).
float gLifeTime = 10.0;
// --- Private variables ---
key gOwnerKey;
// Function to handle the impact effects.
do_impact(key target)
{
// Make a small explosion effect.
llParticleSystem([
PSYS_PART_FLAGS, PSYS_PART_EMISSIVE_MASK | PSYS_PART_INTERP_COLOR_MASK | PSYS_PART_INTERP_SCALE_MASK,
PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_EXPLODE,
PSYS_PART_START_COLOR, <1.0, 0.5, 0.0>,
PSYS_PART_END_COLOR, <0.5, 0.0, 0.0>,
PSYS_PART_START_SCALE, <0.5, 0.5, 0.0>,
PSYS_PART_END_SCALE, <2.0, 2.0, 0.0>,
PSYS_PART_MAX_AGE, 0.5,
PSYS_SRC_MAX_AGE, 0.2,
PSYS_SRC_BURST_RATE, 0.1,
PSYS_SRC_BURST_PART_COUNT, 10
]);
// Check if we hit an avatar.
if (llGetAgentSize(target) != ZERO_VECTOR)
{
// We hit an avatar! Send them a damage message.
// The message format is: "damage_type,amount,attacker_key"
string damageMessage = "kinetic," + (string)gDamageAmount + "," + (string)gOwnerKey;
llRegionSayTo(target, gDamageChannel, damageMessage);
}
// The projectile has done its job, now it must be destroyed.
llDie();
}
default
{
on_rez(integer start_param)
{
// This event runs as soon as the object is rezzed.
// First, get the key of the person who fired the weapon.
gOwnerKey = llGetOwner();
// Make the object physical so it can collide with things.
llSetStatus(STATUS_PHYSICS, TRUE);
// Set a timer to self-destruct if it doesn't hit anything.
llSetTimerEvent(gLifeTime);
}
collision_start(integer num_detected)
{
// This event fires when the object collides with something.
integer i = 0;
for (i = 0; i < num_detected; i++)
{
// Get the UUID of the object we collided with.
key detectedKey = llDetectedKey(i);
// We don't want to hit the person who fired the weapon.
if (detectedKey != gOwnerKey)
{
do_impact(detectedKey);
// The llDie() in do_impact() will stop the script, so no need for a break.
}
}
}
timer()
{
// If the timer runs out, the projectile disappears.
// This prevents lag from missed shots.
llDie();
}
}
Projectile Script Breakdown:
gDamageChannel: A negative integer used for private communication. Roleplay HUDs listen on specific channels for messages like this.gDamageAmount: The amount of "damage" to report.gLifeTime: A failsafe. If the projectile misses everything, it will self-destruct after 10 seconds to prevent it from cluttering the sim.on_rez(): This event is critical. It runs automatically whenllRezObjectcreates the projectile.- It captures the owner's key. This is important for crediting the "kill" or "hit" and for preventing the projectile from hitting the user who fired it.
llSetStatus(STATUS_PHYSICS, TRUE): This makes the object subject to the physics engine, enabling collisions.llSetTimerEvent(gLifeTime): Starts the self-destruct countdown.
collision_start(): The heart of the damage system.- It loops through everything it hit in a single physics frame.
llDetectedKey(i)gets the unique UUID of the collided object or avatar.- It checks that the detected key is not the owner to avoid self-damage.
- It calls our custom function,
do_impact().
do_impact(): A helper function to keep our code clean.- It creates a simple particle explosion for visual feedback.
llGetAgentSize(target) != ZERO_VECTOR: This is a clever and efficient way to check if a collided key belongs to an avatar. Objects have no "agent size," so the function returns<0,0,0>. Avatars return their bounding box size.llRegionSayTo(target, gDamageChannel, ...): This is the communication. It "says" a message on our private channel, but only the specified target avatar (the one who was hit) can "hear" it. A health HUD worn by that avatar would be scripted to listen on this channel and process the message.llDie(): The most important cleanup function. It removes the projectile from the world, ending its script and freeing up resources.
timer(): The failsafe event. Ifcollision_startis never triggered, this event will fire aftergLifeTimeseconds, callingllDie()to clean up the missed shot.
Once you've saved this script, close the projectile's edit window. Your weapon is now fully scripted! Wear the Weapon Launcher, wait for the permission request, grant it, and click to fire.
Common Mistakes to Avoid
- Forgetting
llDie(): The single biggest cause of sim lag in combat sims is orphaned physical projectiles. Always have a cleanup plan, both on collision and on a timer.- Incorrect Projectile Permissions: If your projectile is
no-copyorno-transferin its inventory properties,llRezObjectwill often fail silently. Always check the "next owner" permissions on the projectile inside the launcher.- Phantom Projectiles: If you forget
llSetStatus(STATUS_PHYSICS, TRUE), your projectile will be "phantom" and pass through everything without triggering a collision.- Rezzing Inside Yourself: A zero or small offset vector will cause the projectile to spawn inside your avatar, triggering an immediate collision with yourself.
Advanced Applications and Further Learning
This basic system is a powerful foundation. Here are ways to expand upon it:
- Hitscan vs. Projectile: What we built is a projectile weapon. A "hitscan" weapon is different; it doesn't rez an object. Instead, it uses
llCastRayto instantly trace a line from the user to a target and determine if it hit. This is better for fast bullets, while projectiles are better for rockets, grenades, or magic spells. - Area of Effect (AoE) Damage: In the projectile's
do_impact()function, instead of just checking the collided target, you could usellSensorto find all avatars within a radius (e.g., 5 meters) of the impact point and send a damage message to all of them. - Integration with a Health HUD: The real magic of this system is when it interacts with another scripted object. A simple HUD can be scripted to listen using
llListenon thegDamageChannel. When it hears a message, it can parse the string to deduct health from a variable and update a display for the wearer. - Ammunition: You can add a variable to the weapon script, like
integer gAmmo = 20;. Decrement it with each shot (gAmmo--;) and add a check withintouch_start:if (gAmmo > 0) { ...fire... }.
Practice Exercise
Challenge yourself to apply what you've learned. Modify the scripts to create a "Chain Lightning" bolt:
- When the projectile hits an avatar, it should still do damage as normal.
- After the initial impact, it should use
llSensorto find one other avatar (not the owner and not the initial target) within 10 meters. - If another avatar is found, use
llRezObjectagain from the projectile itself to fire a *new* projectile at the second target. - Hint: You will need to give the projectile a copy of itself in its own inventory to do this. You will also need to calculate a new velocity vector from the impact point to the new target.
Frequently Asked Questions (FAQ)
- 1. My weapon says "Armed and ready," but nothing happens when I click. Why?
- This is almost always a problem with
llRezObjectfailing silently. Check these three things: 1) Is thegProjectileNamestring in the script an exact match for the object name in inventory? 2) Are the "next owner" permissions for the projectile inside the launcher set to allow at least Copy and Modify? 3) Are you on land that disallows object creation ('no-build' land)? - 2. How can I make the projectile fire from the tip of my gun model, not just in front of me?
- This requires a more advanced offset calculation. A common method is to link a tiny, invisible prim to the tip of your gun barrel. In your script, you can get its position and rotation using
llGetLinkPrimitiveParamsand use that as the starting point for yourstartPosanduserRotcalculations, instead of usingllGetAgentInfo. - 3. How do I make my weapon fully automatic?
- You can use the
touchandtouch_endevents to control a timer. Intouch_start, start a repeating timer withllSetTimerEvent(0.1)(for 10 shots per second). In thetimer()event, place your firing code. In thetouch_endevent, turn the timer off withllSetTimerEvent(0.0). - 4. How do I protect my final script so no one can steal my work?
- When you are ready to distribute your weapon, you can change the script's permissions. In the script window, go to File -> Set Permissions. Uncheck "Modify" and "Transfer" for the next owner. This will make your script no-modify and no-transfer in the final product you sell or give away. Always keep a full-permission copy for yourself!
Your Creative Journey Starts Here. Build Without Limits.
You now possess the foundational knowledge to build complex, engaging, and exciting interactive items in the metaverse. The skills you've learned today are your passport to creating the next great combat game, roleplay adventure, or interactive art piece in Alife Virtual.
Why wait? The tools are free, the land is free, and a vibrant community of creators is waiting. Claim your FREE 65,536 sqm private island in Alife Virtual today! Experience the power of unlimited uploads, a professional mesh avatar from day one, and a 100% free economy. Stop paying to create and start creating. Welcome to the future of the free 3D world.
🎓 Ready to Practice In-World?
Get your FREE island and practice everything you just learned — no credit card, no monthly fees.
Claim Your Free Island Now →No credit card required · Takes 2 minutes · Your island is FREE forever