Willy and Mathilda's Houseboat Adventure: The Game



  • I had been keeping up two logs for this game: a more general one on TIGForums, and a more developer-oriented one on Blogger. The latter I will replace with posting here in the HaxeFlixel forum from now on, for better targeted exposure.

    So, for my first post, I wanted to share something I learned about HaxeFlixel while using my own library, FlxScrollableArea. If you're using that or any similar off-screen content method, hopefully this will save you some pulled-out hairs.

    I had been using FlxScrollableArea just fine without any problems in other contexts, so I was a bit surprised when I went to use it another time, and got very strange results: my content looked very strange, with locations of some sprites (but not all) in places other than where I had put them. Worse yet, if I got rid of the scrollable area, and moved my off-screen content on-screen, everything looked right! What was going on?

    It turns out that the thing I was doing differently from all previous times was making my content (contained in a FlxSpriteGroup) a bit more complicated by having some of its elements created with locations relative to their neighbours, rather than relative to a loop variable for laying things out in a row.

    I had code like this, to determine the location to create the next control at:

    nextControlY = controlArrow.y + controlArrow.height + 3;
    

    Seems simple enough not to contain a logical error, right? Nope! The thing I had forgotten here is that controlArrow.y will not equal what I passed to controlArrow's FlxSprite constructor, because it's part of a FlxSpriteGroup. What I pass to the constructor is coordinates relative to the FlxSpriteGroup's coordinates, but what .y returns is relative to the world. Passing that back into the next sprite's constructor means it gets offset by the FlxSpriteGroup's coordinates an extra time, which, when we're talking about offscreen content, means that depending on where offscreen exactly you're putting things, sprites may be offset by a whole screen or more, making them seem to disappear altogether.

    The fix is easy enough. In this case my FlxSpriteGroup is called help, so the corrected code is:

    nextControlY = controlArrow.y - help.y + controlArrow.height + 3;
    

    Whew! Everything works now.



  • Next fix, turns out, is also related to FlxScrollableArea, and this time you can get the fix directly from here. I had never tried using FlxScrollableArea within a FlxSubState before, so this release fixes that. Thanks to this, now I have a scrollable help screen when the player pauses the game:

    Help screen FlxSubState with FlxScrollableArea



  • Another FlxScrollableArea fix! So, this time, I discovered that the setup I had used, where the main camera of the parent state can be scrolled however it needs to be for the game, can cause a seemingly strange problem.

    In my substate, I had set up another camera for viewing the scrollbars (I have them together with the header in the above animation) followed by the FlxScrollableArea camera on top of that. That makes it so I can set the scroll factor of all the non-moving parts (header and scrollbars) on the screen to (1, 1) in order that they aren't duplicated within the scrollable area itself. So you could see the scrollbars no problem, but if the main camera was scrolled, then the scrollbars would no longer respond to clicks. Why? Because I had used getWorldPosition instead of getScreenPosition for processing mouse events. Since I imagine that scrollbars will generally be statically positioned relative to the window, getScreenPosition seems a better choice. Indeed, it fixes this bug.

    I hadn't encountered it before in my vertical scrollbar example above, because the game itself was vertically-scrolling only, and I wasn't testing when it was scrolled much. I hit it when I went to try the help system on a horizontally-scrolling game.

    Find the fix in 0.2.1.



  • The other day I encountered of these issues that I think is HaxeFlixel-related, and then I cannot manage to reproduce. (Well, I'm sure I could, given enough time, but I'm trying to push for final.)

    But in case the workaround is of use...

    I had this line in a function, which, via trace calls, I verified was called only once:

    FlxTween.tween(sidePanel, {x: sidePanelLeftStop}, 0.6, { ease: FlxEase.quintIn } ).then(FlxTween.num(0, 1, 0.8, { ease: FlxEase.circOut }, fadeMap));
    

    Yet, fadeMap was getting called at the end of the run twice with the value 1, instead of only once:

    MapState.hx:142: fademap,0.661437827766148
    MapState.hx:142: fademap,0.74535599249993
    MapState.hx:142: fademap,0.812232862067414
    MapState.hx:142: fademap,0.866025403784439
    MapState.hx:142: fademap,0.90905934288631
    MapState.hx:142: fademap,0.942809041582063
    MapState.hx:142: fademap,0.968245836551854
    MapState.hx:142: fademap,0.986013297183269
    MapState.hx:142: fademap,0.996521728591783
    MapState.hx:142: fademap,1
    MapState.hx:142: fademap,1
    

    (In isolated reproductions, it got called only once, as expected.) Unfortunately, I had some other code I wanted to chain after it, which was not a tween, so I had been banking on the call with 1 being passed; there was some game logic happening in there, which, if executed twice, introduced a pretty nasty bug.

    So the quick and dirty workaround was:

    	var doubleVOneWorkaround:Bool = false;
    	function fadeMap(v:Float){
    		trace("fademap", v);
    		if (v == 1 && !doubleVOneWorkaround) {
    			doubleVOneWorkaround = true;
    			// the rest of the stuff I want executed only once, here
    

    No idea what that was about, but this fixed the bug.



  • Having an interesting time today sorting out some last-minute bugs.

    One is currently fairly vexing. I have a grid of tokens. The current token should have its neighbours highlighted in a certain colour. My function to do this works intermittently.

    The odd thing is, if I pull up the debugger, and check the colour of a neighbour that is not highlighted, it's already the correct colour. I know I have the right one, because I can set it, and see it change visually. If I then set it back to what its value was (according to the debugger), suddenly it looks right.

    Adding trace statements everywhere within my own classes, all the values and code paths followed look correct. So why is it visually incorrect?

    A mystery to be solved...



  • And it's solved. I did have a set_color override in my FlxSpriteGroup subclass, but, I thought, everything was doing what it should do.

    Not so.

    In some cases, I would not call super.set_color(). In these cases, I figured this was OK, because I was just wanting to set one sprite's colour and do the rest manually. But not doing this in these cases was causing the OTHER cases to fail, because then the FlxSpriteGroup's internal .color was not being set. The next time it needed to be set, it figured it was already correct and didn't bother transforming any of the children.

    Whoops. All good now. :)



  • As for the FlxTween callback being called twice from before, I'm not sure what I tried before, but this time I was trying to isolate another bug, and managed to reproduce this earlier one. Reporting now... :)



  • Today I thought I was being clever by leveraging FlxTween chaining, which I thought was pretty nice syntactic sugar, in cases where I wanted a delay in between .then() calls. How? Simply by using an empty .num() tween: FlxTween.num(0,1,delay).then(...).

    Of course, that's how I found a bug in the library. :P

    https://github.com/HaxeFlixel/flixel/issues/1871



  • Today's hard-to-track-down bug I thought I should share with you.

    HaxeFlixel can be deceptively straightforward to work with sometimes. Things that seem to just take care of themselves may actually be hiding something from your understanding that it would be better if you knew. I wish I remembered where I read, but one article I saw had a HaxeFlixel first-timer diving into the internals to understand how the basic engine worked, rather than diving into "coding fun stuff." At the time, I thought, "Wow, that's really hardcore! But I just want to code fun stuff, that's why I picked up a ready-made library to begin with." Now I see the wisdom of it!

    The basic engine knowledge that I needed today was that FlxGroup.update() iterates through its members array, calling each member's update() in turn. However, it does so using a simple loop counter. So, if you happen to update the members array in any of the members' update() functions, it may, sometimes, randomly, depending on how much your code messes with .members and how randomly so, cause any of the following things to happen during the current outer update():

    • a member's update() may not be called
    • a member's update() may be called more than once

    Obviously, this is bad, and can be tough to track down!

    In my case, my HUD appeared to be mysteriously updated twice in a row. For a long time my debugging was fruitless (partly because I'm normally on cpp without stepping tools set up yet :( ...I only get things working in Flash when I'm as stuck as I was today) because I looked at that and said, "No, HaxeFlixel doesn't do that. It must be something else," like, I added two HUDs or something. Nope. Here's what happened within one outer update() call:

    Members before and after an inner update(), during an outer update()

    As you can see, my HUD.update()'s own chain reaction of code caused, somewhere, the HUD's index within .members to move down a spot. Then FlxGroup.update() simply incremented its counter, and ran .update() on the next member...which was my HUD again. So, yes, if you mess with .members, HaxeFlixel does do that.

    Somewhere in the docs I remember reading, "Don't mess with .members unless you know what you're doing." So, for those of you, like me, who, after a while, thought they knew what they were doing, but didn't know enough to know that they didn't know what they were doing...now you hopefully know what you're doing. :)

    The solution? Well, more of the problem, first: I actually need to modify .members because I have some z-order changes that depend on the user's actions, i.e. are not known in advance. So I members.remove() and members.insert() these items as needed (and the insertions are not on top of the stack, BTW.) So I think the only HaxeFlixely way to do it without the above breakage is to put everything into additional FlxGroups that wasn't already...at least everything that might need a z-order change. That way, the outer members array shouldn't have any changes to it. So hopefully by following that design pattern, we all can avoid such bugs sneaking up on us.

    I haven't thought about, and don't care to think about at the moment, what I would need to do if I needed to z-order change something that also had an overridden update(), and that this z-order change had to occur in the overridden update() of something in the same group. (E.g. what if I needed an object to change its own z-order during its own update()?)



  • Another word of caution, if you're add()ing rather than insert()ing with the thought of putting something at the top of the z-order, the above strategy of putting it in a FlxGroup is insufficient. Check FlxGroup.add()'s doc--it will just put your object back in the same spot--or even lower--because it reuses the first null spot it comes across.



  • Furthermore, if you're using the workaround I am, putting everything into a group, you may run into problems if anything you previously had directly added to the state was itself a group, because recursion is currently broken.


  • administrators

    Note that that bug should only affect forEachOfType(), other forEach functions should be fine.



  • @Gama11 Ah, good to know. In my case that's the one I use the most :)



  • In today's news, I went to add a feature that I thought would be easy. The minigame "Acrobatics as the Catfish Fairy" has a series of hoops for you to jump through, stacked vertically. I wanted the higher ones (worth more points) to also be gradually smaller (vertical stretch of the existing graphic.)

    So, I stretched them:

    collidable.setGraphicSize(Std.int(collidable.width), Std.int(collidable.height * Proportion));
    collidable.updateHitbox();
    

    They look correct. Good! However, pixel perfect collision checks, which are pretty crucial here, seemed to be misbehaving. So I modified the game in-place to see exactly how they were misbehaving (a lot easier than jumping on the trampoline over and over)--whenever a collision is happening, the catfish fairy goes red:

    catfish fairy at various hoop points

    What should be happening is, the catfish fairy is red when touching the bottom or top (solid) parts of the hoop, and not at all during the middle part. As you can see, the top/bottom isn't colliding, then a spot on either side of the centre is colliding, but the very centre is again not colliding.

    This particular hoop is actually stretched to be bigger than the original graphic by about 1.5x. Visually, is seems as though the PPC check is happening against the original graphic size, centred.

    Why?

    Seems that the pre-resized values are being used, as I thought, in FlxCollision.pixelPerfectCheck() (currently I'm only looking at the unrotated case):

    boundsB.width = Target.frameWidth;
    boundsB.height = Target.frameHeight;
    

    These are the pre-resized values, because if you look at setGraphicSize(), you see that frameWidth/Height are not set therein. They're actually kept around as a reference to the original sizes on purpose.

    More importantly, there's reference to framePixels for the actual pixel data being checked.

    OK, what's the next course of action?

    Either I could prerender all the hoops at different sizes (I don't like this approach, since it's more difficult to tweak things), or change the PPC check function. I'll take a crack at the latter and see what we come up with, but it might not be an acceptable change, since this is a function that gets run a lot and it needs to stay quick.

    Meanwhile on closer examination of setGraphicSize, it seems I'm using it quite inefficiently, since it's meant to help in the cases where scale stays the same. Much better would be simply collidable.scale.y = Proportion;.



  • And...today we went with The Middle Way. I did not have to prerender, thank goodness. I also did not have to change HaxeFlixel, which, if nobody else was needing this feature, is probably the best anyway.

    The solution was simply to use FlxSprite.stamp() to render the resized image onto a new sprite, which is then at scale 1 according to flixel:

    var collidableToScale = new FlxSprite(0, 0, AssetPaths.acrobaticsBigRingCollidable__png);
    collidableToScale.scale.y = Proportion;
    collidable.makeGraphic(Std.int(collidableToScale.width), Std.int(collidableToScale.height * Proportion), FlxColor.TRANSPARENT, true);
    collidable.updateHitbox();
    collidableToScale.origin.set(0, 0); // important, otherwise the stamp will be vertically off
    collidable.stamp(collidableToScale);
    

    After that we throw away the temporary collidableToScale and are done with it. The result:

    catfish ppc checking correctly with various hoop sizes

    Hooray!



  • Today's adventure was with one of my favourite addons, FlxMouseEventManager. It's a favourite because it tidily takes care of mouse events for any FlxObject for you--you just pass it the handlers, and it does the rest.

    I had used FMEM extensively with success. However, today I went to add some events to an object, and found that some of the events weren't working. I figured it was maybe because I was using FlxSpriteGroups, and I've sometimes encountered oddities with them. (Sometimes it's not the fault of FlxSpriteGroup, but a misbehaving member. E.g. if you have a FlxText that's misreporting its height--a bug I've encountered but yet to figure out how to fix--then it can make the whole group misreport its height.)

    After some debugging, I realized that the events I added with FMEM were working, but the events that the object had already registered with FMEM itself--those were the ones that broke. Turns out that if you call FlxMouseEventManager.add() twice on the same object, the latter call somehow overrides the former call. All I needed to do was modify my object so that I could pass it some events to pass on to FMEM all in one call, and everything worked as expected.

    Again, a lesson in "know what thine libraries are doing for thee." It may not always be possible, but it is with flixel, at least, because you have the source. :)



  • @IBwWG Just a note on stamping, this of course doesn't apply to animated sprites. In that case it's easier to get them before you initialize them, resizing the whole graphic before it gets parsed as frames:

    		if (Scale == 1.0) {
    			loadGraphic(GraphicAsset, true, OriginalWidth, OriginalHeight);
    		} else {
    			var originalBitmap:BitmapData = FlxAssets.getBitmapData(GraphicAsset);
    			var matrix = new Matrix();
    			matrix.scale(Scale, Scale);
    			var newWidth = Std.int(OriginalWidth * Scale);
    			var newHeight = Std.int(OriginalHeight * Scale);
    			var scaledBitmap = new BitmapData(newWidth, newHeight, true, 0); // fill with transparent
    			scaledBitmap.draw(originalBitmap, matrix);
    			loadGraphic(scaledBitmap, true, newWidth, newHeight);
    		}
    

Log in to reply