ID:2050794
 
(See the best response by Lummox JR.)
I want to create a bullet hell battle system in a single player game, however all projectile tutorials seem to only be showing me how to have a player shoot a projectile. So what would be the best way to make bullets come from predetermined places?

I've been able to create an object at a set location, but I'm not sure how to have it move without making it into a mob. Any and all help would be appreciated.
Dragon441 wrote:
I want to create a bullet hell battle system in a single player game, however all projectile tutorials seem to only be showing me how to have a player shoot a projectile. So what would be the best way to make bullets come from predetermined places?

There's zero difference between a player and a turf shooting a projectile, except in how you set the projectile's initial position and direction--both of which are easy.

I've been able to create an object at a set location, but I'm not sure how to have it move without making it into a mob. Any and all help would be appreciated.

I don't understand. Objs and mobs move the exact same way.

In response to Lummox JR
I've been using the program for about 3 days, so unfortunately I haven't figured out everything. But how would I have a turf shooting a projectile? Is there a sample code I can use in the program walkthroughs?

What seems to be the default setting for mobs is that they move with the directional arrows. However I want to bullets to fall automatically without input from the player.
Best response
You're confusing mobs in general with mobs controlled by a player. A mob doesn't have to be player-controlled. An NPC for instance would usually be a mob, but doesn't move with the arrow keys because no one is logged into that mob.

A simple bullet hell setup might look something like this:

obj/projectile
icon = 'bullet.dmi'
// this is only an 8x8 region in the center of a 32x32 icon
bound_x = 13
bound_y = 13
bound_width = 20
bound_height = 20
density = 1
var/atom/owner
var/vx, vy

New(newloc, atom/_owner, _vx, _vy)
// loc is set to newloc automatically, so no need to do it here
owner = _owner
vx = _vx
vy = _vy
bullets += src

// let all bullets pass through each other
Cross(atom/movable/mover)
if(istype(mover, /obj/projectile)) return 1
return ..()

Bump(atom/A)
A.Hit(src, owner)
// you can add bullet hit animation/effects here
bullets -= src
loc = null // let the garbage collector pick this up

atom/proc/Hit(atom/movable/hitby, atom/owner)
// fill this in for subtypes, like for mob

var/list/bullets = new // global bullet list

// call this in world/New()
proc/PhysicsLoop()
spawn(-1)
for(var/obj/projectile/B in bullets)
B.Move(B.loc, B.step_x+B.vx, B.step_y+B.vy)

There are lots of ways this type of system can be setup, but that should give you the gist. If a turf fires a projectile, you could call new/obj/projectile(loc,vx,vy,src) to do so. The vx and vy values are something you'll have to work out for yourself, probably either by tracking a target or just having the turf change its firing angle.
In response to Lummox JR
Thank you very much, this helps out a lot.
In response to Lummox JR
About the "loc = null" statement, Lummox; is this a better alternative to del? I believe I've heard that del has some fairly nasty overhead associated with it--is this true?
About the "loc = null" statement, Lummox; is this a better alternative to del? I believe I've heard that del has some fairly nasty overhead associated with it--is this true?

I answer this question fairly often.

There's nothing bad about del. However, you need to know what it does before you ever use it.

When you put a datum in a variable (lists, and contents (being on the map) count too!), that's called referencing the datum.

BYOND keeps track of how many references a datum has.

When an object no longer has any references, it will be garbage collected (read: deleted, freeing up memory).

When an object has remaining references and should be deleted, those references become what are called "hanging references". Hanging references will cause an object to not be garbage collected, so until you tell it to be destroyed manually, it isn't going anywhere. The engine is a loyal animal. It's like a dog. It doesn't know you didn't mean to step on its tail. You just stepped on its tail. The engine assumes that you know what you are doing at every step, so as long as it can understand what you are telling it what you want it to do, it will do exactly that. It can't know these hanging references are in error.

Furthermore, the object can't know what variables are referencing it. It pretty much literally has to search through every object variable, list, and global variable for references to itself until the refcounter reaches zero. The bigger your game, the slower this is of course.

By using the del instruction, you are telling the engine to search every bit of memory it is currently using for a specific object. That's quite costly.

Let's talk about another interesting problem. Circular references.

Circular references are a minimum of two objects that contain references to each other. Since they reference one another, while they both exist, the other can't be garbage collected automatically. Circular references are a common error in programming that lead to memory leaks.

var/obj/o1 = new()
var/obj/o2 = new()
o1.owner = o2
o2.owner = o1


The above objects are now a reference circle.

The del instruction does have some optimization. It will not search memory at all if the object you are deleting has zero references left. Meaning if you clean up all existing references to the object, the object will be deleted instantly. However, some things reference objects that you have little control over, so it's best to simply track and clean references as you create them using parity and circular references.

Let's look at a common bit of code that causes memory leaks all the time in BYOND games:

mob
var/tmp
mob/target
proc
setTarget(mob/ntarg)
target = ntarg


Now whatever mob you are targeting will never be deleted until you change targets, and there's no way to avoid a manual del to destroy the object.

How do we fix this? Circular references to the rescue! Remember how I made an example of circular references causing problems? Well used responsibly, they can actually fix the problem that they cause.

mob
var/tmp
list/targeting
mob/target
proc
setTarget(mob/ntarg)
if(ntarg==target) return 0
if(target)
var/list/l = target.targeting
l -= src
if(!l.len) target.targeting = null
target = ntarg
if(target)
if(!target.targeting)
target.targeting = list(src)
else
target.targeting += src


So how does this solve our problem? We're going to create a Cleanup() function that will take care of all references that we know of.

datum/proc/Cleanup()

mob/Cleanup()
if(target)
var/list/l = target.targeting
l -= src
if(!l.len)
target.targeting = null
if(targeting)
for(var/mob/m in targeting)
m.target = null
targeting = null
loc = null


And that's called managing your references. If you manage your references properly as you create them, it's much easier than after you've started working on a game. You can avoid the deletion overhead completely with proper planning.