ID:2046827
 
(See the best response by Ter13.)
Code:
mob
var
angle = 0
speed = 5
proc/Test(d)

if(d==EAST)
usr.angle = 0
usr.dir = d
else if(d==NORTHEAST)
usr.angle = 45
usr.dir= d
else if(d==NORTH)
usr.angle = 90
usr.dir= d
else if(d==NORTHWEST)
usr.angle = 135
usr.dir= d
else if(d==WEST)
usr.angle = 180
usr.dir= d
else if(d==SOUTHWEST)
usr.angle = 225
usr.dir= d
else if(d==SOUTH)
usr.angle = 270
usr.dir= d
else if(d==SOUTHEAST)
usr.angle = 315
usr.dir= d
var/dx = round(cos(usr.angle) * usr.speed)
var/dy = round(sin(usr.angle) * usr.speed)
usr.pixel_move(dx, dy)





turf
icon_state = "floor"

wall
density = 1
icon_state = "wall"


MouseEntered()
var/d = get_dir(usr,src)
usr.Test(d)


Problem description:
I am trying to make a system where mob will walk to the mouse location. I know this might not be the best solution but I manage to make it right somehow. The problem with it is that once you enter the turf with a mouse it wont update MouseEntered() proc so if the mouse is not moved to the other turf, the mob will not move.
Best response
First, you'll need to know about some utility definitions:

TILE_WIDTH is a define macro that should be set to the number of pixels your tiles are wide.
TILE_HEIGHT is a define macro that should be set to the number pixels your tiles are tall.
FPS is a define macro that should be set to your world FPS.
TICK_LAG is a define macro that should be set to 10/FPS.

DIR_VERTICAL is a binary shorthand for NORTH|SOUTH
DIR_HORIZONTAL is a binary shorthand for EAST|WEST

floor/ceil/inner/outer are all rounding functions. floor always rounds left, ceil always rounds right, inner rounds toward zero, and outer rounds away from zero.

clamp ensures a value is between a set high and low

atan2 returns an angle from a unit delta
ang2dir returns a direction from a euler angle



#define TILE_WIDTH 32
#define TILE_HEIGHT 32
#define FPS 40
#define TICK_LAG (10/FPS)

#define floor(x) round(x)
#define ceil(x) (-round(-(x)))
#define inner(x) (x<0 ? ceil(x) : floor(x))
#define outer(x) (x<0 ? floor(x) : ceil(x))

#define clamp(v,l,h) min(max(v,l),h)

#define DIR_VERTICAL 3
#define DIR_HORIZONTAL 12

var
list/__euler_dirs = list(EAST,NORTHEAST,NORTH,NORTHWEST,WEST,SOUTHWEST,SOUTH,SOUTHEAST)

proc
atan2(x,y) //return an angle from an x/y offset
return (x||y)&&(y>=0 ? arccos(x/sqrt(x*x+y*y)) : 360-arccos(x/sqrt(x*x+y*y)))

ang2dir(x)
//ensure that the angle is between 0 and 360
if(x<0)
x = x+360*ceil(x/-360)
. = __euler_dirs[floor(ceil(x/22.5)%16 / 2)+1]


Let's just go ahead and set up the client to your specifications. MouseMove() is a far better function to bind than MouseEntered(), and tracking based on the screen position of the mouse is far better than tracking based on objects within the map.

client
var
cursor_x = 0
cursor_y = 0

MouseMove(atom/object,atom/location,control,params)
if(isturf(location)&&control=="default.map1") //make sure that the name of your default map control is here!
//get the screen coordinates of the cursor
var/list/l = params2list(params)
var/tx = l["screen-loc"]
var/ty = copytext(tx,findtext(tx,",")+1)
//find the pixel screen coordinates of the cursor
cursor_x = (text2num(tx)-1)*TILE_WIDTH + text2num(copytext(tx,findtext(tx,":")+1))
cursor_y = (text2num(ty)-1)*TILE_HEIGHT + text2num(copytext(ty,findtext(ty,":")+1))
else
cursor_x = null
cursor_y = null

proc
MoveLoop()
set waitfor = 0
var/mx,my,cx,cy,dx,dy,ang,dist,s
while(1)
if(cursor_x!=null&&cursor_y!=null)
//calculate the center of the mob:
mx = (mob.x-1)*TILE_WIDTH+mob.step_x+mob.bound_x+mob.bound_width/2
my = (mob.y-1)*TILE_HEIGHT+mob.step_y+mob.bound_y+mob.bound_height/2
//calculate the x/y coordinates of the cursor according to the map:
cx = cursor_x + bound_x
cy = cursor_y + bound_y
//determine the delta position of the center of the mob and the cursor's map position:
dx = cx - mx
dy = cy - my
//calculate the angle of the mouse's position from the center of the mob.
ang = atan2(dx,dy)
//calculate the distance of cursor from the position of the center of the mob.
dist = sqrt(dx*dx + dy*dy)
if(dist>=24)
s = mob.speed
mob.Step(cos(ang)*s,sin(ang)*s)
sleep(TICK_LAG)

Move() //override move so using the default movement keys doesn't do anything.

New()
. = ..()
if(.)
screen += new/obj/screenbg()
MoveLoop()

obj
screenbg
mouse_opacity = 2
screen_loc = "WEST,SOUTH to EAST,NORTH"
plane = -100
layer = 0


And here's a code snippet that will implement smooth subpixel movement and give you an easy helper function that will let you move your mob by a set number of pixels or subpixels on each x/y axis.

atom
proc
EdgeTouch(atom/movable/o)
var/x1 = (x-1)*TILE_WIDTH
var/y1 = (y-1)*TILE_HEIGHT
var/x2 = x1 + TILE_WIDTH
var/y2 = y1 + TILE_HEIGHT

var/ox1 = (o.x-1)*TILE_WIDTH+o.bound_x+o.step_x
var/oy1 = (o.y-1)*TILE_HEIGHT+o.bound_y+o.step_y
var/ox2 = ox1 + o.bound_width
var/oy2 = oy1 + o.bound_height

. = 0
if(x2==ox1) . += WEST
else if(ox2==x1) . += EAST
if(oy2==y1) . += NORTH
else if(y2==oy1) . += SOUTH
movable
var
rem_x = 0
rem_y = 0
speed = 4
tmp
obstacle_dir = 0
proc
Step(dx,dy)
if(isturf(loc)&&(dx||dy))
var/px = clamp((x-1)*TILE_WIDTH+step_x+bound_x+rem_x+dx,0,world.maxx*TILE_WIDTH-1)
var/py = clamp((y-1)*TILE_HEIGHT+step_y+bound_y+rem_y+dy,0,world.maxy*TILE_HEIGHT-1)
var/tx = floor(px/TILE_WIDTH)+1
var/ty = floor(py/TILE_HEIGHT)+1
px -= (tx-1)*TILE_WIDTH
py -= (ty-1)*TILE_HEIGHT
var/oss = step_size
var/nss = ceil(max(abs(dx),abs(dy)))
step_size = nss
. = Move(locate(tx,ty,z),ang2dir(atan2(dx,dy)),px,py)
if(step_size==nss) step_size = oss
else
return 0

Move(atom/NewLoc,Dir=0,Step_x=0,Step_y=0)
obstacle_dir = 0
. = ..()
if(.)
rem_x = obstacle_dir&DIR_HORIZONTAL ? 0 : Step_x-inner(Step_x)
rem_y = obstacle_dir&DIR_VERTICAL ? 0 : Step_y-inner(Step_y)

Bump(atom/Obstacle)
obstacle_dir |= Obstacle.EdgeTouch(src)

EdgeTouch(atom/movable/o)
var/x1 = (x-1)*TILE_WIDTH+bound_x+step_x
var/y1 = (y-1)*TILE_HEIGHT+bound_y+step_y
var/x2 = x1 + bound_width
var/y2 = y1 + bound_height

var/ox1 = (o.x-1)*TILE_WIDTH+o.bound_x+o.step_x
var/oy1 = (o.y-1)*TILE_HEIGHT+o.bound_y+o.step_y
var/ox2 = ox1 + o.bound_width
var/oy2 = oy1 + o.bound_height

. = 0
if(x2==ox1) . += WEST
else if(ox2==x1) . += EAST
if(oy2==y1) . += NORTH
else if(y2==oy1) . += SOUTH
Thank you for this Ter13. This is far better than I would ever done by myself. And thank you for the explenation I apreciate it alot !
This is far better than I would ever done by myself.

It just comes with time and understanding the tools at your disposal.

BYOND actually makes a lot of this much more difficult than it needs to be in that pixel movement is sort of hamfisted into a tile-based engine. The engine's core really wasn't designed for pixel movement, so the pixel movement stuff had to be interleaved with the existing core's assumptions.

We're also very unlucky in that we don't have a lot of trig stuff built in and there aren't a lot of good examples of trig laying around.

A couple of the things that this example relies on are completely new to the engine in the last year or two, and very few people use/know about/understand them.

Among them are client.bounds (This is a feature I requested a few months ago and Lummox was right on top of it) and MouseMove() (This is a feature that I believe KaioChao requested maybe two years ago.).

You'll start to get it in time, and the more you reach out for assistance and try to understand the why the more experienced people do what they do, it will go faster.

Good luck!
obj
screenbg
mouse_opacity = 2
screen_loc = "WEST,SOUTH to EAST,NORTH"
plane = -100
layer = 0


For what reason plane stands for. The code is working for me just fine and I removed it.

Added :I tried out the code and sometimes the mob stops moving or wont go forward for some reason like invisible density is blocking him. Once you move your mob to other location you can sometimes enter that "invisible density" location.

http://prnt.sc/ab02re Thats how it looks like and I cannot go anymore forward.
Added :I tried out the code and sometimes the mob stops moving or wont go forward for some reason like invisible density is blocking him.

That's because there is no edge sliding solution in this example. The mob will stop moving when an obstacle is encountered rather than sliding along the edge of something they bump into.

Also, plane is a relatively new variable to the engine (It's also something that I'm sort of responsible for in a way). When stuff is drawn on the screen, they are layered by plane first, and layer second. I recommend looking it up in the reference.
Guess it was my fault. I forget to update byond so plane var didn`t work for me.

Thanks I appriciated it and everything is working just fine!
Greeds,
Synpax
I have a nother question. How can I center the mob once I use zoom-in option. I tried but it seems camera / client.eye wont fallow the mob
I have a nother question. How can I center the mob once I use zoom-in option.

How big is your dmm? What's your view set to? What are the settings on your interface's main map element?
world
view= "29x12"
icon_size=64

Map size is 39x23
All I did for the map settings in interface is stretch icons to fit and byond letteboxing for scaled map.

In response to Ter13
Ter13 wrote: That's because there is no edge sliding solution in this example. The mob will stop moving when an obstacle is encountered rather than sliding along the edge of something they bump into.

I tried to make edge sliding sulution for 2 deays now but I just can`t make it work. Could you just point me out a little bit