Sunday, December 28, 2008

Isometry, yay!

So, I managed to get my isometric code going today for my little MMO.

I've never done it before, so it was a bit of a challenge. Being a 3d guy mostly, I was well prepared to break out the matrices and do what was needed, but I managed to find all of the old school pixel shortcutty algorithms (that all work very nicely with Flash, it being quite a lot like an old school platform.)

The thing that bit me in the ass for the longest was the fact that my tiles weren't quite right. I did my own programmer art of course, in Flash, and ran into all sorts of problems:

  1. Flash anti-aliases even if you tell it not to.
  2. Flash does weird things with the image size it outputs, sometimes it uses the canvas size, and sometimes something else. It's a bit weird.
There was more as well, but I've forgotten them. Flash however is great for programmer art - Vector art and programmers are a natural fit. Once I figured out what I needed to do to get isometric art going, it was a snap. Basically, you draw a square, you rotate it 45 degrees and then you squish it so its height is half its width. Bam, there's your isometric base panel, and all your isometric lines can derive from that. (Quite easily in Flash as well).

So I had fun for an hour or two messing with isometric graphics, and got a good placeholder for my game. I still need a real artist, or I need to spend a few days myself (which I currently don't have) - we'll see how it goes.

Behold, my programmer art!



Anyway, I'd like to put together a real post on how to do this, as I had to scour tons of different half-complete tutorials to put together what I needed to do this the right way. Lots of very old stuff out there.

And is it just me, or do you hate it when you read someones post or tutorial about some math related thing and they don't define all their variables? GAH.

Anyway, that's my shift at the salt mine, I'm off to bed.

Wednesday, December 24, 2008

How to do something you really shouldn't do in Flex

Here's the thing: I'm not an artist. But, since I'm a one man band right now, I have to be. So, after having become completely sick of my "random colors, basic Flex skin" UI, I spent a morning in Photoshop mocking up a new UI.

Any photoshop gurus in the house will definitely see my PS noob-itude when you see the screenshots.

So, I get to the part where I have to take my glorious creation and make it real. Pshaw, I say! No problem. It's now just about a week later and I'm just about through the other side. I had to write a ton of components, and this is about one of them.

Here's part of what I wanted to do:


That's two Canvases with lots of stuff on them that come in from the top and the bottom of the screen and overlap each other. I didn't realize until I hit the wall what was wrong with that, and you may not either, so I'll tell you.

Whichever Canvas is on top gets all the events, whether it has something there or not. So in my case the bottom panel was last (topmost) in the display list, so its button worked great, but the right button didn't work at all.

I tried a few different things to get this to work, before I finally got something that did. I even in a moment of desperation tried to have a hidden canvas that found the controls in the intersection rect on both canvases and cloned them. Sadly, there's no generic clone method for UIComponents.

So, what I ended up doing was this - those two panels are in a "smart" container that manages their sliding up and down and changing sizes and what not, and that container subscribes to the mouse events..



private function addMouseListeners():void
{
addEventListener(MouseEvent.CLICK,handleIntersectingPanelMouseEvents);
addEventListener(MouseEvent.DOUBLE_CLICK,handleIntersectingPanelMouseEvents);
addEventListener(MouseEvent.MOUSE_MOVE,handleIntersectingPanelMouseEvents);

}



Passing the click events through turned out to be not too hard - when the effects finished playing (to adjust panel sizes or positions) - I recalculate the area of intersection of the two panels.. Then, when the mouse events fire, if I'm within that area of intersection, I clone the click and send it through:


var panel:AbstractPanelBase = getChildAt(0) as AbstractPanelBase;

for each (var uic:UIComponent in panel.getChildren())
{
if (uic.hitTestPoint(evt.stageX,evt.stageY))
{
// Hardcoded to button, to fix this make sure border objects and such are not in main children array
if (uic is Button)
{
newOver = uic as EventDispatcher;

if (evt.type == MouseEvent.MOUSE_MOVE)
{
// Moves have to be interpreted into ins and outs

if (overControl!=uic)
{
if (overControl!=null) // Mouse out and mouse over
{
var newEvt:MouseEvent = new MouseEvent(MouseEvent.ROLL_OUT,true,false,
evt.localX,evt.localY,evt.relatedObject,evt.ctrlKey,evt.altKey,
evt.shiftKey,evt.buttonDown,evt.delta);

overControl.dispatchEvent(newEvt);
}

// Fire the mouse over
overControl = uic as EventDispatcher;

newEvt = new MouseEvent(MouseEvent.ROLL_OVER,true,false,
evt.localX,evt.localY,evt.relatedObject,evt.ctrlKey,evt.altKey,
evt.shiftKey,evt.buttonDown,evt.delta);

overControl.dispatchEvent(newEvt);
}

} else
{
// Clicks just get sent through..

newEvt = evt.clone() as MouseEvent;

// Send only to topmost child, hopefully that is 0

(uic as EventDispatcher).dispatchEvent(newEvt);
}

}
}
}


That bit of code passes the clicks through, but it also does ROLL_OUT and ROLL_OVER events since just passing MouseMove events through won't do it. We're not handling a few more complex cases here, like:

  1. If the control we're worried about isn't a button. It's hardcoded like that because all I'm currently worried about are buttons, and there's some other stuff in the intersection area that would have to be accounted for otherwise. It would be easy enough to extend to a general case.
  2. We're not handling overlapped items inside the the panels - if there are two buttons overlapped the hit test will just find the first one and send the event to it.
Now, the final bit is the ROLL_OUT event when your mouse didn't just roll from one control to another, that's basically right after the code above:


// There was a control that was registered as having the mouse over it,
// but this event didn't find one, so overControl is no longer valid and we need to fire a mouse out
if (overControl!=null && newOver==null)
{
newEvt = new MouseEvent(MouseEvent.ROLL_OUT,true,false,
evt.localX,evt.localY,evt.relatedObject,evt.ctrlKey,evt.altKey,
evt.shiftKey,evt.buttonDown,evt.delta);

overControl.dispatchEvent(newEvt);

overControl = null;
}

// stop propagation of the old event here
evt.stopImmediatePropagation()
So that's the basics of it. If I'd known what a pain it was going to be, I probably would have designed my UI a little differently, but I really don't regard time spent learning how to do difficult things in Flex as wasted time. I've done quite a few out there things in this language so far, and I expect to do quite a few more.

Oh, one last VERY important thing that I of course, forgot:

You must remove all of the mouse listeners at the start of that event handler, and then put them all back as you leave it. It sucks, but otherwise you'll get a nice infinite loop. I experimented with mouseEnabled and mouseChildren and neither in any combination does it, unfortunately.
Welcome to the Blog!

Since I'm doing a lot of stuff in Flex and Java right now that I want to post, I started this blog back up. My old blog was neglected and most likely compromised by some very popular Wordpress security vulnerabilities, so I've pulled the plug on it. I will likely merge its (years, geesh!) of posts and this blog when I get another server up.

Posts coming soon!