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

FlxSpriteGroup: setting origin should cause members to pivot around the same point #2981

Merged
merged 2 commits into from
Dec 15, 2023

Conversation

47rooks
Copy link
Contributor

@47rooks 47rooks commented Dec 12, 2023

Currently, rotation and scaling on a FlxSpriteGroup produces strange results. The problem stems from the fact that the originTransform function does not set all the member origin fields to point to the same place - to do this the origin of each member must be set relative to its own position. The current originTransform sets all the origins to the same value. This change fixes this computing the correct relative origin for each child. A unit test is added also. I have also tested this with an application which demonstrates that the rotation and scaling work as expected.

@MondayHopscotch
Copy link
Contributor

Definitely been bitten by this before (one of the many reasons I've historically avoided FlxSpriteGroup). I was doing a jam at the time and never got back around to looking into it. Thanks for digging into and fixing this!

@@ -1057,7 +1057,7 @@ class FlxTypedSpriteGroup<T:FlxSprite> extends FlxSprite
Sprite.offset.copyFrom(Offset);

inline function originTransform(Sprite:FlxSprite, Origin:FlxPoint)
Sprite.origin.copyFrom(Origin);
Sprite.origin.set(x + origin.x - Sprite.x, y + origin.y - Sprite.y);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this behave "intuitively" if the entire group is moved? I'm not in a spot to test it myself, but it feels like there still may be weirdness if you set origin -> move group -> scale.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I modified my test program - a little robot holding a brick - the robot and the brick are separate sprites in the same group. I can scale the group, drive it about, rotate and scale up and down and move again. All looks ok.

@Geokureli
Copy link
Member

Geokureli commented Dec 12, 2023

can I get a little test case that highlights the before and after behavior? I understand there's a unit test, but I would like something more practical

@47rooks
Copy link
Contributor Author

47rooks commented Dec 12, 2023

Sure. Do you mean as part of this change (if so where in the repo would i put that ?) or just a link to a test repo of my own ?

@47rooks
Copy link
Contributor Author

47rooks commented Dec 13, 2023

Here is a simple playstate that demonstrates the behaviour. Compile it with an existing flixel and one with my change and you can see how the rotation and scaling work. Keys - A - rotate counterclockwise, D - rotate clockwise, H - scale by 3.0, J scale to 0.5. If you need something more complex let me know.

package;

import flixel.FlxG;
import flixel.FlxSprite;
import flixel.FlxState;
import flixel.group.FlxSpriteGroup;
import flixel.util.FlxColor;

class PlayStateOriginDemo extends FlxState
{
	var _repairBot:FlxSpriteGroup;

	override public function create()
	{
		super.create();
		FlxG.camera.bgColor = 0xff008080;

		// Repair Bot
		repairBot();
	}

	function repairBot():Void
	{
		_repairBot = new FlxSpriteGroup(FlxG.width / 2.0, FlxG.height / 2.0);
		_repairBot.screenCenter();
		add(_repairBot);

		var bot = new FlxSprite();
		bot.makeGraphic(100, 100);

		var block1 = new FlxSprite(90, 90);
		block1.makeGraphic(20, 20, FlxColor.RED);

		_repairBot.add(bot);
		_repairBot.add(block1);

		add(_repairBot);

		_repairBot.origin.set(50, 50);
	}

	override public function update(elapsed:Float)
	{
		super.update(elapsed);

		// Repair bot movement
		if (FlxG.keys.pressed.A)
		{
			_repairBot.angle -= 10;
		}
		else if (FlxG.keys.pressed.D)
		{
			_repairBot.angle += 10;
		}
		else if (FlxG.keys.justReleased.H)
		{
			_repairBot.scale.set(3.0, 3.0);
		}
		else if (FlxG.keys.justReleased.J)
		{
			_repairBot.scale.set(0.5, 0.5);
		}
	}
}

@Geokureli
Copy link
Member

I'm wondering if this is considered a breaking change, existing projects may be expecting the old behavior, even though it's not desirable. we may need to wait until flixel 6.0.0

@Geokureli Geokureli added this to the 6.0.0 milestone Dec 13, 2023
@47rooks
Copy link
Contributor Author

47rooks commented Dec 13, 2023

Yeah I wondered the same thing. I wondered if this behavior should be governed by a flag maybe with the default being the existing behavior. Maybe a flag called say localOrigins set to true by default.

@Geokureli
Copy link
Member

Geokureli commented Dec 13, 2023

I would also like to point out, in your example, if block1 moves after the initial setup, it will no longer rotate and scale around the shared origin.

Screen.Recording.2023-12-13.at.12.28.45.PM.mov

I don't think i would want FlxSpriteGroup to account for this, but I do think that rotating groups in flixel is just gonna be wonky

@47rooks
Copy link
Contributor Author

47rooks commented Dec 13, 2023

Yeah any per component update like that would require resetting the origin. Bit like hit box updates I guess. So maybe some doc update is also required

@MondayHopscotch
Copy link
Contributor

I would also like to point out, in your example, if block1 moves after the initial setup, it will no longer rotate and scale around the shared origin.

Screen.Recording.2023-12-13.at.12.28.45.PM.mov
I don't think i would want FlxSpriteGroup to account for this, but I do think that rotating groups in flixel is just gonna be wonky

This definitely drifts into somewhat weird territory. You'd have to know to update the origin after moving individual elements within a FlxSpriteGroup to keep them sync'd.

What is the "intended use" of a FlxSpriteGroup here? Set it up and don't touch the individual components inside the group? (No way to force that, I know)

@Geokureli
Copy link
Member

Yeah I wondered the same thing. I wondered if this behavior should be governed by a flag maybe with the default being the existing behavior. Maybe a flag called say localOrigins set to true by default.

I'm trying to think of cases where people would want the old behavior, and I'm struggling to see a valid case. My thought process is: if you're setting the group's origin, you likely want all the objects to pivot around that point, if you wanted them to all have the same origin, they would all need to be the same size and you could just loop through and set them all. A bool would make this no longer a breaking change which is good in the short term, but them we have this potentially useless bool hanging around forever. I think I would rather make the breaking change on a major version when we release a "Flixel 6.0.0 Migration Guide".

Until then, if this is something you need in your project sooner, you can extend it and override the origin setter

So maybe some doc update is also required
yeah

@47rooks
Copy link
Contributor Author

47rooks commented Dec 13, 2023

Ok the only use for the existing behavior that I could think of was as a group of similar objects that you wanted to spin or scale. Maybe a group of pinwheels spinning for example. But they'd have to be the same size and there are other ways you could do it.

Not terribly likely. But I hear you about the useless flag forever.

@47rooks
Copy link
Contributor Author

47rooks commented Dec 13, 2023

Until then, if this is something you need in your project sooner, you can extend it and override the origin setter

Extension doesn't work unless you use haxe 5.0. There is a bug which leads to a type mismatch. At least I couldn't figure out how.

@Geokureli
Copy link
Member

Geokureli commented Dec 13, 2023

Extension doesn't work unless you use haxe 5.0. There is a bug which leads to a type mismatch. At least I couldn't figure out how.

thanks for reminding me i was gonna try this: HaxeFlixel/flixel-ui#261

@47rooks
Copy link
Contributor Author

47rooks commented Dec 13, 2023

thanks for reminding me i was gonna try this: HaxeFlixel/flixel-ui#261

Oh... cool yeah something like that might work.

@Geokureli Geokureli changed the base branch from dev to release6 December 13, 2023 19:58
@Geokureli
Copy link
Member

Merging to branch release6 for the upcoming flixel 6.0.0 alpha, thanks!

@Geokureli Geokureli merged commit 58bfba6 into HaxeFlixel:release6 Dec 15, 2023
15 checks passed
@Geokureli Geokureli changed the title Fix FlxSpriteGroup origin FlxSpriteGroup: setting origin should cause members to pivot around the same point Dec 15, 2023
Geokureli added a commit that referenced this pull request Dec 15, 2023
Geokureli added a commit that referenced this pull request Jan 31, 2025
* Fix FlxSpriteGroup origin (#2981)

* update children origins relative to their positions

* add originTransform unit test

* Better framerate independant camera lerping (#2922)

* adjust lerp based on the current frame rate

* better framerate independant camera lerping

* fix error

* remove setter

* docs

* fix default lerp in follow()

* fix typo

* add #2981 and #2922

* Flxstate new (#2733)

* take FlxState constructor instead of FlxState

* add NextState type

* 4.0.5 fixes

* more 4.0.5 fixes

* fix deprecation warnings in unit tests

* take nextState in VCRFrontEnd

* remove @from Instance in InitialState

* use is operator

* improve docs

* rename method

* more docs

* more modular FlxSplash, set _gameJustStarted in FlxGame

* allow splash to be skipped by a switchState call

* touch-ups for #2733 (#3003)

* take FlxState constructor instead of FlxState

* add NextState type

* 4.0.5 fixes

* more 4.0.5 fixes

* fix deprecation warnings in unit tests

* take nextState in VCRFrontEnd

* remove @from Instance in InitialState

* use is operator

* improve docs

* rename method

* more docs

* more modular FlxSplash, set _gameJustStarted in FlxGame

* allow splash to be skipped by a switchState call

* touch-ups

* add #2733

* Remove deprecated features (#3048)

* remove deprecated features

* more removals

* Update FlxCamera.hx

* remove defaultCameras

* Default zoom (#2907)

* add bindScrollPos

* improve SCREEN_BY_SCREEN follow mode

* make cameras suck less when using FlxCamera.defaultZoom <> 1.0

* Replace references to FlxG.camera with this.getDefaultCamera (#3072)

* add getDefaultCamera, stop using FlxG.camera for everything

* more uses of getDefaultCamera

* fix flxpath

* changelog

* Calculate adjustedLerp only when necessary (#3106)

* Calculate adjustedLerp only when necessary

* Skip updateLerp on followLerp <= 0

* replace boundLerp with gt/lt checks

---------

Co-authored-by: George Kurelic <[email protected]>

* style

* 1 line patch? (#3254)

* update haxelib after merge

* Log pos (#3338)

* change logs to include posInfos

* fix flash errors

* D'oh

* update Changelog

* Add analog directional input to FlxVirtualPad (#3340)

* add overloaded scale

* add FlxReadOnlyPoint

* deprecate statusAnimations

* fix drawCircle centering

* Add analog stick to FlxVirtualPad

* fix code climate

* D'oh

* fix coverage

* fix flash

* add distance overloads

* add FlxMath.getFrameLerp

* honor camera in checkInput

* add FlxVirtualStick deprecate FlxAnalog

* rename to getElapsedLerp

* fix multi-touch

* D'oh!

* doc

* add FlxAnalogState helpers

* rename signals, add statuses

* add deadzone

* remove lerp constructor arg

* fix unit test warning

* overload more shit

* 5.10.0

* change 5.10->6

* Remove unused legacy code & defines (#3059)

* Remove legacy code & defines
- Removed `FLX_POST_PROCESS` define &  related code
- Removed references to `openfl_next`, `lime_legacy` &  `next` defines
- Removed Lime & OpenFL version checks that are always true/false due to the current minimum versions

* Removed `legacy` define being set in include.xml

* Re-added `#if !flash` check in `initRenderMethod`

I mistakenly removed the entire code due to it including `lime_legacy`.

* Brought back `undump()` & `canBeDumped` as `refreshBitmap()` & `canBeRefreshed` respectively (old ones are now deprecated)
- Also brought back `onAssetsReload()` & `getBitmapFromSystem()`

* Added missing line breaks

* remove old import

* Remove another deprecated define

* Restored mistakenly-removed define check

* Rename `refreshBitmap()` to `refresh()`

---------

Co-authored-by: George FunBook <[email protected]>

* FlxSave: Replace `FlxSaveStatus.ERROR` with SAVE_ERROR (#3294)

* replace `FlxSaveStatus.ERROR` with SAVE_ERROR

* update changelog

* add link

* Add deprecated  ERROR back

* remove implicit int casts to/from directions (#3308)

* remove implicit int casts to/from directions

* D'oh

* D'oh!

* deprecate operators and fix haxe 4.2.5

* order imports

* remove casts, add explicit cast functions

* fix not() and add tests

* Doc

* doc + tests

* update changelog

* finalize(?) changelog

* enable glsl3 (when openfl does) (#3347)

* update Changelog

* prepare for release

---------

Co-authored-by: 47rooks <[email protected]>
Co-authored-by: rich <[email protected]>
Co-authored-by: DetectiveBaldi <[email protected]>
Co-authored-by: Flainn <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants