Reputation: 305
I am making a game in Matlab (a sort of space invaders shooter) The graphics are all on one figure and just generated with multiple plots, I am aware that matlab is not meant for such things, which makes it particularly fun to show classmates. for the key inputs I am currently using:
f = figure(1)
set(f, 'KeyPressFcn', @(x,y)disp(get(f,'CurrentCharacter')));
a = get(f,'CurrentCharacter');
if a=='w' %move up..
if a=='d' %move right..
if a=='2' %switch to weapon 2..
This works perfectly but each time a key is pressed it is displayed on the console, causing unwanted lag.
I tried:
f = figure(1)
set(f, 'KeyPressFcn');
a = get(f,'CurrentCharacter');
but it displays "string -or- function handle -or- cell array" each time a key is pressed.
How do I identify a Key Press and store it into a variable without having anything pop up on the console? I would also prefer not having to make second function, as everything is kept cleanly in one .m file at the moment.
Thanks!
Upvotes: 0
Views: 4792
Reputation: 943
As the author of a few popular games on MATLAB Central, I can give you an idea of how to do this properly in MATLAB. I cannot guarantee my way is not the most optimal, but this is the best solution I have come up with after thinking about this problem for years. Firstly, there are a few principles that I usually follow when writing up games:
Use 'CurrentKey' in favour of 'CurrentCharacter', since the former recognizes more keys that are not classified as 'characters'.
You may also need a 'KeyReleaseFcn' since this is a shoot'em'up. Usually you would like the aircraft to keep moving when you hold down a key, and stop when you release the key; you don't want to press and release a key repeatedly in order for the aircraft to keep moving. How it works is: when the player presses down 'w', we invoke KeyPressedFcn once, in which we set a flag variable 'w_status' to true; when the player releases 'w', invoke KeyReleasedFcn once, and set the flag to flase. In the main loop of the game, repeatedly check if 'w_status' is true. If so, then move aircraft up by one step, otherwise don't update the position.
If you want to keep everything in one file, try to implement KeyPressFcn and KeyReleaseFcn as nested functions. It is better than squeezing all the code in to a one-liner.
Avoid hard coded key names in if-else clauses. You may later want to allow the user to reassign the keys, so it is a better idea to keep the key names in an array which you can modify.
So the entire game will look like this:
function MainGame()
KeyStatus = false(1,6); % Suppose you are using 6 keys in the game
KeyNames = {'w', 'a','s', 'd', 'j', 'k'};
KEY.UP = 1;
KEY.DOWN = 2;
KEY.LEFT = 3;
KEY.RIGHT = 4;
KEY.BULLET = 5;
KEY.BOMB = 6;
...
gameWin = figure(..., 'KeyPressFcn', @MyKeyDown, 'KeyReleaseFcn', @MyKeyUp)
...
% Main game loop
while GameNotOver
if KeyStatus(KEY.UP) % If left key is pressed
player.y = player.y - ystep;
end
if KeyStatus(KEY.LEFT) % If left key is pressed
player.x = player.x - xstep;
end
if KeyStatus(KEY.RIGHT) % If left key is pressed
%..
end
%...
end
% Nested callbacks...
function MyKeyDown(hObject, event, handles)
key = get(hObject,'CurrentKey');
% e.g., If 'd' and 'j' are already held down, and key == 's'is
% pressed now
% then KeyStatus == [0, 0, 0, 1, 1, 0] initially
% strcmp(key, KeyNames) -> [0, 0, 1, 0, 0, 0, 0]
% strcmp(key, KeyNames) | KeyStatus -> [0, 0, 1, 1, 1, 0]
KeyStatus = (strcmp(key, KeyNames) | KeyStatus);
end
function MyKeyUp(hObject, event, handles)
key = get(hObject,'CurrentKey');
% e.g., If 'd', 'j' and 's' are already held down, and key == 's'is
% released now
% then KeyStatus == [0, 0, 1, 1, 1, 0] initially
% strcmp(key, KeyNames) -> [0, 0, 1, 0, 0, 0]
% ~strcmp(key, KeyNames) -> [1, 1, 0, 1, 1, 1]
% ~strcmp(key, KeyNames) & KeyStatus -> [0, 0, 0, 1, 1, 0]
KeyStatus = (~strcmp(key, KeyNames) & KeyStatus);
end
end
Notice that the callbacks make use of a 'dictionary' KeyNames to eliminate the need of any if-else clauses. This way, regardless of the number of keys to use and what they are actually used for, these two functions can be plugged into ANY game without any modification.
To see how this idea works in real life, you can check out my games on MATLAB Central:
http://www.mathworks.com/matlabcentral/fileexchange/authors/111235
There is one space shooting game 'Stellaria' there. However, it was written back in the day when I did not know about nested functions, so the code was split into many small function files and might be very hard to read. Please Use with caution.
Upvotes: 5
Reputation: 11812
For this purpose, it would be easier to write a full function that deal with the keystroke and assign the handle to that function to the figure callback.
The following minimal example react to any key pressed when the figure is active and just for the purpose of the example display the character detected in the gui, but there is no console output.
function h = guigame
h.fig = figure(1) ;
h.txtDebug = uicontrol('Style','text','String','init') ;
set(h.fig, 'KeyPressFcn', @processinput);
guidata(h.fig,h)
function processinput(hobj,evt)
h = guidata(hobj) ;
a = get(h.fig,'CurrentCharacter');
%// just to show that you captured the right key
%// note the absence of console output
set(h.txtDebug,'String',a)
%// now do what you want with your captured key
%// if a=='w' %move up..
%// if a=='d' %move right..
%// if a=='2' %switch to weapon 2..
Note that polling the figure CurrentCharacter
is the way closest to your initial code, but it is not foolproof in case the user start to play with the special keys (ctrl
/alt
/shift
and so on).
If you want to be able to handle these case, in the processinput
function just use the evt
data. If I set a debug point at the beginning and look at evt
I get:
K>> evt
evt =
Character: 'f'
Modifier: {1x0 cell}
Key: 'f'
So your function already has everything it needs to know without calling a = get(f,'CurrentCharacter');
, and you can also handle special cases.
although for a small game you may not need to handle every single key combination, it is good to know how to do it ;)
Upvotes: 0
Reputation: 14371
You are calling set
without a third argument. As you aren't using the KeyPressFcn
callback, you don't need to set anything. Just try
f = figure(1);
a = get(f,'CurrentCharacter');
This will most likely give you a = ''
as output. This is because you didn't press any button yet. If you click on the figure, press any button and call
b = get(f,'CurrentCharacter');
you will get the button you pressed as result. You can therefore call this in a loop and do the corresponding actions. Note that you will get the same button as result until a new button is pressed.
To prevent MATLAB from jumping to the console each time a button is pressed, you could set the callback to a NOP function.
The best way to handle this problem is of course to use the KeyPressFcn
to control your software instead of overriding / ignoring it.
Upvotes: 1