craftyjs / Crafty

JavaScript Game Engine
http://craftyjs.com
MIT License
3.42k stars 557 forks source link

Request: Allow for offsetting 2D elements drawing positions relative to their actual positions #1020

Closed dekdevy closed 2 years ago

dekdevy commented 8 years ago

Hey, I've been looking for a way to modify where sprites are drawn relative to their position, similar to the .origin() feature allowing to change where they are rotated. At first I thought that .shift() is the solution, but It seems that shift doesnt actually shift any internal values, but actually "hard" shifts the position. This feature might not seem like an absolute must if you design your game around the engines way of doing things, but it could for sure be a useful feature for a lot of devs who are used to this. Many game developers are used to having an entities position in the center, as this often works best with how games logic is programmed. Simple example: Have a top down game where you control a spaceship. When you shoot, you want to spawn the projectiles at the center of the ship. Currently this would mean you have to compute the center yourself by taking the ships size as a vector and halve it, add it to the original position.. etc.

My proposal for this would be simple; Add a function to the 2D element which takes similar inputs as .origin(). Allow for input such as "center", "top left" etc, for easy use - or alternatively allow for input as fixed values.

200sc commented 8 years ago

I think it's really really impractical to actually change what ._x and ._y give you while having the drawn position stay the same, as it's also accessing those same values. Why not just have a ._centerX and ._centerY?

Also, as someone who has written a game which uses a server in CraftyJS, no, we didn't store positions from their center because we didn't want to have to change positions back from center if they were accessed by a client. It honestly didn't even come to mind.

Can you point out what other engines do positioning this way? Any javascript engines? I'm specifically interested in javascript because I believe part of why Crafty would be using top left positioning would be based on dom element positioning.

I think the codebase would get very confusing if you had the accessor methods ._x and ._y, which I guess would be functions which were replaced with other functions whenever you called this .setPos-esque function while also having some kind of ._origX and ._origY which are only used internally by Crafty.

I think it would make your code harder to read, because you'd need to remind anyone reading your code "By the way I centered x and y so these functions don't return what you expect anymore" and I think it would introduce a lot of bugs related to grabbing x and y and not getting what you'd expect.

Rather than redefining x and y accessors, which causes all of these concerns, I'd either go with the centerX and centerY situation, which would just add half the height and width anyway and wouldn't be very good for performance, or I'd add a ._xOffset(offset) function with optional string arguments for 'center' and such, if there was a thought that the variable nature of this accessor was very important.

A compromise could be ._myX which would only be affected by your .myOffset(xOffset,yOffset) call?

dekdevy commented 8 years ago

@Sythe2o0 Well this seems to have sparked quite the fundamental discussion, so allow me to explain where im coming from. You have a good point that introducing such a feature might be a cause for confusion.
So let me explain why storing entity positions at their center is a practice that can be beneficial. The whole idea stems from the fact that coupling game visuals to game logic isnt very practical. Lets say you have a game with monsters. Monsters of different sizes. The monsters can drop items. These items can have a lot of different sizes. They can drop huge swords and really really small gold coins.
Now, if you want to drop items at the monsters positions, how do you do this? The most obvious, performant, and uncluttered approach would be to drop the item at the monster position: item.x = monster.x; this is only possible if the entities positions are all centered by default, right? If this doesnt seem intuitive to you, how do you handle it? item.x = monster.x + (monster.w*0.5 - item.w*0.5); ?

To answer your question regarding which engines do this: Pretty much all of them. Unity , Unreal etc.

It all boils down to the fact that in most game engines, entity position and entity visuals are mostly something completely unrelated. (this is me talking from my experience with the indie / AAA industry, im new to web based development but It should translate to any kind of game dev) In unity for example you add a 2d component to the entitiy. the 2d component has the entity as a logical anchor and moves relative to the entity position, which allows to shift and offset the entity visuals however you please, without affecting the logics of the game (this is really important for a lot of stuff.. lets say you want to squash a ball in a tennis game whenever you hit it. you would do this by changing the scale of the ball, and not touch the actual position at all. The scale would be happening relative to the center so it would make sense, visually. If the balls position was stored at its upper left corner, you would have to temporarily shift its position relative to the scaling in order to make it look correct - except you wouldnt be able to do that without fiddling deep inside the balls movement code and that would just be horrible because you are now coupling visuals to game logic. )

I realized that this is a pretty fundamental thing and its probably not gonna be possible to implement just like that, because in craftyjs it seems that an entities position is owned by the 2d component rather than some kind of root entity component if I understand correctly.

edit: I didnt see your edits regarding the engines, sorry. I cant talk much about javascript engines - just dropping my thoughts here. The dom part makes sense.

200sc commented 8 years ago

No, if I need to access an entity's center at all regularly I define a centerX,Y value for it. I understand a constant center reference is valuable.

Both Unreal and Unity are 3d Engines. I've worked with Pygame and Game maker as 2D engines, and positions haven't ever been centered as far as I can recall. Phaser, the only big javascript engine off the top of my head, also uses top-left as default, although they do offer an 'anchor' which is equivalent to your suggestion.

My concern with modifying _x and _y is exactly what you mention at the end, it's rather fundamental and there's probably at least a hundred places in the code base that would need to be modified to adapt to the change. On the other hand, having something else exist as a reference to a variable offset seems perfectly reasonable to implement very quickly.

mucaho commented 8 years ago

The display transforms are currently the same as the logical anchor's transform. Like you guys have figured out, you could have the logical anchor as the center in a local coordinate system, where collision bounding rectangles and all display stuff is actually attached to it by using offsets from the center. The transforms (translation, rotation, scale) of the logical anchor then have to be applied to the transforms of display and collision stuff to get the final transforms in the world coordinate system for collision detection system and drawing layers. (Note the performance impact).

Logic separation of components and systems is an ongoing process (#578). I'm definitely for exploring this idea of properly separating the logical anchor from display & collision, if it's still straightforward to use and performant enough.

You could try to simulate it by yourself for the time being with the help of the attach method. Have an invisible parent entity that serves as the anchor, then attach a display entity as a child (and possibly multiple collision entities as children). Then it should be "just" a matter of routing the events between those entities (mostly from children to parent entity I think).

EDIT: Some ideas if you're interested So something like this would be very clean for logic. The question is how many times do you need multiple & independent display components and collision hitboxes? It doesn't look that nice api-wise (there are surely better alternatives though), plus you get the aforementioned performance impact for calculating from local coordinate system to world coordinate system. Now, if there was a way to transform existing API design to build this implicitly in the background, that would be interesting. For simple uses, you could still do it "the old way", for advanced uses you have some more options.

Crafty.e("2D, Fourway")
      .attr({ translationX: 100, translationY: 100 }) // world position
      .fourway()
      .bind('BulletHit', function() {
         this._children['Body'].color('blue') ;
      })
      .attach("Body",
         Crafty.e("Renderable, Color")
               .attr({ x: -25, y: -15, w: 50, h: 50 }) // offsets from local center
               .color('green');
       )
      .attach("Hat",
         Crafty.e("Renderable, Sprite, hatSprite")
               .attr({ x: -10, y: -25, w: 10, h: 10 });
       )
      .attach("FeetHitbox",
         Crafty.e("Collision")
               .collision([-25, 35,  25, 35,  25, 45,  -25, 45])
               .onHit("Bullet", function() {
                   this._parent.trigger('BulletHit');
               });
       )
      .attach("WeaponHitbox",
         Crafty.e("Collision")...
       )
mucaho commented 8 years ago

I have been thinking about this for quite some time now and have managed to come up with a proposal. If you're interested keep on reading, I'd be happy about any comments that anyone has about this proposal (also cc @starwed )


It'd be a pretty fundamental change, so I have considered the ideas as noted in the comments by dekdevy and Sythe2o0.

Any change that is introduced should minimize the impact on:


Here's what I propose:


What are the implications of implementing this proposal?


Last update to this post: 10:18 PM Thursday, March 24, 2016 Coordinated Universal Time (UTC)

dekdevy commented 8 years ago

I like your proposal and I am super thankful for all the thoughts that you put into this. I dont have much to add. It seems to address all the problems that I had! Just curious exactly how you would do this internally. One tiny thing to keep in mind is that if you change the width of the Renderable or the Sprite itself, that this would also have to update the internal offset values automatically. Lets say you set .origin('center) and then change the sprite of a different size, the origin would be wrong if its not using some kind of dynamic calculation internally.
Also, great point with Box2D. If I remember correctly, by default Box2D will also use center-based origins when creating rectangle or sphere fixtures.

mucaho commented 8 years ago

Lets say you set .origin('center') and then change the sprite of a different size, the origin would be wrong if its not using some kind of dynamic calculation internally.

Good observation! Hmm, I wonder if origin offsets should be specified as relative coordinates in addition (or instead of) absolute coordinates, ranging from 0.0 to 1.0 like UV coordinates. A value of 0.5 would center the sprite, no matter its dimensions. That complicates things for custom polygon hitboxes though, as these coordinates would be relative to the polygon's minimum bounding rectangle.

JaredSartin commented 7 years ago

Necro-ing, so graphics drawing will always be top-left and origin is ONLY for rotation?

starwed commented 7 years ago

Necro-ing, so graphics drawing will always be top-left and origin is ONLY for rotation?

I'd rather say that the rectangle will always be the base primitive crafty uses for it's built in components. Rendering to Canvas, DOM, and WebGL all ultimately require knowing the top-left position and the width/height, so making the underlying properties anything else would probably cause a performance hit.

That doesn't mean we can't add convenience methods that make it easier to work in other idioms. The simplest approach would be to define a "Centered" component that provided new setter/getters for the underlying _x and _y properties in terms of the center of the entity.

e.g. setters for x position and width could take into account the origin.

This has come up often enough that I'll take a stab at implementing it after the 0.8 release. (Though if anyone else is interested in working on that, go ahead!)

starwed commented 7 years ago

Opened PR #1089 that provides some related functionality.