Welcome, Guest. Please login or register.
Did you miss your activation email?

Login with username, password and session length

 
Advanced search

1398278 Posts in 67579 Topics- by 60900 Members - Latest Member: saqirmdev

January 20, 2022, 05:36:41 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)AS3 Challenge! - Snake!
Pages: 1 [2]
Print
Author Topic: AS3 Challenge! - Snake!  (Read 7380 times)
Sam
Level 3
***



View Profile WWW
« Reply #20 on: February 11, 2012, 12:32:02 PM »

I like your "has this button been pressed since I last asked?" function, and use something very similar in a general key handling class I use (although that kind of "destructive" function does have to be used with care). However I don't see how that's going to avoid cases where the game will ignore user input.

Taking the same danger case:
Snake is moving upwards, user presses Right then Down really fast such that those events are both received before the next main game loop is called.

Assuming your main loop would have something like:

Code:
if (getClick(keyCodeForUp) && (currentDirection != DIR_DOWN))
{
   currentDirection = DIR_UP;
}
else if (getClick(keyCodeForDown) && (currentDirection != DIR_UP))
{
   currentDirection = DIR_DOWN;
}
else if (getClick(keyCodeForRight) && (currentDirection != DIR_RIGHT))
{
   currentDirection = DIR_RIGHT;
}
else if (getClick(keyCodeForLeft) && (currentDirection != DIR_LEFT))
{
   currentDirection = DIR_LEFT;
}

// and a switch or something to actually move the player depending on currentDirection

As the inputs came really fast, getClick(keyCodeForRight) and getClick(keyCodeForDown) would both return true on this iteration of the main loop.

In this particular instance (and this particular ordering of if/else statements) down is checked first, and denied as the snake's going the opposite direction. Right will then be checked and allowed. So the end result is the snake turns and moves right.

As calling getClick(keyCodeForDown) causes future calls to it to return false until another key press is detected, that first down key press has been lost. So again we're back to the issue of a valid player input being ignored.

If our if/else statements in the above code happened to be in another order then we'd actually get the correct response from the game. If it checked Right first, then Down it would correctly turn right in one update iteration then down in the next (as it wouldn't call getClick(keyCodeForDown) on the first iteration, and so wouldn't lose the record of that input). Of course we'd still have the same problem occurring for some other combination of directions and inputs, but it gave me an idea:

If the getClick function simply returned the value for that key without automatically changing it to false (i.e. make the function call non-destructive) then we can get the behaviour we want without needing to use a proper queue. For simplicity of writing, this code just directly accesses the keyClick array.

Code:
if (keyClick[keyCodeForUp] && (currentDirection != DIR_DOWN))
{
   currentDirection = DIR_UP;
   keyClick[keyCodeForUp] = false;
}
else if (keyClick[keyCodeForDown] && (currentDirection != DIR_UP))
{
   currentDirection = DIR_DOWN;
   keyClick[keyCodeForDown] = false;
}
else if (keyClick[keyCodeForRight] && (currentDirection != DIR_RIGHT))
{
   currentDirection = DIR_RIGHT;
   keyClick[keyCodeForRight] = false;
}
else if (keyClick[keyCodeForLeft] && (currentDirection != DIR_LEFT))
{
   currentDirection = DIR_LEFT;
   keyClick[keyCodeForLeft] = false;
}

// and a switch or something to actually move the player depending on currentDirection

The difference from the previous code is that the record of an input is only removed when that input is acted upon, meaning we don't ever ignore user input. That'll work for our test case, but now that I've typed it out I realise it gives a fresh new error case:

If the player is moving upwards, presses down then presses left with any length of a delay between the two events then they will turn downwards one frame after they turn left, even if the Down event was several seconds earlier. Now we're misinterpreting user input.


What a lot of text, read this bit for the important point:

Considering the problem on a slightly higher level, the order of key presses matters.

Our test case is a snake moving up, then the player pressing Right followed by Down with the key events occurring before the next iteration of the main update loop. This should make the snake do a non-suicidal U-turn. However if the player had pressed Down followed by Right, the snake should just turn right. No solution that merely looks at what keys have been pressed since the last update could differentiate between those two cases.

We need a solution that takes the order of key presses into account. A queue seems the clearest solution for that, although others certainly exist.
« Last Edit: February 11, 2012, 12:37:31 PM by Sam » Logged
buranmu
Level 0
*


View Profile
« Reply #21 on: February 11, 2012, 04:27:54 PM »


Does the following line fix it if I add it to my key listener:
Code:
event.stopPropagation();



Sam gave a awesome explanation of this already, and as he said, the event propagation doesn't prevent that from happening. It's actually completely unneeded in my code, and it's just a (perhaps bad?) habit I've acquired from working with as3.

To solve that problem, I just don't update the direction until each enterFrame event. I store the most recent direction that the player inputs (ignoring illegal/suicide inputs). So even if you were going left and then hit up and right before the next event, it would still be comparing against the left direction until that next event actually happens. I haven't fully read through the boolean/queue discussion about what is the best method, but to me this very straightforward and easy to understand, and it felt appropriate at the time -- I'm sure there are more robust options out there. Hand Thumbs Up Right
Logged
Moczan
Level 5
*****



View Profile
« Reply #22 on: February 12, 2012, 03:08:48 AM »

Yeah, I'm more interested in seeing interesting ideas than anything else here. If you take a look at my code (which isn't amazing, but works) I set up everything as a sort of ultra-lite game engine. So a lot less functionality than Flixel or whatever, but takes care of some of the menial stuff.

Edit: I should add that I'm not an expert in AS3 and it's not my primary dev language. I've just been tinkering with it for a while now.

Yep, that's why I tried the non-grid Snake and did rendering using BitmapDatas only, to show an interesting variation on a classic and to teach those with less AS3 experience the basics of Bitmap rendering. I didn't comment it at all, but tried to pack everything into self-explanatory function, if somebody has any question about the code, feel free to ask  Wink


Logged
michaelplzno
Level 10
*****



View Profile WWW
« Reply #23 on: February 12, 2012, 03:43:07 AM »

@Sam Thanks for the explanation of how events are handled in flash. It makes sense when you think about it now that I've heard the explanation. You talk of objects that have left the stage not propagating their key events, but wouldn't they also not be drawn or selectable as well? Unless you are telling me that a text field could have focus, be removed from the stage and thus become invisible and unselectable but will still exist and keep focus?

Of course I thought stop propagation might just stop keyboard events till the next frame... that wouldn't have been the best solution either as you've discussed.

@randomshade and @Moczan Non grid is cool.

My proposed solution to the input problem:

Since everyone likely has an as3 "Array" containing the snake segments, in the key handler we simply check to see if moving once in the proposed new input direction will hit the second element in the snake list. It removes the need for queues and added variables and so on. It also does not ignore input if the user has some kind of magic fast fingers and presses a valid direction and then the suicide direction.
Logged

Sam
Level 3
***



View Profile WWW
« Reply #24 on: February 12, 2012, 04:18:00 AM »

Unless you are telling me that a text field could have focus, be removed from the stage and thus become invisible and unselectable but will still exist and keep focus?

Yup, exactly. You can make a test to demonstrate it: Create and add a TextField, create KeyboardEvent listeners on the stage, and use a timer to remove the TextField after a couple of seconds of execution.

If the user clicks the TextField before it's removed from stage (and doesn't do any further clicking afterwards) then you'll find that your KeyboardEvent listeners suddenly stop receiving any events. You can also check and see that the stage's focus property will still be referencing the TextField. As soon as the user clicks anywhere within the SWF focus will be set to either some child of the stage or to null, and the event listeners will hear the keyboard events again.

It sounds like a fairly contrived example, but it can happen if you're using TextFields to show floating damage or XP numbers (although they might have to be set to Input mode in order to capture focus) and the user clicks on one before it fades out and is removed from the stage.

I mentioned you can make a simple class to monitor and "protect" the focus without messing up the proper focus-setting behaviour for TextFields and other interactive elements that need keyboard input. Here's a copy of mine:
Code:
public class FocusProtector
{
private var stage:Stage;
private var currentFocus:InteractiveObject;

public function FocusProtector(stage_:Stage)
{
stage = stage_;
stage.addEventListener(FocusEvent.FOCUS_IN, focusInListener);
}

private function focusInListener(e:FocusEvent):void
{
var newFocus:InteractiveObject = e.target as InteractiveObject;

if (currentFocus != null)
currentFocus.removeEventListener(Event.REMOVED_FROM_STAGE, removedFromStageListener);

newFocus.addEventListener(Event.REMOVED_FROM_STAGE, removedFromStageListener);
currentFocus = newFocus;
}

private function removedFromStageListener(e:Event):void
{
stage.focus = null;
}
}

My proposed solution to the input problem:

Since everyone likely has an as3 "Array" containing the snake segments, in the key handler we simply check to see if moving once in the proposed new input direction will hit the second element in the snake list. It removes the need for queues and added variables and so on. It also does not ignore input if the user has some kind of magic fast fingers and presses a valid direction and then the suicide direction.

 _ _ _  0 ms after last update
|_|_|_|
|_|O|_|  A snake travelling upwards
|_|O|_|

 _ _ _  3 ms after last update
|_|_|_|  INPUT ONE:
|_|O|A|  User presses right, we check that A is free
|_|O|_|    It is, so snake's direction is changed to Right

 _ _ _  8 ms after last update
|_|_|_|  INPUT TWO:
|_|O|_|  User presses down, we check that B is free
|_|B|_|     It's filled with snake, so the direction is invalid, snake continues Right

My issue is that I consider INPUT TWO to be a valid input, and that by interpreting it as the player attempting a suicidal U-turn (and so declaring it an invalid input) we are making an error.

I hold this position because if the user made precisely the same input sequence, but started it 10ms later (assuming we're updating state every 16ms) then it would be a valid input:

 _ _ _  0 ms after last update
|_|_|_|
|_|O|_|  A snake travelling upwards
|_|O|_|

 _ _ _  13 ms after last update
|_|_|_|  INPUT ONE:
|_|O|A|  User presses right, we check that A is free
|_|O|_|    It is, so snake's direction is changed to Right

--Update!--

 _ _ _  0 ms after last update
|_|_|_|
|_|O|O|  A snake travelling to the right
|_|O|_|

 _ _ _  2 ms after last update
|_|_|_|  INPUT TWO:
|_|O|O|  User presses down, we check that B is free
|_|O|B|     It is, so snake's direction is changed to Down

It seems ugly to me that the same input sequence would have different results depending on when during the update cycle it is made.
Logged
michaelplzno
Level 10
*****



View Profile WWW
« Reply #25 on: February 12, 2012, 05:00:41 AM »

Ok, that is weird about the keeping focus after stage removal. Ah AS3.

Back to input. I think you're thinking of fighting games more than snake. The point of snake to me is that you have to time your key presses perfectly based on what you see. If there has been no update then you pressed too soon. In a fighting game it would be an obviously huge mistake to loose a key press.

If we did the queue thing I would call it "fighting snake" where you can jam in key presses to almost program the snake's future movements like a fighting game.

Edit:(FIX HORRIBLE GRAMMAR MISTAKE)
Logged

Sam
Level 3
***



View Profile WWW
« Reply #26 on: February 12, 2012, 06:11:27 AM »

The point of snake to me is that you have to time your key presses perfectly based on what you see. If there has been no update then you pressed too soon.

Ah, that's fair enough then. Most programming decisions end up being game design decisions in the end.  Hand Thumbs Up Right
Logged
randomnine
Level 1
*


View Profile WWW
« Reply #27 on: February 13, 2012, 04:23:01 PM »

Here's mine. Spent the weekend on it, though I did play around with some advanced mechanics to make things more interesting and use this as an opportunity to play with Pixel Bender. Sorry - I got into it and ended up polishing it a fair bit!

Playable SWF and source code (source code in .zip at bottom of page)

It's in HaXe, not AS3, but they're very similar languages. It also uses some basic engine code I use for all my games.

This queueing discussion wasn't here when I started. Shocked I queue one move ahead because pulling off intricate manoeuvres makes people feel awesome, and I want my players to feel awesome. Precision moves in Snake are hard enough. I think it's always worth facilitating awesomeness where you can.

I switched to interpolating the snake's head and tail between movement frames because it felt better and made it easier to time key presses. It's pretty easy to add this just as an interpolation step in the rendering without changing the game logic.

On the design side: My play field's only 18 x 13 and there are five apples, to get people doing lots of tight manoeuvres even when it's easy early on. If the play field's too big and empty there's no urgency to anything and the game becomes a grind to get to the hard part.
Logged

jotapeh
Level 10
*****


View Profile
« Reply #28 on: February 13, 2012, 04:49:46 PM »

randomnine that is a gruesome but awesome snake variant.
Logged
randomshade
Level 1
*

Fastzelda


View Profile
« Reply #29 on: February 13, 2012, 05:27:56 PM »

@randomnine: Awesome  Evil
Logged
roboprez
Level 1
*


Got to love them pixels


View Profile WWW
« Reply #30 on: February 18, 2012, 01:28:45 AM »

Here's mine

And here's the source

It took a little longer then I expected (5 hours) but I was going for 'style' over gameplay. Since the snake moves quite slowly I made the field only 12x12 tiles. I could hae changed the speed but I wanted to emphasize the slidey boxes.

In the source download I saved a copy of the .swf every time I stopped working on it (5 times) so if you want you can see the progression.
Logged

dimmduh
Level 0
*


View Profile
« Reply #31 on: January 12, 2013, 08:52:25 PM »

Mine www.kongregate.com/games/dimmduh/snake-snake
Logged
Pages: 1 [2]
Print
Jump to:  

Theme orange-lt created by panic