Welcome Guest [Log In] [Register]
Welcome to the Chrome Conflict Developer Forum! ~Admin DJ

Check the active topics here! And the staff list here! ~Alex

You can join us on our Discord server here! ~Spiral

New or Lost? Click here!

Welcome to The Steel Sentinels 2 Development Forum. We hope you enjoy your visit.


You're currently viewing our forum as a guest. This means you are limited to certain areas of the board and there are some features you can't use. If you join our community, you'll be able to access member-only sections, and use many member-only features such as posting replies, sending personal messages, and voting in polls. Registration is simple, fast, and completely free.


Join our community!

Username:   Password:
Add Reply
Incomprehensible ramblings
Topic Started: 14th September 2016 - 02:47 AM (67 Views)
Idratex
Member Avatar
Idratex
Interaction devices. Logical and physical. Want to abstract abstract abstract away.

POINT (x,y) INPUT:

  • Indicate (considering the thing we are indicating; 'mouse hover')
  • Select (commit the thing we are indicating; 'mouse click')
  • Hold (initiate some non-instantaneous action; 'mouse drag')
  • Release (end said action)


Grrrr. We just have set buttons, that are usually the same across all devices. It's not rocket science though - if you want to support all hypothetical interfaces, that could be a lot of work.

Ok, hold on a sec. We can sample from: mouse position (x,y) and button (whether keyboard or mouse) position: down / up.

So this suggests a better paradigm; sampling the mouse (x,y) position gives us a logical point signal. And everything else is a button, with press and release events. No?

What about scrolling? This is a different mode to both of the others. We can scroll up n steps or down n steps. So the type of "scroll" could be said to be Signed Integer.

Summarising:
Code:
 

type Input.Point = float * float (* you can't click outside of the window and have it registered as a click, so might as well be normalised float?? *)
type Input.Button = Up | Down
type Input.Scroll = Int


We could even account for touch devices, ie by type Input.Path = (* path / curve data structure *)

Then, if you really wanted, you could use the letters 'j' and 'k' on your keyboard instead of your left and right mouse buttons! Since we have lots of input sources that we normally consider quite different, but are functionally the same, we have disposed of the constraining distinction and got a more versatile interface as a result. After all, the type captures precisely what sort of information the application needs; MS Paint doesn't strictly require a mouse per se, it just requires a) a constant source of positions (where the ink should go) and b) a "tool" button ie put the pen on the page, now take it off. But keyboard keys are functionally identical to mouse buttons i.e. both a keyboard key and a mouse button are either "up" or "down" at any one time.

This all seems connected to Functional Reactive Programming.

Anyway... so let's say we have UI with various windows, controls etc. Let's adopt the familiar paradigm whereby there is only one control active at one time for text input, and possibly another for other inputs (e.g. the "submit" button). If we have a point (x,y), then we can indicate / select the thing immediately below the point to make it active. So we immediately have a logical analogue of a single-button mouse:

Code:
 

where_posn : Input.Point
select_posn : Input.Button


If we have a linear order sort of thing on the controls, then we can step through them with appropriately provided forward / backward buttons. A concrete example of which could be Tab, Shift+Tab. Or, if we can decide which is the next control in each of the four compass directions, then we could use directional buttons to navigate instead, indicating but not selecting each control we step over. Then, when the destination is reached, a "select indicated" logical button could be employed.

Code:
 

indicate_up : Input.Button
indicate_down : Input.Button
indicate_left : Input.Button
indicate_right : Input.Button
select_indicated : Input.Button


Then, when we're in an area that is too small to display its content, we could have two logical scrolls:
Code:
 

scroll_hor : Input.Scroll
scroll_ver : Input.Scroll


What if we want to allow panning the view arbitrarily rather than scrolling by an integral number of lines? One way is via four compass directions:

Code:
 

pan_up : Input.Button
pan_down : Input.Button
(* etc *)


Or, we could let the user drag the view around with the mouse -- or, from our logical, device-independent point of view, have the panning behaviour based on a logical Point signal / device thingy:

Code:
 

pan_where : Input.Point
panning : Input.Button


Now we can easily bind RMB to `panning`, for example.

There's a concept in software engineering of "incidental complexity", that is, complexity that is caused by the tools and environment you're working with rather than your actual problem. I would like to abbreviate this as "fluff"; stuff that could well be completely different depending on your language or tools. The "non-fluff", sort of, core logic, would intuitively not depend on such things, as the software is a means to that end, not an end in itself, and since there's only one end in mind, it would not vary with those other things.

Consider the following. SS is a platform game involving players being able to walk and jump around and shoot each other. One of the "fluff"s I have to deal with is using my external windowing system. I'm working in Java and using GLFW for this, and I need to translate from one language level (the language of (positional / textual) key (press / release / repeat)s, mouse (press / move / release)s) to -- well, something more domain-specific, for example, "move left" or "shoot".

Intuitively, some would say naively, I hit upon the following:

Code:
 
interface PlayerInput {
void goLeft(); // Called on 'A' key pressed
void goRight(); // Called on 'D' key pressed
void stop(); // Called on 'S' key pressed
void prepareJump(); // Called on 'W' press
void performJump(); // Called on 'W' release
void beginShoot(); // Called on left mouse press
void endShoot(); // Called on left mouse release
}


At first sight, at least, this appears to be a perfectly good solution to the particular problem identified; I wanted to translate between these concepts, and now all I need to do is call the appropriate method when I get the appropriate event.

However, "conventional" (read: learned, orthodox) wisdom suggests to me that there absolutely must be something terribly wrong with this code (after all, it's simple, and we must have complexity, for it to be right!). This may be correct, but I suspect it's just paranoid.

A literal reading of the code suggests a very obvious but unenlightening interpretation. You know - "goLeft() makes the player go left, goRight() makes ... etc". Well, that isn't quite true. First of all, goLeft(), as it is going to be used in this context, what with the mapping from the primitive external input language and so on - represents only the player's intention to go left, not a guarantee that they will do so. Presumably, if the player is right next to a wall on their left then goLeft() will not make the player go left. What we are doing is taking physical buttons (or, more precisely, buttons OR individual letters OR special key combinations), which may be bound to domain-specific inputs in whatever manner the player feels is most natural, and mapping them to those domain-specific inputs, which are still just that - inputs - the highest level of the domain, and as such they represent intentions. (presumably - a game that let you do whatever you want whenever and wherever you want would equate the intentions with the effects, although it might not be very interesting). We could think of it as being a "logical" controller, analogous to the real-world "physical" controller, which is more general.

Even with that cleared up, however, there is still more to this than meets the eye. On the surface, we may say it does this or that simple thing, but in reality we may have, by writing such code in such a language, unwittingly signed several subtle "contracts" with the compiler or system, that render such a view approximate at best, and misleading at worst.

This is only a matter of awareness. First of all, pretend for a moment that the methods are all called a, b, c, etc. Now what can we say? Not a great deal. But the fact that we can still say some things shows us that there was more going on than a naive linguistic assessment of what we wrote. I'll show you what I mean. For a start, in Java, void a() says: "whenever I have something with this interface, the current thread can branch to some subroutine called a(), and presumably return sometime later".

This has non-trivial implications for how we use this interface. In GLFW I poll the window system, each frame, for events, and process them in a single-threaded event loop, before doing rendering etc. If we consider this "context of use", then we immediately get a constraint on what implementations of PlayerInput are allowed to do: all of the methods must be fairly simple, quick operations that will not block the event loop, otherwise the UI will not be responsive and we could mess up the time steps for our rendering and world simulation.

Another way that "the use informs the implementation" is that these methods are called when keys are pressed, rather than while. So perhaps we could think of the interface (or, indeed, rewrite it) as follows:

Code:
 

interface PlayerInput {
void intendBeginGoLeft();
void intendBeginGoRight();
void intendStop();
void intendPrepareJump();
void intendPerformJump();
void intendBeginShoot();
void intendEndShoot();
}


Another point is that these are all "purely impure" methods - they are fully imperative subroutines, rather than functions. They take no explicit parameters and do not return a value, hence their semantics is entirely dependent on the time at which they are called. However, this is perfectly suitable for their purpose. Those are precisely the semantics of the player's intent to do something - it can be anything, at any time.

An important implication for implementors is this: this interface assumes the implementor has access to all of the context (entire world and game state, no less) required to handle the input. It could either have this information as private state, state in an enclosing outer class, or, more worryingly, access it via global variables or ServiceProviderSingleton sort of things. But note that the interface provides literally no information other than what action is requested; everything else is assumed implicit. Which is, again, perfectly suited to the task at hand - this is the entrypoint into the domain, which should be opaque from the view of the input-mapping code.

A further aspect is that this code specifies a compile-time structure. The methods are "baked in" to the interface. If we have a PlayerInput, we can always call any of those methods. Again, there is nothing wrong with this in this particular situation. However, as we shall see shortly, it is often easy to encode fundamentally dynamic (at runtime) domain concepts as static language constructs, which creates unnecessary complexity to handle.

Remember what I was saying earlier about input mapping? Most games let the user choose whichever keys or mouse buttons they want for domain (game) -specific inputs. This is commonly known as "keybinding". So the system maintains some sort of mapping between "physical" inputs (that which GLFW provides; positional key / key letter / key combination) and "logical" inputs (intendBeginGoLeft()). How could this be represented in our design here?

That's easy, you say - just a HashMap<PhysicalInput, --errrrmmm -- yes, we have just discovered another hidden contract that we signed. We chose to represent our logical inputs with methods in an interface, that seeming the natural thing to do at the time. However, now we want to treat them as first-class citizens, so to speak. What do we do? Well, in modern Java versions I think you can actually reference the method like so: bindings.put(Key.W, playerInput::intendPrepareJump); and I've no idea what the type looks like. But what about previous versions of Java, lacking in such a basic construct as the first-class function? :) We could use reflection. But that would be horribly ugly. Because the design was trying to tell us something, but we kept ignoring its advice.

What have we learned so far about this domain concept? Logical inputs are completely specified by what they are and when they occur (they can happen at any time). They must not do much computation at all (else our event loop will stall). They also need to handled based on the run-time keybinding preferences (i.e. the bindings could change between instances of the application, even during the game itself). What is all this telling us?? The answer is this. Logical inputs (as we have defined them) do not belong as code. They belong as data. EDIT: Not necessarily, but still interesting ideas.

Now, in a proper language, we could write

Code:
 

type PlayerIntent = BeginGoLeft | BeginGoRight | Stop | PrepareJump | PerformJump | BeginShoot | EndShoot


But in Java we have to be a bit more verbose
Code:
 

enum AvatarIntent {
BeginGoLeft, BeginGoRight, Stop,
PrepareJump, PerformJump,
BeginShoot, EndShoot
}


(ok, so there's not much difference :) but it's a pain for more complicated examples, such as type constructors that take arguments)

Now we have a HashMap<PhysicalInput, AvatarIntent> that can be configured at runtime. Now, since this is now data rather than code we must (eventually) re-bridge the gap so that things can actually happen. Again, in a proper language we could have something like

Code:
 

let handleIntent = function
| BeginGoLeft -> ...
| BeginGoRight -> ...
...etc


in Java we are stuck with if/else or switch case, if you have a "decision allergy" then you might have AvatarIntent as a superclass of its members and use dynamic dispatch etc...

What we did here was functionally equivalent to using reflection, just with better morals :)

NB: ommitted that which is normally bound to scrollwheel; ie NextWeapon, PrevWeapon. Also neglected Melee. This is naturally an unstable set that should be expected to change and grow over time.

** ADDENDUM **
In light of the stuff about 'interaction', I ought to rephrase the stuff here:

Left, right, stop and jump are all examples of Button-like behaviour. Shooting will ordinarily require a position to shoot at, so we could have

Code:
 

where_aim : Input.Point
shoot : Input.Button (* Up -> Down (aka 'a press') is BeginShoot, Down -> Up (aka 'release') is EndShoot *)

beginLeft : Input.Button (* active on Up -> Down transition (aka 'a press') *)
beginRight : Input.Button (* ditto *)
stop : Input.Button (* ditto *)
jump : Input.Button (* Up -> Down (aka 'a press') is PrepareJump, Down -> Up (aka 'release') is PerformJump


EDIT MODE

One of the things I need from the start is a way to create and adjust the game world. This could be accomplished via a separate level editing tool, but I feel it'd be simpler to just integrate it into the game proper; plus, that way, it'll be possible to switch back into "play" mode, play a bit, pause and switch to "edit" mode, adjust, and repeat.

Note that this feature is for development convenience; it needn't make it to the version that will be distributed to players.

This gives us more inputs to deal with. You don't edit by jumping, flying and shooting (at least, I hope not). Therefore we'll need another set of bindings for edit mode, mapping the same set of physical inputs, no longer onto control-my-avatar inputs, but to completely different editing intents.

Worse, we may well have to deal with modality. This is because, when playing, the player always controls a single player avatar with the same inputs. However, there may be many tools and modes within edit mode so the logical device to which a physical device is mapped could depend heavily on context! Dunno how to deal with this...

First, let's consider drawing and modifying terrain. It's basically the simple polyline that we're all familiar with from graphics applications. To place a vertex, we need a point (x,y), and the command "place a vertex here"; logical Point and Button inputs.

Editing (eg moving) or deleting a vertex requires ... well, obviously, a vertex. This applies to all objects on screen anyway... let's keep it fairly concrete for now though. So we'd have something like Input.Vertex. Hmmm...

An editable object may support various operations, depending on the type of the object. Most will have a position in space so will be able to be moved around - but not completely freely, as we don't want to move thing into solid objects etc. Some won't even have a position in that sense, for example the background.

Note that I have yet to mention "fluff" a second time.
Oh well...
Edited by Idratex, 14th September 2016 - 02:51 AM.
Offline Profile Quote Post Goto Top
 
MC Productions
Member Avatar

Please get this to Myrm so he can follow this and ACTUALLY DO CODING. It's actually quite amazing (And quite long too >_> ).

~Cruel
Offline Profile Quote Post Goto Top
 
Blackout
Member Avatar
Community Organizer
This inspires me. Thank you.

@Cruel as much as I seem to love beating Myrm over the head to code, he HAS done things that I may make a post here myself showing off, so maybe we should cut him some slack(ik, that's quite rich coming from me). I try to emphasizes the lack of development recently rather than as a whole.
Offline Profile Quote Post Goto Top
 
Pringles Man
Member Avatar
The Chief Designer of the Ilolan Republic

Code E>
Offline Profile Quote Post Goto Top
 
1 user reading this topic (1 Guest and 0 Anonymous)
« Previous Topic · Programmers' Hub · Next Topic »
Add Reply