Here's what I've got so far:
#define WORLD_ICON_SIZE 64
#define WORLD_HALF_ICON_SIZE 32
var/tmp/projectiles = 0
atom/movable/proc/set_pos_px(px, py)
//this sets the tile (x, y) and the pixel(x, y) from absolute coordinates
src.x = px / WORLD_ICON_SIZE
src.y = py / WORLD_ICON_SIZE
src.pixel_x = px % WORLD_ICON_SIZE - WORLD_HALF_ICON_SIZE
src.pixel_y = py % WORLD_ICON_SIZE - WORLD_HALF_ICON_SIZE
proc/atan2(x, y)
//a quick atan2()
//I think lummox jr wrote this somewhere deep in the byond forum
if(!x && !y) return 0
return y >= 0 ? arccos(x / sqrt(x * x + y * y)) : -arccos(x / sqrt(x * x + y * y))
proc/get_angle(atom/a, atom/b)
//returns the angle between two atoms (in radians)
var/res = atan2(b.y - a.y, b.x - a.x)
res += 270//right hand conversion
while(res > 360) res -= 360
while(res <= 0) res += 360
return res
proc/get_dir_angle(atom/a, atom/b)
//converts an angle into a direction, for projectile facing
var/n = get_angle(a, b)
if(n >= 337.5 || n <= 22.5) return EAST
if(n >= 22.5 && n <= 67.5) return SOUTHEAST
if(n >= 67.5 && n <= 112.5) return SOUTH
if(n >= 112.5 && n <= 157.5) return SOUTHWEST
if(n >= 157.5 && n <= 202.5) return WEST
if(n >= 202.5 && n <= 247.5) return NORTHWEST
if(n >= 247.5 && n <= 292.5) return NORTH
if(n >= 292.5 && n < 337.5) return NORTHEAST
pixel_projectile
parent_type = /obj
animate_movement = NO_STEPS
var/tmp
angle = 0
px = 0 //the true pixel location of the projectile
py = 0
last_x = 0 //the last x/y coordinate of the tile we were in
last_y = 0
velocity = 10 //speed of the projectile
atom/owner
New()
..()
owner = usr
if(!owner) return
if(!owner:target) return
projectiles++
loc = owner.loc
if(owner.target == loc) del src
//set initial position
px = x * WORLD_ICON_SIZE + WORLD_HALF_ICON_SIZE
py = y * WORLD_ICON_SIZE + WORLD_HALF_ICON_SIZE
//calculate angle
angle = get_angle(src, owner.target)
//begin!
spawn update_loop()
Del()
projectiles--
..()
Cross(atom/movable/a)
//if something happens to cross this tile while we're sitting in it, which would happen after initially entering the tile
//we should check for collide!
if(a != src && a != owner) collide_with(a)
return ..()
proc/update_loop()
//update position
src.px = src.px + src.velocity * cos(src.angle)
src.py = src.py + src.velocity * -sin(src.angle)
//check if new position is valid
if(!check_bounds()) del src // ?
//update x, y, pixel_x, pixel_y to reflect new location
src.set_pos_px(src.px, src.py)
//did we enter a new tile?
if(last_x != x || last_y != y)
last_x = x
last_y = y
if(!loc) del src
//collide with everything in the tile
for(var/atom/a in loc.contents - src - owner)
collide_with(a)
//do it all over again, unless the world is slow, then wait a little longer
spawn(world.tick_lag * (world.cpu >= 10 ? world.cpu / 10 : 1)) update_loop()
proc/check_bounds()
//checks to see if a pixel is in bounds of the map
if(px < WORLD_ICON_SIZE || py < WORLD_ICON_SIZE || px >= (world.maxx + 1) * WORLD_ICON_SIZE || py >= (world.maxy + 1) * WORLD_ICON_SIZE)
return 0
return 1
proc/collide_with(atom/a)
//generic collide
if(a.type == src.type) return
//world << "[src] collides with [a]"
del src
pixel_projectile/test
icon = 'demo/gfx/mob.dmi'
icon_state = "bot"
Assumes:
-all atoms have a target variable which is equal to another atom at the time of firing. not necessary but I use that to calculate the angle at which to fire.
-assumes the owner is a mob, not a turf or an obj
-assumes the person who created the projectile is the owner
-this is a very basic projectile, there are many other cases where you might want the projectile to die (like if it runs into walls). those aren't handled here for simplicity sake of just making the fastest possible pixel projectile.
What I would really like to do is put delta_time in there somewhere so that as the loop updates get further apart from over-processing time... the projectiles actually move along at a frame independent speed.
Anyways, other than that, I don't see room for improvement. Anyone here be my guest and make a suggestion for improving the efficiency of the pixel_projectile!