You know the old warning about never announcing something you're working on, because you'll lose all motivation to do it? We BYOND developers actually heed a slightly different warning: When you announce something you're working on, people are bound to give you so much feedback you won't know what to do with it all. Instead of starting with big atom support and parlaying that into pixel movement, we've gone ahead with the pixel movement project full steam ahead. This means good things for BYOND games, and we've made it our mission to do this in the most intuitive way possible. Our goal has been for new projects to be able to use these features easily, and for existing games to upgrade without too much trouble. The new release is now in beta, and here's the low-down on the new features.
First, there is a new step_size var belonging to all movable atoms. If you set a mob's step_size to 4 without doing anything else, it will move with a speed of 4 pixels per tick, instead of a full tile which is the default. When it moves, its step_x and step_y vars will be updated to reflect its partial position--and these are relative to its loc. The pixel_x/y/z vars are still available for strictly visual offsets.
There are four vars you can use to set up a movable atom's bounding box: bound_x, bound_y, bound_width, and bound_height. bound_x/y are how far inward, in pixels, the atom's physical bounding box is from the lower left.
In legacy code, as you know turf/Enter() looks at all its contents to see if an atom can walk onto it or not. But if that turf is only partially covered by a dense mob, it's possible to step onto the turf but not bump the mob at all. Therefore, the Enter() proc will now ignore anything in the turf's contents that doesn't cover the entire turf. In a similar vein, mobs in the same group will no longer Bump() to swap positions but can instead overlap--but in legacy code, which is to say any situation where both mobs are not using pixel movement at all, they'll follow the old behavior.
But wait, you say, how will we be able to check on whether two mobs are about to hit? For movables we have a new family of procs that work like the Enter() family: Cross() and Uncross(), and Crossed() and Uncrossed(). Cross() is called to see if two pixel movers are allowed to overlap, and Crossed() is called after the overlap occurs. We've gone back and forth on these procs a hundred times, and at one point their functionality was actually folded into the Enter() family instead, but this notation seemed to be a lot clearer.
As far as the movement itself is concerned, you can of course use the step and walk procs as the simplest way to get around, and they all take an additional argument now for the speed you want to use (if you leave it blank, it goes with the movable's own step_size).
Moves can happen in one of two ways: A move is considered a jump if it passes a distance greater than the mover's step_size or a full tile, whichever is bigger. A jump is pass/fail only, like the moves of yore. But if the movement covers a more limited distance, it is considered a slide and can partially succeed. A movable will return 1 for a successful jump, 0 for a complete failure, or otherwise it returns the number of pixels it moved. In the event of a partial move, Bump() is called for any obstacles but Move() will still report success.
What about high-speed movement? If a mover has a speed much bigger than its own size, will it skip past obstacles? Nope, we covered that too, by breaking up the movement into smaller steps internally. Let your bullets fly!
Client pixel offsets are handled intelligently here too. You won't need to do any special processing. The pixel_x/y/z vars for the client still work for purely visual offsets, but the view will follow your mob without any trouble.
Pixel movement is obviously incompatible with gliding, the method we have used for a long time to show smooth transitions from one tile to another, so gliding is now deprecated. It works fine for older projects and will still work for tile-based projects for the foreseeable future, but in the case of pixel movement it isn't used. (Gliding will act only on atoms that move in full-tile increments, don't have step_x/y positions, and have bounds that are a multiple of the tile size.) However, while in motion a pixel mover will use its moving icon state, if any.
On a related subject, icon animations have been changed so that the delay is now expressed in 1/10s instead of in ticks, allowing existing projects to upgrade to a version with a lower world.tick_lag with a little less trouble. And instead of having to calculate tick_lag, you can use the new world.fps var instead which is a little more convenient.
Pathfinding has been covered too. Pixel movers have access to pathfinding using the A* algorithm. (High-speed movers aren't handled here. At least for the moment, you're on your own with those. This is considered low-priority because high-speed movement will mostly be confined to projectiles, I expect.)
With all this going on we haven't forgotten about the map editor, and not for lack of trying. I've been working on some changes to allow step placement of pixel-movers, so you should be able to make some more organic maps than you can presently. As with the isometric expansion in version 455, the map editor has proven to be one of the most intransigent parts of the code to change. The old Alt+click behavior that sets the active object has been replaced with Ctrl+Shift+click, because the Alt key has new behavior. When you use Add mode, an Alt+click will place an object (it has to be movable) at the pixel position you choose instead of placing it in the default position on the tile. In select mode, Alt+drag will grab the object and move it around by pixels. The Nudge option has been changed to work on step_x/y vs. pixel_x/y/z, which also means the z offset nudge has been removed and that nudging is no longer done for turfs or areas.
All these changes have of course required a lot of testing, but the new version is finally available for beta testing. It's been a lot of work, but the impact on game quality should be huge. Start your new creations and upgrades now!
Don't forget to check out the pixelicious demo!
New and changed vars and procs
- world/fps = 10
- Shortcut for 10 / world.tick_lag, for easily setting frames per second in your project.
- atom/movable/step_size = 32
- Speed of atom's movement. The actual default is the width from world.icon_size.
- atom/movable/step_x = 0
atom/movable/step_y = 0
- Offset in pixels from default position on tile.
- atom/movable/bound_x = 0
atom/movable/bound_y = 0
atom/movable/bound_width = 32
atom/movable/bound_height = 32
- Bounding box of atom; a whole tile by default. x,y are how far into the tile the bounding box actually begins. Actual defaults of width and height depend on world.icon_size.
- List of turfs covered by the atom's bounding box. src is in the contents of each of those turfs.
- If a movable atom's bounding box covers the turf, that atom is in the turf's contents list.
- bounds() and obounds()
- Returns all atoms within a box +dist pixels around the reference atom in every direction. This is equivalent to bounds(ref,-dist,-dist,dist*2,dist*2) using the format below.
- Uses the ref's bounding box, offset by x and y and with additional width and height.
- Uses an arbitrary bounding box, where x,y coords start at 1,1 for the lower left corner of the map.
- Distance in pixels between two objects. Negative if the objects overlap.
- step and walk procs
If speed=0, then the atom's own step_size is used instead. These procs still use only the regular 8 directions to move.
- Move() will either "slide" or "jump". A slide is any turf-to-turf move on the same z level where the distance in pixels is up to max(step_size,world.icon_size). Slides can bump into obstacles but still count as successful.
The step_x/y arguments represent the target values. The actual loc and step_x/y may be changed if the movement succeeds only partially or the values of step_x/y are too big or too small.
When sliding, Move() returns the number of pixels moved.
- turf/Enter(atom/movable/O, atom/oldloc)
- Now Enter() ignores anything in the turf's contents that doesn't cover the entire tile. For any contents covering the whole tile, Cross() is called. Returns 0 if the turf and O are both dense, or if any of the Cross() calls return 0. The oldloc argument is new, added only for convenience.
- Called if O is trying to overlap src. Returns 1 if neither object is dense, or if O is a mob in src's group. Otherwise returns 0.
(Exception: If src and O both move in whole-tile steps as in legacy projects, group is not considered. Bump() will swap the mobs instead if it can.)
- Called if O is trying to stop overlapping src. Returns 1.
- Called by Move() when O has overlapped src.
- Called by Move() when O has stopped overlapping src.
- Called if obstacle was bumped during Move(), even if movement partially succeeded. If src and obstacle are both mobs and neither uses pixel movement, Bump() will swap them if src is in obstacle.group. If either one uses pixel movement, nothing happens regardless of group.