Input Handling

Started by
9 comments, last by Alberth 6 years, 8 months ago

Using the LibGDX InputProcessor, what can I do with the keycode integer values generated in keyDown() and keyUp() to handle input correctly? To expand, what I mean by handling input correctly is to handle it in such a way so that GameObjects which need to be controlled by user input, do not themselves actually handle the user input or hold any input related values such as key down/up flags, and are instead influenced indirectly by something else which handles the input.

One way in which I feel this can be done is with a Controller object which can act as a mediator between the input values and the object you want to be influenced by the input. The Controller would hold a reference to the object and methods to receive the keycode input value. Depending on this value, the Controller would then, using a switch case or set of if-else's, call relevant methods on the object such as move(x, y) or attack(). This decouples the object from the input and allows you to control it in a top down way.

The problem with this approach, in my case at least, is that it does not provide a fine enough level of control over the object so that it responds appropriately to different sets of keys. A concrete example of this would be if the object is moving in a certain direction because a key is held and another key is pressed detailing that it should move in the opposite direction, I would want the key that would make the object move in the opposite direction to be ignored so long as the first key remains held. With the Controller setup that wouldn't happen. The second input would override the first, causing the object to move in the opposite direction.

The way that I have managed to achieve the level of control I need over how input is acted on by GameObjects, which in my case is primarily the Player object, has been by making the Player receive the keycode directly from the InputProcessor and handle it through the use of flags and states in the form of State objects. The flags represent the state of input which is relevant to it and the states determine explicitly the state of the Player and what it can do at any one time, including how it handles input state. The current state updates every Player update and based on a set of conditions, including the state of the flags, it either changes to another appropriate state or continues updating.

This setup is bad for a couple of reasons. It makes it so that the Player is responsible for maintaining key state in the form of flags, essentially becoming an input handler. It also tightly couples the States it uses to itself, making it so that they can only be used by the Player and not another kind of object.

The problem I am facing is that I don't know how to change the setup so that GameObjects which should act on input can do so while remaining decoupled from the input, while also maintaining the fine level of control that states and flags provide over how the input is processed.

What can I do to achieve this? Thank you.

 

Advertisement

When you find there are several only loosely coupled issues with a piece of code, chances are you're building blocks are too big. A good tactic that often works is to chop your building blocks to smaller pieces, to get a better view of what is happening. After you did that, you can decide what piece goes where in terms of software structure.

1. You speak about converting numeric values to "up" or "down" notions, so that would be one piece

2. Somewhere in the chain of pieces you want to delay certain keys under certain conditions, ie your flag-stuff. That's a second piece.

3. The player needs movement commands eventually, so that's the 3rd piece.

4. You speak of several game objects that need to be controlled, so that's a 4th piece.

I think these are mostly all pieces. Next step is the order of the pieces. The 4th piece distributes input to different objects, so it needs to be before piece 3 and probably piece 2 too. Before piece 1 means you first distribute in the right direction, and then translate key values. That means eg "spacebar" can mean different thing with different game objects. Let's assume piece 1 must be done always first (so "up" key is always up for all game objects), and the 4th piece thus ends up after piece 1.

Piece 3 is the last piece in the chain for the player, which thus leads to the next sequence of computation steps:

A. Convert numeric values to notions like up-key-pressed, up-key-released, shoot-key-pressed, shoot-key-released, etc.

B. Distribute the keys towards the right game object

C. Convert key pressed/released from A to commands understood by the game object (like a player)

D. The player that handles physics, displays the images, and handles death, etc.

Next, we arrive at object level, what functionality is performed where and how. A seems useful to do near receiving the event, imho, as it means you can quickly discard any event that is not a valid key, etc. B I am not sure how you do that. You need something that fowards the key events towards the game objects, either by a call, by a publish-subscribe mechanism, or what ever you do there.

Piece C is probably closely related to piece D, so making it an object that is stored in a field in D seems useful. By making it a separate object, you can use it in other places too, or you can replace it with a different implementation to get different reaction of the player to the keys.

Finally, D must define an interface for C if you do this nicely.

 

Does this help?

 

I'm sorry but no, I don't really follow you.

Would you be able to be more specific about how you would implement your idea?

Is the idea of a Controller one which should be pursued? If so, in what way should it be structured?

In a solution of yours for a problem like this, do states ever come into the equation? Are they suitable in some way, on some level, even if perhaps not at the object level?

A thought, and probably not a well formed one, for a solution has occurred to me in which some sort of Event types would be generated depending on the value of keycode. The Events would be processed somehow so that depending on which ones are active at a given time, others can also be active or not active and perhaps can be forced to go from active to inactive. Then whichever GameObject is interested in dealing with these input events can process the active ones which are available. This would mean that if the Events manage themselves so that a moving left event for example is active, then a moving right event would not be able to become active and so in effect opposite movement while moving would be ignored.

That's as far as that idea goes. I don't know how it would be implemented, if it would even be possible to.

However, as an idea, does it sound plausible or is it far fetched?

It would be very helpful if concrete examples of how to handle input in a correct manner could be provided. Remember, we're not dealing with the totally abstract here. I need to know a good way to get from the starting platform A which is the InputProcessor and its keyDown(int keycode), keyUp(int keycode) methods to B which is the Player so that input can be dealt with in a controlled way which so far I have only been able to do in what I believe is a very crude manner. I would like the manner in which input is dealt with to be less crude and less invasive of GameObjects. I just don't know how.

1 hour ago, Seer said:

I'm sorry but no, I don't really follow you.

You seem to have noticed you had a problem, and then jumped to a solution called "Controller", and then got quite stuck. Basically you now have a number of empty boxes with labels like "Input handler" and "game object", and "controller", but what to put where?

To solve that blockage, you should analyze what (computation) steps happen exactly from input to a moving player. What I mean, what are the elementary steps, that must happen from getting keyboard events to getting the player to move correctly, no matter how many boxes you have, and where you put them (that is, independent of any implementation consideration).

In my experience that works best if you don't have an implementation solution in mind, since that imposes a structure that you consciously or unconsciously also add to the equation. Your efforts to try and fill that structure and at the same time inventing what the elementary steps are, is a very hard problem. Stop doing that. Drop the boxes, in fact throw away your entire program. Imagine you start a new program, an ideal one, where anything is possible.

 

So the question becomes: "In an ideal world, given keyboard events as you get them, and a player that moves like you want it to move, what are the computation steps (and in which order should they be applied), such that you get the desired effect to process keyboard input?"

That's the question I tried to answer first, and it ended with 4 pieces, A to D. Maybe my pieces are wrong, or in the wrong order, but you are the better judge there.

But in essence, no matter how you organize your code, those 4 pieces are (I think) what must be done at all times, in that order.

 

Now you go back to your boxes. Your problem is much simpler now. You have a number of boxes, you have a number of computation steps that must be performed in a particular order, just assign all steps to some box (invent more boxes if needed or throw away boxes that you don't need). After you did that, you know what to program where.

 

2 hours ago, Seer said:

In a solution of yours for a problem like this, do states ever come into the equation? Are they suitable in some way, on some level, even if perhaps not at the object level?

Oh sure, you also already had them, you just named them "flags". You can see each combination of values of the flags as a state. When you change a flag, you would jump to a different state. "states" only means you have special behavior based on the state (you do do X in state A, and Y in state B). Whether you select that special behavior X or Y because you compute it in a State object A or B, or you select that behavior because of some flag values makes no difference.

In this case I would do conversion C from key-events to player actions probably in a state machine (although I haven't yet found a nice way to write a state machine in a generic programming language).

2 hours ago, Seer said:

A thought, and probably not a well formed one, for a solution has occurred to me in which some sort of Event types would be generated depending on the value of keycode.

I proposed that too, in 2 steps. Step A converts from numeric key values to "up-pressed", "down-released", "shoot-pressed" etc events, step C converts those events to actions that the player (piece D) understands.

The reason to make that 2 steps is that C may or may not be specific to the player (depending on how you want keyboard control to work on different things you can control).

2 hours ago, Seer said:

That's as far as that idea goes. I don't know how it would be implemented, if it would even be possible to.

However, as an idea, does it sound plausible or is it far fetched?

It sounds like you want to do piece A, and then B distributes to all game objects?

If you swap B and C, then from A you get eg "shoot-released" like events, C converts that to player actions, and then B distributes that to one (or all) game objects?

 

See how simple it becomes if you have a list of essential elementary steps that you must do? Swap the order, change a function, and you get completely different options.

If you start too early thinking in terms of classes and objects where to put them and how to keep them generic and expandable and all that, you're unconsciously blocking a lot of options, and it's much harder to reach that clean sequence of steps where you make the true decision how processing is done (the objects are basically just wrapping around the processing structure, they don't affect the behavior itself).

 

2 hours ago, Seer said:

It would be very helpful if concrete examples of how to handle input in a correct manner could be provided. Remember, we're not dealing with the totally abstract here. I need to know a good way to get from the starting platform A which is the InputProcessor and its keyDown(int keycode), keyUp(int keycode) methods to B which is the Player

In my pieces, I inserted two more processing steps, so your B ended at D in my case. My step C is the "flags" thingie you have now to convert from key events to player actions, except it's coded as a separate object, so you can change or re-use it. My step B transfers the key-events from A to C, which I am not sure of how that exists (or even if it exists) in your code.

The problem with examples is that you're then basically copying inventions that someone already invented and published. That works for common problems, eg you don't invent common algorithms like A* or Bresenham. Instead you read how it works, you read an example, and then you code it.

This approach fails horribly as soon as you move into new territory, or unpublished territory (or published but not-findable teritory). That happens as soon as you go down into enough detail, where your combination of elements, and your ideas of what should exactly happen, becomes a unique combination. In such a case, there are no examples, and you have to work out how to solve the problem yourself. How to do that is also valuable experience that you must learn to become a successful programmer.

Sorry for the delay in getting back. I've been working on this bit by bit and have made some progress.

However, I have encountered a problem which I cannot get past. I have an idea as to why what I am trying is not working, but to confirm this I must ask:

Will the final object reference of a sequence of same type object reference assignments still be a reference to the original object, regardless of how many object references were assigned beforehand or of changes in scope across multiple classes?

Or, will the local scope of each new reference override the preceding reference from which it was assigned so that the newly assigned object no longer references the original, but instead is a new object in memory?

I would have thought the former, but my program is not behaving as it should based on that.

Of course, my assumption as to the reason behind the misbehaviour could be wrong, but I cannot see what else might be the cause of the problem right now.

To show you what I'm actually talking about, step through these classes below and follow the progress of InputHandler.currentState as it is passed around and referenced.

InputHandler -> NotHandlingInputStateTransitionSet -> Transition

As you may see, when a Transition according to its TransitionConditions is successful, it assigns currentState to a reference to an appropriate predetermined newState, thus changing InputHandler.currentState and allowing different behaviour depending on which State it is referencing.

Or at least this is what should happen and I cannot see why it does not. As I said, this may not actually be what's causing State Transitions not to work, but it is what appears most likely to me and I thought it worth looking into first.

If you need more context or if anything is unclear let me know. Thanks.

 


public class Y {
  // Contents omitted for brevity.
}

public class X {
  // making them public for brevity of the example.
  public Y a;
  public Y b;
  
  public X(Y a, Y b) {
    this.a = a;
    this.b = b;
  }
  
  // Other contents omitted for brevity.
}

public class App {
  public static void main(String[] args) {
    f();
  }

  public static void f() {
    Y ya = new Y();
    Y yb = new Y();
    Y yc = new Y();
  
    X x1 = new X(ya, yb);
    X x2 = new X(ya, yc);

    System.out.printf("Before g call:\n");
    System.out.printf("\tya=%s\n", ya);
    System.out.printf("\tyb=%s\n", yb);
    System.out.printf("\tyc=%s\n", yc);
    System.out.printf("\tx1=%s (a=%s, b=%s)\n", x1, x1.a, x1.b);
    System.out.printf("\tx2=%s (a=%s, b=%s)\n", x2, x2.a, x2.b);
  
    g(x1, x2);

    System.out.printf("\nAfter g call:\n");
    System.out.printf("\tya=%s     (SAME)\n", ya);
    System.out.printf("\tyb=%s     (SAME)\n", yb);
    System.out.printf("\tyc=%s     (SAME)\n", yc);
    System.out.printf("\tx1=%s (a=%s, b=%s)     (SAME)\n", x1, x1.a, x1.b);
    System.out.printf("\tx2=%s (a=%s, b=%s)     (x2.b different)\n", x2, x2.a, x2.b);
  }

  public static void g(X x1, X x2) {
    System.out.printf("\nInside g:\n");
    System.out.printf("\tx1 inside g=%s (a=%s, b=%s)\n", x1, x1.a, x1.b);
    System.out.printf("\tx2 inside g=%s (a=%s, b=%s)\n", x2, x2.a, x2.b);

    Y yp = new Y();
    Y yq = new Y();
    Y yr = new Y();

    System.out.printf("\typ=%s\n", yp);
    System.out.printf("\tyq=%s\n", yq);
    System.out.printf("\tyr=%s\n", yr);
  
    X x3 = new X(yp, yq);

    x1 = x3;
    x2.b = yr;

    System.out.printf("\tx3 inside g=%s (a=%s, b=%s)\n", x3, x3.a, x3.b);
    System.out.printf("\tx1 inside g=%s (a=%s, b=%s)\n", x1, x1.a, x1.b);
    System.out.printf("\tx2 inside g=%s (a=%s, b=%s)\n", x2, x2.a, x2.b);
  }
}

In java you cannot change the parameters, but you can change the internals of parameters.

Simplest way to see what is happening is to make an example like above, where you print the objects. As I didn't provide a toString method, it prints ugly addresses, but good enough to see what happens.


Before g call:
	ya=com.test.Y@15db9742
	yb=com.test.Y@6d06d69c
	yc=com.test.Y@7852e922
	x1=com.test.X@4e25154f (a=com.test.Y@15db9742, b=com.test.Y@6d06d69c)
	x2=com.test.X@70dea4e (a=com.test.Y@15db9742, b=com.test.Y@7852e922)

Inside g:
	x1 inside g=com.test.X@4e25154f (a=com.test.Y@15db9742, b=com.test.Y@6d06d69c)
	x2 inside g=com.test.X@70dea4e (a=com.test.Y@15db9742, b=com.test.Y@7852e922)
	yp=com.test.Y@5c647e05
	yq=com.test.Y@33909752
	yr=com.test.Y@55f96302
	x3 inside g=com.test.X@3d4eac69 (a=com.test.Y@5c647e05, b=com.test.Y@33909752)
	x1 inside g=com.test.X@3d4eac69 (a=com.test.Y@5c647e05, b=com.test.Y@33909752)
	x2 inside g=com.test.X@70dea4e (a=com.test.Y@15db9742, b=com.test.Y@55f96302)

After g call:
	ya=com.test.Y@15db9742     (SAME)
	yb=com.test.Y@6d06d69c     (SAME)
	yc=com.test.Y@7852e922     (SAME)
	x1=com.test.X@4e25154f (a=com.test.Y@15db9742, b=com.test.Y@6d06d69c)     (SAME)
	x2=com.test.X@70dea4e (a=com.test.Y@15db9742, b=com.test.Y@55f96302)     (x2.b different)

In the "inside g" output, you do see x1 getting changed to x3, but x1 is a local variable inside g that is initialized with the content of x1 of function f. You can also see that x2 inside g always points to the same thing, and that x2.b is changed.

After the call, x1 in f is not modified (passing a value down into a function never changes that variable), but x2.b in f does get modified, as it didn't change x2, but a value inside x2.

To force a change of x1 in f, the simplest solution would be to make g return the new value (leading to "x1 = g(x1, x2);"). If you want to change more values, usually you make a new class returning all the new values, and construct an object of that class inside g which you pass back up.

Changing content of a passed-in value is generally not much recommended, as it causes side-effects to the call. If you use it, be sure to document this fact, to avoid throwing unexpected surprises to any user (including yourself) of that function.

 

Ah, I see. This would be the exact same when passing objects to constructors of other classes then wouldn't it. I thought that object instances in Java acted as references to an object in memory and that a change made to one instance of that object would be reflected in every assigned instance, since they all reference the same object. From what you've shown me, it seems this does not hold true when passing instances out of scope. Then it seems, you're working with a copy of the instance.

But wait, that can't be true can it? When you modified x2.b, the change persisted across scopes. If x2 was truly a copy, that wouldn't happen would it? I'm a little confused on that.

 

I got the transitions to work thanks to your advice. All still in early stages, things may yet need to be changed, but I like the shape and flow of the transitioning process now. If you're interested, you can see how transitioning between NotHandlingInputState and HandlingUpKeyPressedState works by having a look at these classes.

InputHandler ->

NotHandlingInputStateTransitionSetHandlingUpKeyPressedStateTransitionSetStateTransitionSet ->

NotHandlingInputStateHandlingUpKeyPressedStateState ->

TransitionInputTransitionConditionTransitionCondition

The StateTransitionSets were made to break up each States Transition and TransitionCondition initialisation process into more manageable chunks. Is it acceptable to make classes like this whose job is to perform one very specific task such as in this case setting up a specific set of Transitions for a State? If not, what would you recommend to help manage setting up the States in InputHandler? Each State can be made with any number of Transitions, each with any number of TransitionConditions, where each TransitionCondition can be any specific type of TransitionCondition. If all of this was done in InputHandler.initialiseStates(), the method would be a huge mess.

I would be interested to know your thoughts on the design so far if you have time to give it a look. Of course if anything is unclear I'll do my best to explain. Thanks.

2 hours ago, Seer said:

Ah, I see. This would be the exact same when passing objects to constructors of other classes then wouldn't it. I thought that object instances in Java acted as references to an object in memory and that a change made to one instance of that object would be reflected in every assigned instance, since they all reference the same object.

This is true, however your notion of "object instance" may be subtly wrong. Let's try again with an even simpler example.


public class Z {
  public int a = 1;
}

Z fx1 = new Z();
Z gx1 = fx1;

Here there is one object (one instance of a class), namely the result of "new Z()". There are 2 references that can point to an object of class Z, namely "fx1" and "gx1" (you can think fx1 as being x1 in f, and gx1 as being x1 in g). Both references point to the "new Z()" result.

If you change fx1.a ("fx1.a = 21;"), then gx1.a will also change, since it's the same place in memory (namely inside the Z object that was created with "new Z()").

If you try to change gx1 ("gx1 = new Z();"), then this change does not propagate to fx1. What happens here is that a second Z object is created, and gx1 now refers to that 2nd object. fx1 has not changed, and still points to the first Z object. Since fx1 and gx1 now point to different Z objects, a change in fx1.a will not show in gx1.a and vice versa.

The difference here is changing a field in a common object versus changing references (variables themselves).

change_object_versus_change_reference.png.2f0e64464baeef8666a43007f7f26dce.png

fx1 and gx1 are references to an object (they contain one end-point of the line). Each box is a Z object, created by "new Z()".

"gx1.a=21" follows the line from gx1, finds "a" inside that box, and changes it to 21

"gx1 = new Z()" makes a new Z box, and put one end of the line into gx1 (overwriting whatever it pointed to before).

 

If "a" was a class reference too (eg of type Q), then "fx1.a = new Q()" would change fx1.a to point to the new Q box. That is visible to all variables that can access where fx1 points to.

 

In your original code you were changing gx1, and expecting that fx1 would change too (as in, fx1 would point to the second Z box), except there was some additional wrapping stuff there with a function call, to make it more confusing :P .

 

As for your solution, yes, returning the new state works nicely here.

6 hours ago, Alberth said:

If you try to change gx1 ("gx1 = new Z();"), then this change does not propagate to fx1. What happens here is that a second Z object is created, and gx1 now refers to that 2nd object. fx1 has not changed, and still points to the first Z object.

What if instead you assigned fx1 = new Z(). Would gx1 change, since it points to fx1?

 

6 hours ago, Alberth said:

In your original code you were changing gx1, and expecting that fx1 would change too (as in, fx1 would point to the second Z box), except there was some additional wrapping stuff there with a function call, to make it more confusing :P .

I thought this since I was passing a State instance between classes without calling new State(). Since the final State reference was not the same as the original, I am wondering if when passing objects a copy is made or new is called implicitly. Is either case true? Maybe you've explained this already and I just haven't understood.

 

What do you think about the way I am currently setting up State Transitions with the StateTransitionSets? Would you consider it poor design to create such classes to only ever do one thing once, just to make the State initialisation process more manageable?

If this is a bad way to do it, what would you recommend instead? I would rather not do it all in initialiseStates() going TransitionCondition by TransitionCondition, Transition by Transition, for every InputHandler State. It's also hard to see how I can initialise in a loop due to all the different types of States and varying number of TransitionConditions per Transition and Transitions per State.

8 hours ago, Seer said:

What if instead you assigned fx1 = new Z(). Would gx1 change, since it points to fx1?

No it wouldn't change gx1. It makes a third Z box, and put a link to it into fx1, discarding whatever was before in fx1. This "put a link" idea also happens when you assign one variable from another one, the links get copied into the new variable.

 

Variables never point to other variables, they only point to objects. If two variables point to the same object and you change the object, both variables will see the change.

For primitive types, (bool int float char) there is no object, nothing is shared between variables, the value is directly stored in the variable.

 

8 hours ago, Seer said:

I am wondering if when passing objects a copy is made or new is called implicitly. Is either case true? Maybe you've explained this already and I just haven't understood.

Yeah, this is one of those things that are hard to understand until all the pieces of the puzzle fall at the right spot, and then you find it hard to understand how it is not understandable since it is so simple. Unfortunately, it makes it very difficult to explain from both sides.

In general, "new" is never called implicitly in Java. The only objects in the program that exist are those that you "new" yourself. (This is not entirely true, sometimes Java itself or a data container makes more objects, but those are quite hidden from you. For example, "for (X x : x_es) { ... }" makes an iterator object, and "map<>" makes internal nodes for storage of its entries, and there are more, but these don't affect your own data.)

 

What you may be missing is when you call a function, its parameters become local variables to that function and they are initialized with the arguments that you provide to the call:


public static void g(Y x1, Y x2) {
  x1 = x2;
  return new Y();
}


Y x1 = ... ;
Y x2 = ... ;

Y x3 = g(x1, x2);

x2.a = ... ;

If you would replace the "g(x1, x2)" call by equivalent code, you would get something like


Y x1 = ... ;
Y x2 = ... ;

Y x3; // Already make room for storing the result.

// Start of replacement code of g(Y x1, Y x2).
// Create new variables g_x1 and g_x2, representing local values
// x1 and x2 inside g, and initialize them from the arguments.
Y g_x1 = x1;
Y g_x2 = x2;

// Statements of g function. Note how it uses local variable names of g.
g_x1 = g_x2;
Y g_ret_value = new Y(); // Compute return value.

x3 = g_ret_value; // Copy link to returned object into caller context.
// End of replacement code of g(Y x1, Y x2);

x2.a = ... ;

A function call is not much more than creating a new set of variables representing the parameters, initializing them from the caller context, executing the code and then returning to the previous context by assigning the return value.

The code in the g function is quite isolated from the caller, the only ways to affect data of the caller is either to return a value (x3 in the caller context has changed), or by changing objects that are shared between the caller and the g function (not done here, but g_x1 and x1 share an object, so if the g function changed that object through g_x1, the caller would find that change after the call through x1.

This expansion is a simplistic example to the kind of things that a Java compiler does to speed things up. It rewrites code to something equivalent but faster. Getting rid of a function call is one of the simpler cases there.

 

9 hours ago, Seer said:

What do you think about the way I am currently setting up State Transitions with the StateTransitionSets? Would you consider it poor design to create such classes to only ever do one thing once, just to make the State initialisation process more manageable?

As for your states, I had a look, but got quite lost. Things are spread over too many files to make sense in a short amount of time (Java tends to do this, as it needs a new file for each class.)

In general, the biggest aim in code is to make things understandable to yourself and fellow human beings. State machines aren't that complicated in processing, so the biggest problem is usually how to initialize them in a readable and maintainable way. If that takes some additional code, I would not worry about it, readability is by far more important than the number of times that you use a class (as long as it's > 0).

 

9 hours ago, Seer said:

I would rather not do it all in initialiseStates() going TransitionCondition by TransitionCondition, Transition by Transition, for every InputHandler State. It's also hard to see how I can initialise in a loop due to all the different types of States and varying number of TransitionConditions per Transition and Transitions per State.

Somewhere you have to express the content of each state, and each transition. You can move that information around to another place, or change its shape, or express it at a higher level, but you cannot make it disappear.

The bottom solution, just list everything literally, always works. It's just a lot of dumb work. Trying to be smarter had only limited success for me. My currently preferred solution would be an extreme form of "move+change shape". Move the state machine definition outside Java, and generate Java code from that definition. Obviously, my idea isn't new, you can find these things on the Internet with magic words like "code generator state machine Java" or so. The biggest problem there is likely lack of documentation, and not being what you need. There are a lot of small variations between state machines, finding one that fits your problem is not easy.
 

My solution has a huge initial cost (bigger than anything that you want to spend on this for the first few years, I think). Since you have already done most of the work in your current approach anyway, I'd suggest just continue what you have decided, spend a lot of lines on listing all the content of states and transitions, test it, and move on to more interesting parts of the game.

This topic is closed to new replies.

Advertisement