Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposed API for the DiamondIso component #917

Open
airza opened this issue May 29, 2015 · 19 comments
Open

Proposed API for the DiamondIso component #917

airza opened this issue May 29, 2015 · 19 comments

Comments

@airza
Copy link
Contributor

airza commented May 29, 2015

I'm adding more functionality and would like to propose a public API for this to make sure I'm not missing anything or doing it inefficiently. This will also help the docs, and make sure I'm writing the correct unit tests.

init: function (tw, th, mw, mh, x, y)
Creates the grid. Tile width and height are specified by the first two parameters. The map's width and height are specified by the second two, with the X axis being down-right and Y axis being down-left. The X and Y coordinate indicate the map's placement on the canvas, such that the top of the tile at (0,0,0) on the grid has its top corner here. Right now

place: function(obj, x, y, layer)
Puts an object (obj) into the grid at X Y and grid height layer. If the object's height or width isn't a multiple of the grid height this probably won't work. There's no need for a sprite or whatever to actually fill the entire grid, but its actual crafty size has to fit correctly in. We should probably have a switch that will determine if the grid updates that object's realX/realY position as well. Destroying an object also removes it from this grid.

It's not clear to me from a design perspective if tiles should have their current grid location passed back to them by this function- is it a more maintainable architecture if all of the code that can tell objects about the relative location of other objects lives inside of the grid object itsself? I really have no idea.

detachTile: function(obj)
Removes an object from the grid's storage and returns it to the user.

centerAt:(x, y)
This function currently centers the viewpoint on a given tile (Supposedly). Since sometimes one probably wants units to be centered on a given tile this one makes sense to keep in (I don't think the centering is exactly perfect though, i should fix that.

getZAtLoc:(x,y,layer):
Used both internally and externally- the map sets up certain Z values so that tiles render correctly on top of one another. This may be possible to make private, but right now entities that walk on top of the map use it to get Z values for where they should be walking.

getOverlappingTiles: I don't remember why I wrote this method. LOL. It currently loops through the entire grid to figure out all of the entities which currently are under one another on the board, which is probably useful if you have drag and drop functionality. It seems like Crafty.findClosestEntityByComponent is a much better choice for anything you could use a grid for, so I'm going to remove this.

polygon: This looks like it's trying to do something with the bounding box of elements, but it's not correct and it's not clear why the grid should handle drawing the areamap of a tile. I'm going to delete it.

getTile(x,y,z): Gets the tile at x,y, and layer Z. Pretty straightforward. Since we may want to turn this into a sparse representation internally someday this is going to be a getter.

getTileDimensions(): Returns the tile height and width (Which right now have to be the same)

findPath(Start, End):
This i've already implemented - when given a start/end X and Y coordinate, it will look at things on the grid with the component "Obstacle" and find a way around it. It would be nice to work with the collision library on this somehow, but collision on the grid and on a 2d screen work two fundamentally different ways. This only currently works for things on Z level 1 that are walking around on top of Z level 0. It returns a list of squares on the grid that the entity should walk through.

Trivially easy upgrades for this would include: A parameter to specify additional components that represent obstacles, and a parameter to allow diagonal movement. (Right now the advantage of having the "obstacle" component is that the grid can store the collision map and then call it up when an entity walks around.) Movement fails for squares on the grid with an obstacle at level 1 or that are empty at level 0.

moveEntity(startCoords/object, destination,speed): Moves an entity on the map using the most efficient route available. Always walks through the middle of each tile (it's not difficult to change this or allow a specified path.) Currently it updates the entity's placement in the grid when it reaches the center of the square, but probably it should update as soon as the entity moves in. It's also a method attached to its own component since i haven't gotten brave enough to try adding new components to the library.

pos2px(left,top,layer): This returns the x/y coordinate of the tile at this location (IE, where the x and y of a tile would have to be placed in order to be correctly placed at this X Y Z.) Layer isn't implemented yet, but is a simple addition of tile height to the Y coordinate.

px2Pos(x,y): This returns which tile on layer zero the specified coordinates are on. This could theoretically be updated to specify different layers, but right now I don't see a super obvious reason to do so.

The remaining functionality that i think needs to be added:
rotate(Clockwise/Counterclockwise): Rotates the grid left or right, in place, so that everything is in every other position. Triggers "GridRotate" on every single entity in the grid, passing a translation function that correctly translates realX/realY/z coordinates. (I think this is a simple linear transform, and hopefully not many entities on the grid need to bind to this.) The X/Y coordinates of the grid probably shouldn't rotate unless there is a compelling reason for them to do so. We COULD make every single function which interfaces between pixels and grid locations dynamically change based on rotation, but this would be a hellscape.

The current entities which I am developing/would like to develop in conjunction with this proposed functionality:

"Obstacle": It counts as something you can't walk through (No code needed for this, it just helps performance for pathfinding if the grid collision map isn't dynamically updated for every single pathfinding call.)

"PathFollower": Currently has one function:
followWaypoints(Array[Array] waypoints,speed): It follows a list of realx/realy coordinates at the given speed using the tween component. Calls "Waypoint" at each one and "WaypointsDone" at the end. Needed for grid movement! Also handy for objects that want to move in set paths on the map, though.

Does anyone have any questions or comments about this proposed API? It will come with [drumroll] updated documentation for the diamondIso component, and should allow us to remove the regular iso (I'm not clear if anyone is using the other one and its component system is kind of unintuitive.)

@airza
Copy link
Contributor Author

airza commented May 29, 2015

Oh, I just forgot that every entity will pretty much need to bind to grid rotate because entities will have different sprites based on rotations. Womp womp.

@airza
Copy link
Contributor Author

airza commented May 29, 2015

So, maybe it's a better idea to have a specified GridEntity component that will know its own position, and have a function available to replace its sprite when rotation happens.

@starwed
Copy link
Member

starwed commented May 30, 2015

I'll try to take a detailed look at this later, but one approach would be to make everything much more general. Maybe things like "GridEntity" should be abstracted out, and then you can specify a method for translating grid coordinates to screen coordinates. That way you could use the same methods regardless of whether you were making an isometric or a top down game.

I guess a good test of an isometric engine's flexibility would be if you could implement something crazy like naya's quest. :)

@airza
Copy link
Contributor Author

airza commented Jun 15, 2015

Well, I finished everything except rotation, which is a horrendous pain in the ass. It might be a good idea to standardize the input and output point objects, but I don't remember if it breaks backwards compatibility to do so.

@starwed
Copy link
Member

starwed commented Jun 15, 2015

If you want some specific feedback, feel free to open a PR with what you've got -- no worries if you plan on doing more work or cleaning it up, you can just update the PR as you do so.

@mucaho mucaho mentioned this issue Jul 6, 2015
3 tasks
@mucaho
Copy link
Contributor

mucaho commented Jul 17, 2015

I think isometric is a good motivating example to introduce the concept of cameras.

  • viewport is already an orthographic camera looking towards - z, thus its look direction is (0, 0, -1).
  • we can add look direction vector and the z position coordinate explicitly to the viewport
  • that would make our lives 100x easier, as we do not have to juggle between real x,y coordinates and the isometric x,y coordinates
    • the entities retain their x,y coordinates
    • isometric tiles gain an additional rotate3d(45, 45, 45) style property
  • that means both "normal" sprites and "isometric" sprites can be drawn together correctly (normal sprites can be drawn in isometric perspective; inverse not true)
    • normal drawing means camera looks towards (0, 0, -1), position (x, y, +∞)
    • isometric drawing means camera looks towards (-1, -1, -1), position (+- x, +- y, +- z)
    • camera panning occurs alongside another plane in isometric mode
  • separate layers would probably increase performance
    • e.g. one parent div for isometric entities with rotate3d(45, 45, 45) style property
  • care has to be taken to support isometric only on newer browsers and use only 2d transforms on older browser

This can be done before / parallel to #712, mpetrovic's layer branch also contains a camera implementation.
In my opinion that should be our main focus until next release (post 0.7).
I will try and make a small demo to see if it's doable.

@airza
Copy link
Contributor Author

airza commented Jul 20, 2015

I dropped a PR targeting the previous implementation. A camera would be nice to have but the assumption that all isometric sprites can be freely rotated (or even viewed from a skewed azimuth like an iso camera would use) and still look correct seems wrong.

@mucaho
Copy link
Contributor

mucaho commented Jul 21, 2015

the assumption that all isometric sprites can be freely rotated (or even viewed from a skewed azimuth like an iso camera would use) and still look correct seems wrong

Yes, and the more I think about it, the less appealing orthographic cameras with 3d position and rotation seem to me. Possible performance impact and browser compatibility limitations just for isometric perspective.

My major motivation behind isometric camera is to be able to run game logic on a node back-end and feed back position updates to a client front-end and have everything correctly displayed. No conversions needed, existing components (like Fourway) work out-of-the-box.

I have been toying around with an alternative idea lately: You have 2 sets of entities; one set of hidden entities driving the game logic, which feed their constantly changing state (like position) to the set of isometric entities. This way we should be able leverage the work & effort that has been put into the current implementation, while also being compatible to existing functionality (like Fourway).

Playing around with current isometric implementation, I have encountered an issue. @airza Would you kindly try to run this example on your current implementation? Do you also notice the stone box making sudden, periodic jumps?

@airza
Copy link
Contributor Author

airza commented Jul 21, 2015

Hmm... That's not good. Let me take a look at it while I think about the idea that you're suggesting. :)

@airza
Copy link
Contributor Author

airza commented Jul 21, 2015

Ah, I see:

The isometric implementation uses a different addressing system for tiles on the grid than diamond iso. It tries to build the px2pos against a grid where the tiles on the same X axis are placed in a zig-zagging left-to-right pattern. The author of this implementation attempts to distinguish between these by using a bitwise AND:

left: x * this._tile.width + (y & 1) * (this._tile.width / 2)

When the float jumps from under 3 to over 3, the y&1 suddenly snaps from 0 to 1 which causes it to jump quite a bit- The author of that didn't anticipate that people would try to use it to interpolate values between two tiles. I can see how you'd fix it (calculate the position of the tile on the left and right of it and then draw a vector between, but the difficulty of that coordinate system vs diamondIso is why I chose to update diamondiso in the first place, so it's a different issue than this one.

@mucaho
Copy link
Contributor

mucaho commented Jul 21, 2015

So it's not an issue at all, that's working as intended according to the zig-zag isometric ordering. After I switched to diamond ordering the box moved in a straight line as expected, thanks for taking a look at it.

@mucaho
Copy link
Contributor

mucaho commented Jul 21, 2015

Yes, it may be feasable
Has:

  • concept of depth in addition to z
  • z based / simple 3D collision
  • right angle rotations
  • keyboard movement
  • drag & drop of tiles
  • sprite/tile stacking
  • "arbitrarily" scaled sprites can be used in conjunction with each other
  • transformation from a generic 3D grid to a isometric tile map
  • viewport related utilities (like Crafty.viewport.follow)

Misses:

  • automatic splitting of tall sprites into multiple iso tile entities
  • avoid broadphase map pollution by both entity sets -> grow one set of entities in one direction of map, grow the other in another direction

@airza
Copy link
Contributor Author

airza commented Jul 21, 2015

Yeah, this is pretty similar to what I have in place for the game i've been
working on using this library. Though for me it was only important to
figure out when characters cross a boundary since all of the movement is
mouse-bound.

If we want an entity which is designed to move across the surface of isos
it could be done with the built in collision map from the PR. The Z layer
stuff is fixed on that one too.

On Tue, Jul 21, 2015 at 1:24 PM, mucaho notifications@github.com wrote:

Yes, it may be feasable https://jsfiddle.net/gt9o6469/ - lacks correct
depth ordering, resizing and rotation.


Reply to this email directly or view it on GitHub
#917 (comment).

@mucaho
Copy link
Contributor

mucaho commented Jul 21, 2015

This collision map sounds interesting. Is it functionally equivalent to the collision map you would have from bird/top-down perspective? Or does it recognize that the player can't walk on tiles that are hidden behind a tall object? e.g. In Secret of Mana you can't walk behind a house because it would obstruct your vision - this is maybe a tad bad example because it's not isometric :)
Secret of Mana house isometric

@airza
Copy link
Contributor Author

airza commented Jul 21, 2015

Yeah, it's the latter type - when you place or remove a tile from the grid on the 1 or 0 z layer it checks to see if 1)the tile on the 0 z layer is in place (IE, there's a floor) and 2) there's no tile with the "Obstacle" component on the z layer 1.

I don't have any linkups in place with the existing collision map since I did things with the mouse - you click on the tile and then it uses the pathfinding at the grid level to see which boxes are non-crossible If I was trying to leverage the existing collision functionality for this, it seems like having a collision box projected onto the surface of the board for solid tiles would be the way to go. Then entities walking could have a subcomponent where their feet were that invisibly checked collision.

@mucaho
Copy link
Contributor

mucaho commented Jul 22, 2015

Ok, let me make sure I understood everything correctly. Isometric, as it currently is implemented in the linked PR, includes:

  • A data structure that manages placement (and thus position) of entities inside an isometric grid. You can add/remove entities from this data structure.
  • Collision bounds and pathfinding for entities inside this isometric grid
  • Utility methods to convert a 3D point from birds-eye view to isometric view and vice versa
  • Isometric camera utilities like centerAt

What I would ideally prefer to see eventually:

  • Data structure that manages placement of entities in 3D grid - could be provided as an external component. Mapping from grid coordinates to spatial coordinates happens in external, unrelated code (see also starwed's comment).
  • Collision bounds and pathfinding implemented on top of this generic grid - could be provided as an external component. Pathfinding has thus nothing to do with how the entities are displayed.
  • Only isometric-specific utilities provided by a "Isometric" component / Crafty property (like centerAt, etc...)
  • As already discussed, leveraging as much of existing functionality for entities with isometric-display as possible.

How I propose we proceed:

  • I would like to see (a possibly minimal) example where the code in the PR is used. If everything checks out, merge it.
  • Gradually work towards "ideal" state.
    There are certainly better approaches, but the approach I discussed above is a good first step I hope. You can see some proposed implementations in my WIP example.

Thoughts? This is probably a big design decision, so I hope other people voice their opinion also. @starwed @kevinsimper (sry for all the cc's lately)

@airza
Copy link
Contributor Author

airza commented Jul 24, 2015

It has collision boundaries at the discrete level- you can ask if it if a particular X,Y coordinate is enterable by an entity travelling on top of the grid (@ z level 1).

I haven't tested centerAt much, but I'll go doublecheck it right now. TBH i'm surprised that it's a method of the grid and there isn't just a Crafty.Viewport.Centeron(o) which calculates the center of the object passed and the viewport and aligns accordingly.

"Utility methods to convert a 3D point from birds-eye view to isometric view and vice versa"- yeah, it does the vectorization conversion automatically.

If you want to take a look at what i'm using it for (which showcases some of the nice features in a way that the base library doesn't have right now) you can look here:

https://aqueous-fjord-6193.herokuapp.com/Crafty

You can click around and the character will route intelligently. If you so desire, you can use the bright green button to switch to a mode where the obstacles can be dragged around- then she will correctly route around them in the most effective way possible.

The actual walking code is custom and basically just maps the array of coordinates that it receives from the pathfinding.

@airza
Copy link
Contributor Author

airza commented Jul 24, 2015

The intended behavior of centerAt is to center the camera on the grid, right?

@mucaho
Copy link
Contributor

mucaho commented Jul 24, 2015

Nice looking game you got there!

The intended behavior of centerAt is to center the camera on the grid, right?

Yes and as you said all viewport related functions should work out of the box with isometric entities. I guess this isometric-specific centerAt also considers tile.width in order to properly center the entity on screen. I wonder if there is a noticeable difference between iso centerAt and Crafty.viewport.centerAt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants