I'm making the game Snake. It's all working but if you press buttons too quickly it has an unfortunate behavior. Before I started attacking this issue, the code handled a single key press per loop iteration, and if you pressed a key multiple times in a row, it took that many loops before you could change direction.
My initial change was to read all the keys that have been pressed since the last game loop, and only apply the last one. This worked, but really, if I press two keys, I want them both to be applied. What I don't want is for me to be stuck going right because I pressed right three times.
So my next approach was to peek at the next inputted character and see if it's the same as the last one. If so, read it out of the stream, and try again.
My understanding is that Console.KeyAvailable returns true if there is any unread data in the input stream, and Console.In.Peek() returns the first character of data in the input stream, or else hangs until there is data there.
Here's my current code:
private static void UpdateDirection()
{
ConsoleKey key = ConsoleKey.Escape; // dummy key
Dir curDir = s_snakeDir;
lock (s_consoleLock)
{
if (Console.KeyAvailable)
{
key = Console.ReadKey(true).Key;
// ignore multiple key presses of the same direction
while (Console.KeyAvailable)
{
int next = Console.In.Peek();
if (next != (int)key)
{
break;
}
// read and ignore next character
Console.ReadKey(true);
}
}
switch (key)
{
// do stuff...
}
}
}
If I press two keys on the same loop, the game stalls. What happens (as far as I can tell) is it gets to int next = Console.In.Peek(); and waits for the next character.
What I don't understand is why Console.In.Peek() needs to wait. If Console.KeyAvailable is true, then there should be a character in Console.In's stream.
EDIT
I believe I've figured out the issue. Console.In.Peek() is looking for characters, not key presses, and the arrow buttons don't have characters associated with them. So my next question is how to do what I want to do.
I also made a snake multiplayer game (source code here) and i had exact the same poblem:
If somebody presses a key for a while then the direction is set very delayed...
I solved this problem with a keyBuffer and a keyCheck thread:
List<ConsoleKeyInfo> keyBuffer = new List<ConsoleKeyInfo>();
new Thread(() =>
{
while(true)
{
if (Console.KeyAvailable)
{
ConsoleKeyInfo key = Console.ReadKey(true);
if (keyBuffer.Count > 0)
{
if (keyBuffer[keyBuffer.Count - 1] != key)
keyBuffer.Add(key);
}
else
keyBuffer.Add(key);
}
Thread.Sleep(1);
}
}).Start();
while(true)
{
if (keyBuffer.Count>0)
{
switch (keyBuffer[0].Key)
{
//SwitchSomeStuff
}
keyBuffer.RemoveAt(0);
}
//PlayGame
}
There's no conceivable point in not using Console.ReadKey(). You are only interested in the position of the keys to control the snake, not what letter they produce. WASD is ZQSD in France for example. And you get a use out of the cursor keys, you can't do that with Console.Read()
Related
the problem is that when I click on one object, all other objects (with the same name as that object) make a sound.
This is the code I'm using:
void OnMouseOver()
{
if (Input.GetMouseButton(0))
{
if (this.gameObject.name == "door")
{
anim.SetBool("open", !(anim.GetBool("open")));
SoundManagerScript.PlaySound("door");
}
if (this.gameObject.name == "window")
{
anim.SetBool("open", !(anim.GetBool("open")));
SoundManagerScript.PlaySound("window");
}
}
}
How to prevent playing a sound for all objects?
Converting my comment to an answer. The problem in the script is related to user input.
Input.GetMouseButton returns whether the given mouse button is held down. Therefore, your if statement is true while mouse is held down. This is the reason why you have some many sounds playing continuously because you Input.GetMouseButton returns true on several frames during the interaction .
Instead Input.GetMouseButtonDown should be used. Because it returns true on the frame the user pressed the given mouse button. It will return false when mouse is held down. Therefore, it will return true only once when mouse is pressed.
you could always try and set up an array of these objects that contain the sound and then turn them off all at the same time.
public gameObject[] itemsWithSound;
for(int i = 0; i < itemsWithSound.length; i++)
{
itemsWithSound.SoundManagerScript.StopSound();
}
so I'm creating an XNA game, that consists of a spaceship, shooting bullets around, at different enemy spaceships that carry around "strings". It's essentially a spelling game, where I have to spell out the word by hitting the spaceships with the right letters.
Anyway, my problem is that, I've currently got two "if" statements in order of shooting a bullet, and it'd be some horrible programming if I did this for the rest of the 25 letter keys on the keyboard. Yes, I do need every single "letter" key on the keyboard, as it will be a spelling game. Here is an example of code I've got for one of the bullets.
What I'm essentially doing here is if "Fired" boolean is false, and the appropriate key is pressed, I am positioning a rectangle in the position of the gun and setting "Fired" to true, to let the bullet loose. An then obviously whenever it hits the top of the screen, I set the fired back to false, in order of a reloading concept.
if (MyKeys.IsKeyDown(Keys.Q) && OldKeys.IsKeyUp(Keys.Q) && Fired[0] == false)
{
CurrentChar = "Q";
Shot[0].X = SpaceStationGun.X;
Shot[0].Y = SpaceStationGun.Y;
Fired[0] = true;
}
if (Fired[0] == true)
{
Shot[0].Y -= SHOT_SPEED;
if (Shot[0].Y < 0 - SHOT_HEIGHT)
{
Fired[0] = false;
}
}
Now the question is, how would I loop this for the rest of the 25 letter keys on the keyboard, and still hold the functionality?
Keyboard.GetState().GetPressedKeys() gives you a collection of Keys that were pressed during the duration of its state.
I do not see how your MyKeys and OldKeys are being updated but I will assume you can use the following
var keysPressed = MyKeys.GetPressedKeys()
.Where(k => !OldKeys.GetPressedKeys().Contains(k));
foreach(var key in keysPressed)
{
// Do your logic
}
OldKeys = MyKeys;
MyKeys = Keyboard.GetState();
When pressing two keys and then pressing a third key, all without releasing, Input.GetKeyDown does not know that the third key has been pressed.
Here is my code:
public class Keys
{
internal bool leftPressed = false;
internal bool rightPressed = false;
internal bool upPressed = false;
internal bool downPressed = false;
}
public Keys keys = new Keys();
void Start () {
}
void Update ()
{
updateButton(KeyCode.LeftArrow);
updateButton(KeyCode.RightArrow);
updateButton(KeyCode.UpArrow);
updateButton(KeyCode.DownArrow);
}
void updateButton(KeyCode key)
{
if (Input.GetKeyDown(key))
{
if (key == KeyCode.LeftArrow)
keys.leftPressed = true;
if (key == KeyCode.RightArrow)
keys.rightPressed = true;
if (key == KeyCode.UpArrow)
keys.upPressed = true;
if (key == KeyCode.DownArrow)
keys.downPressed = true;
}
else if (Input.GetKeyUp(key))
{
if (key == KeyCode.LeftArrow)
keys.leftPressed = false;
if (key == KeyCode.RightArrow)
keys.rightPressed = false;
if (key == KeyCode.UpArrow)
keys.upPressed = false;
if (key == KeyCode.DownArrow)
keys.downPressed = false;
}
}
All I wanted to do was have a player class move a gameobject when a key was pressed down. If both directions were pressed they would cancel each other out and set the player's velocity to 0.
Hitting left and right at the same time does so, but trying to press up or down doesn't change the up or downs value at that point. It's as though Input.GetKeyDown only contained 2 values and not the whole keyboard keys states?
Note: even pressing two random keys on the keyboard would stop left, down, up or right from changing values.
Does anyone have a suggestion or a fix to my code?
Thanks,
Unless I'm missing something it seems like you would be better off using the built in axes system.
Also your keyboard might not support more than two keys down at once.
Also GetKeyDown does not return if the key is down it returns if it was pushed down last frame: https://docs.unity3d.com/ScriptReference/Input.GetKeyDown.html
You want Input.GetKey to get the keys current state if you don't want to use the axes system.
void Start()
{
StartCoroutine(crMoveIt());
}
private IEnumerator crMoveIt()
{
while(true) // have a bool var for this
{
if(Input.GetKeyDown(KeyCode.A))
{
// Move object here
}
yield return null;
}
}
Where you want to move the object, see this
https://docs.unity3d.com/ScriptReference/Vector3.MoveTowards.html
Or if you're using a rigidbody please read
https://docs.unity3d.com/ScriptReference/Rigidbody.html
Instead of passing in a keycode to check each frame, just have a coroutine handle the input and then call something after that each frame. A coroutine will do that same as an Update, but you can have several of these running versus just one Update() method.
I would of wrote more code for the movement of an object, but you provided no references to any GameObject in your example. I answered a question not to long ago that shows how to move an object a different way that does not use Vector3.MoveTowards() here
Unity; Random Object Movement
Hope this helps. Edited, forgot something. =)
When running the following code in a loop (var enterKey = Keyboard.GetState().IsKeyDown(Keys.Enter);) pushing the enter key returns true even after I've released the key.
Any ideas as to what I'm doing wrong?
Try checking like this:
KeyboardState newState;
public void Update(...)
{
newState = Keyboard.GetState();
if(newState.IsKeyDown(Keys.Enter))
{
*do what you want here*
}
}
This way, on each update you are updating current keyboard state (which keys are pressed), so you can check for different key presses in each frame.
EDIT:
You are likely to want to check for a single click, so instead of asking 2 question, here is a "bonus" to your question. Code is similar:
KeyboardState newState, oldState;
public void Update(...)
{
//gets keyboard state for this single frame
newState = Keyboard.GetState();
//checks if enter is down
if(newState.IsKeyDown(Keys.Enter))
{
*do what you want here*
}
// checks if enter is clicked
// if statement asks if in this frame, enter button is down
// AND if enter button was not down in the last frame
// this way, if statement below will fire only on each click
if(newState.IsKeyDown(Keys.Enter) && oldState.IsKeyUp(Keys.Enter))
{
*do what you want here*
}
//set old state to new state, so the next frame knows
//what was happening in frame before that one
oldState = newState;
}
I suspect the state of the keyboard only gets refreshed between Update(...) cycles (or a close equivalent thereof) so looping is not going to do anything useful.
To detect changes in keyboard state, you'll want to compare keyboard states between successive calls to Update(...).
Restarting my computer fixed the problem, it must have been a bug outside of the code.
I'm trying to make my first game, a console tetris.
I have a class Block, that contains x and y integers. Then I have a class Piece : List<Block>, and a class Pieces : List<Piece>.
I can already randomly generate pieces, and make them fall one row per second. I still didn't get to collisions detection, but I think that I already know how to work it out later.
The problem is that I don't know how to control the pieces. I've read a bit about keyboard hooking and checked some tetris tutorials, but most of them are for windows forms, which really simplifies events handling and the such.
So... Could you please point me the beginning of the path to controlling the pieces on a console? Thanks!
public class Program
{
static void Main(string[] args)
{
const int limite = 60;
Piezas listaDePiezas = new Piezas(); //list of pieces
bool gameOver = false;
Pieza pieza; //piece
Console.CursorVisible = false;
while (gameOver != true)
{
pieza = CrearPieza(); //Cretes a piece
if (HayColision(listaDePiezas, pieza) == true) //if there's a collition
{
gameOver = true;
break;
}
else
listaDePiezas.Add(pieza); //The piece is added to the list of pieces
while (true) //This is where the piece falls. I know that I shouldn't use a sleep. I'll take care of that later
{
Thread.Sleep(1000);
pieza.Bajar(); //Drop the piece one row.
Dibujar(listaDePiezas); //Redraws the gameplay enviroment.
}
}
}
What you are looking for is non-blocking console input.
Here is an example:
http://www.dutton.me.uk/2009/02/24/non-blocking-keyboard-input-in-c/
Basically, you would check Console.KeyAvailable in your while loop and then move the piece according to what key was pressed.
if (Console.KeyAvailable)
{
ConsoleKeyInfo cki = Console.ReadKey();
switch (cki.Key)
{
case ConsoleKey.UpArrow:
// not used in tetris game?
break;
case ConsoleKey.DownArrow:
// drop piece
break;
case ConsoleKey.LeftArrow:
// move piece left
break;
case ConsoleKey.RightArrow:
// move piece right
break;
}
}
You could use a Low-Level Keyboard Hook as shown here