Optimized keyboard controls for XNA game - xna-4.0

The code I am using for controlling the four-directional movement of the player's sprite in my 2D game is exhibiting some unwanted affection. I realize that this affection is because the if conditions that are first met will trump the later else ifs... So the directional affection my code shows now is: left > right > up > down.
What kind of affection I want is: the first direction pressed > the second direction pressed > the third direction pressed > the fourth direction pressed.
I also want it to remember what order the keypresses are in untill they're released.
Example:
I hold left, the sprite moves left.
I push up while still holding left, and the sprite immediately moves up.
I release up while still holding left, and the sprite resumes its movement left.
This memory should encompass all four directional keys so that the controls won't feel buggy if the user has "fat fingers".
This is the code I use for movement so far:
if (CurrentKeyboardState.IsKeyDown(Keys.Left) == true)
{
Speed.X = moveSpeed;
Direction.X = moveLeft;
}
else if (CurrentKeyboardState.IsKeyDown(Keys.Right) == true)
{
Speed.X = moveSpeed;
Direction.X = moveRight;
}
else if (CurrentKeyboardState.IsKeyDown(Keys.Up) == true)
{
Speed.Y = moveSpeed;
Direction.Y = moveUp;
}
else if (CurrentKeyboardState.IsKeyDown(Keys.Down) == true)
{
Speed.Y = moveSpeed;
Direction.Y = moveDown;
}
I am thinking I could use a List and just put the direction pressed (left, right, up, down) as strings into the list if it isn't already in the list, and then always check what the latest item in the list is to decide what directio to move. And of course remove the strings when the corresponding keys are released. Would this be a good way of solving it?
Here is my attempt on this:
if (currentKeyboardState.IsKeyDown(Keys.Left))
{
if (!keyDownList.Contains("left"))
{
keyDownList.Add("left");
System.Diagnostics.Debug.WriteLine("left inserted");
}
}
else if (oldKeyboardState.IsKeyDown(Keys.Left))
{
keyDownList.Remove("left");
System.Diagnostics.Debug.WriteLine("left removed");
}
if (currentKeyboardState.IsKeyDown(Keys.Right))
{
if (!keyDownList.Contains("right"))
{
keyDownList.Add("right");
System.Diagnostics.Debug.WriteLine("right added");
}
}
else if (oldKeyboardState.IsKeyDown(Keys.Right))
{
keyDownList.Remove("right");
System.Diagnostics.Debug.WriteLine("right removed");
}
if (currentKeyboardState.IsKeyDown(Keys.Up))
{
if (!keyDownList.Contains("up"))
{
keyDownList.Add("up");
System.Diagnostics.Debug.WriteLine("up added");
}
}
else if (oldKeyboardState.IsKeyDown(Keys.Up))
{
keyDownList.Remove("up");
System.Diagnostics.Debug.WriteLine("up removed");
}
if (currentKeyboardState.IsKeyDown(Keys.Down))
{
if (!keyDownList.Contains("down"))
{
keyDownList.Add("down");
System.Diagnostics.Debug.WriteLine("down added");
}
}
else if (oldKeyboardState.IsKeyDown(Keys.Down))
{
keyDownList.Remove("down");
System.Diagnostics.Debug.WriteLine("down removed");
}
try
{
if (keyDownList[keyDownList.Count-1].Contains("left"))
{
//move left
speed.X = moveSpeed;
direction.X = moveLeft;
}
else if (keyDownList[keyDownList.Count-1].Contains("right"))
{
//move right
speed.X = moveSpeed;
direction.X = moveRight;
}
else if (keyDownList[keyDownList.Count-1].Contains("up"))
{
//move up
speed.Y = moveSpeed;
direction.Y = moveUp;
}
else if (keyDownList[keyDownList.Count-1].Contains("down"))
{
//move down
speed.Y = moveSpeed;
direction.Y = moveDown;
}
}
catch (Exception e)
{
}
I had some problems with it initially, but it seems to work fine now with the exception of it generating exceptions (A first chance exception of type 'System.ArgumentOutOfRangeException' occurred in mscorlib.dll) while my sprite is standing still. Any tips on how to stop that?
I'm not just looking for a solution that works, but for something durable and efficient that feels rock solid and professional, so discussion on the topic is more than welcome.

Durable, efficient, rock solid, etc. it may not be, but what you're saying with your if/else if block there is that you're only interested in one keystate per frame, when I don't think that's the case.
What happens if you try:
if (CurrentKeyboardState.IsKeyDown(Keys.Down) & !CurrentKeyboardState.IsKeyDown(Keys.Up))
{
Speed.Y = moveSpeed;
Direction.Y = moveDown;
}
if (CurrentKeyboardState.IsKeyDown(Keys.Up) & !CurrentKeyboardState.IsKeyDown(Keys.Down))
{
Speed.Y = moveSpeed;
Direction.Y = moveUp;
}
And repeat similar for left and right. By testing mutual exclusivity between opposing directions, you keep yourself from adding to and subtracting from direction in the same frame. Also, by using separate conditions instead of an if/elseif chain, you allow the possibility to process Left + Up in the same frame.

Related

how to use GetAxisRaw with 2d movement (beginner unity)

I am making a platformer game where you have to dodge spikes, and I tried to use the transform.position method, but it gave too many bugs. With rigidbodies(rb.addforce), it has acceleration, and I saw somewhere that you could use getaxisraw to do it. Is there any way that I could add this to my current script without deviating too much (still being able to use wasd and arrow keys)?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class playerscript : MonoBehaviour
{
public float movespeed = 0.01f;
public Rigidbody2D rb;
public bool isgrounded = true;
public float jumpheight = 500f;
public float level = 1;
// Start is called before the first frame update
void Start()
{
rb = this.gameObject.GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void FixedUpdate()
{
if (Input.GetKey(KeyCode.A) || (Input.GetKey(KeyCode.LeftArrow)))
{
rb.AddForce(-Vector2.right * movespeed);
}
if (Input.GetKey(KeyCode.W) && isgrounded || (Input.GetKey(KeyCode.UpArrow) && isgrounded))
{
rb.AddForce(transform.up * jumpheight);
isgrounded = false;
}
if (Input.GetKey(KeyCode.D)||(Input.GetKey(KeyCode.RightArrow)))
{
rb.AddForce(Vector2.right * movespeed);
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "enemy")
{
Debug.Log("hio");
if (level == 1)
{
Debug.Log("resetpos");
transform.position = new Vector3((float)-11.343, (float)-0.49, 0);
}
}
}
private void OnCollisionStay2D(Collision2D collision)
{
if (collision.gameObject.tag == "ground")
{
isgrounded = true;
}
}
}
From https://docs.unity3d.com/ScriptReference/Input.GetAxisRaw.html, I think that Input.GetAxisRaw("Horizontal") will return -1 if the user presses in left or a, 0 if the user is not pressing left or right or a or d, and 1 if the user presses right or d. Similarly, this also occurs for Input.GetAxisRaw("Vertical"). I think it will return -1 if the user wants to go down, 0 if the user is not pressing up or down, and 1 if the user wants to go up.
In FixedUpdate(), you can get Input.GetAxisRaw("Horizontal") to get whether they want to move right or left, and Input.GetAxisRaw("Vertical") to get whether they want to move down or up. Then, you can handle it by moving the character appropriately.
For example, you can do this:
void FixedUpdate()
{
if (Input.GetAxisRaw("Horizontal") == -1)
{
// Code to move left
}
else if (Input.GetAxisRaw("Horizontal") == 1) {
// Code to move right
}
if (Input.GetAxisRaw("Vertical") == -1)
{
// Code to move down or squat
}
else if (Input.GetAxisRaw("Vertical") == 1)
{
// Code to move up or jump
}
}
Please excuse me if I made any C# syntax errors.

RayCast not working as expected, help! (Unity 3D)

Okay so I'm making a photography game where when you 'take a photo', Unity sends a few raycasts forward to check if certain tagged items are in the photo (all within the cameras FOV). My problem is, this seems to work intermittently! Sometimes it finds the tagged objects, other times it will be right in front of the view yet it will miss it completely! Can anyone advise about what I'm doing wrong?
public static Transform target;
public static GameObject[] targetName;
public static float length = 250f;
public static Transform thisObject;
// Start is called before the first frame update
void Start()
{
thisObject = GameObject.Find("Main Camera").GetComponent<Transform>();
//target = GameObject.FindGameObjectWithTag("Trees").transform;
}
// Update is called once per frame
void Update()
{
//InFront();
//HasLineOfSight("Trees");
}
public static bool InFront(Transform target1)
{
Vector3 directionToTarget = thisObject.position - target1.position;
float angleOnXAxis = Vector3.Angle(thisObject.right, directionToTarget);
float angleOnYAxis = Vector3.Angle(thisObject.up, directionToTarget);
//Debug.Log(angleOnYAxis);
if (Mathf.Abs(angleOnXAxis) < 130 && Mathf.Abs(angleOnXAxis) > 50
&& Mathf.Abs(angleOnYAxis) < 115 && Mathf.Abs(angleOnYAxis) > 62)
{
//Debug.DrawLine(transform.position, target.position, Color.green);
return true;
}
return false;
}
public static bool HasLineOfSight(string objectTag)
{
RaycastHit hit;
Vector3 direction = target.position - thisObject.position;
//Debug.Log(direction);
if (Physics.Raycast(thisObject.position, direction, out hit, length))
{
if (hit.transform.tag == objectTag)
{
Debug.DrawRay(thisObject.position, direction * 0.96f, Color.red);
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
public static GameObject SortObjects(string objectTag)
{
targetName = GameObject.FindGameObjectsWithTag(objectTag);
GameObject closestObject = null;
for (int i = 0; i < targetName.Length; i++)
{
if (Vector3.Distance(thisObject.position,
targetName[i].transform.position) <= length)
{
if (InFront(targetName[i].transform))
{
if (closestObject == null)
{
closestObject = targetName[i];
}
else
{
if (Vector3.Distance(targetName[i].transform.position, thisObject.position) <= Vector3.Distance(closestObject.transform.position, thisObject.position))
{
closestObject = targetName[i];
}
}
}
}
}
return closestObject;
}
public static bool ObjectCheck(string objectTag)
{
//Debug.Log(SortObjects(objectTag));
if (SortObjects(objectTag) != null)
{
target = SortObjects(objectTag).transform;
//Debug.Log(target);
if (InFront(target) && HasLineOfSight(objectTag))
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
I'm essentially calling ObjectCheck() with the tag I want to check for to get the closest, visible, object with that tag. What is wrong with this code??
In your script, only the closest object to the main camera gets checked. SortObjects() determines the closest tagged object, and then you only handle that single object in ObjectCheck(). - That object might be obstructed by something else, so the method returns false. And other tagged objects that are actually visible, are not picked up this way...
So, you could rename and change your SortObjects() function to check for both conditions right in the loop (InFront(target) && HasLineOfSight(objectTag)), and filter the objects out right in there, since only those objects are of interest.
Also, your HasLineOfSight() method checks the tag of the hit object, but what you probably wanted to do, is to check if the raycast actually hits that exact object. So it should instead compare the hit's gameObject to the target's gameObject, ignoring the tag, since a correct tag alone isn't enough. (Side note: it would make sense to place all "photographable objects" on a "photo layer", and set the layer mask in the Physics.Raycast() call accordingly, it's more efficient that way in larger scenes.)
The way the angles are calculated in the InFront() method is probably causing issues, because the direction vector to the target is really in 3D. To calculate the angles, you could try to use Vector3.Project() or Vector3.ProjectOnPlane(), but that will also be problematic, because of perspective camera issues.
This check is strongly related to the topic of "frustum culling", a technique usually used for rendering. But it's similar to what you need, to filter out all the (possibly) visible objects in the camera's field of view (frustum culling doesn't handle obstruction, it is just a geometric check to see if a point lies within the camera's frustum space). See:
https://en.wikipedia.org/wiki/Viewing_frustum
https://en.wikipedia.org/wiki/Hidden-surface_determination#Viewing-
http://www.lighthouse3d.com/tutorials/view-frustum-culling/
https://docs.unity3d.com/Manual/UnderstandingFrustum.html
If you want to dig deeper and optimize this, there are a couple of ways this can be done. But luckily, Unity comes with many useful related functions already built into the Camera class. So instead, you could use Camera.WorldToScreenPoint() (or Camera.WorldToViewportPoint()), and compare the resulting screen coordinates to the screen size or viewport, like discussed in Unity forum. (The frustum math is hidden behind these compact functions, but beware that this is probably not the optimal way to do this.)
Instead of calling FindGameObjectsWithTag() every time, you could do it only once in Start(), assuming objects do not get created/destroyed while the game is running.
I've tried to modify your script, since I'm also learning Unity again... The script can be dragged to the main camera, and it should show the "focus object" in the Scene view with the green debug line. I hope this helps:
using UnityEngine;
[RequireComponent(typeof(Camera))]
public class PhotoCast : MonoBehaviour
{
public float maxDistance = 250.0f;
public string objectTag = "photo";
protected GameObject[] objs;
protected GameObject objFocus;
protected Camera cam;
public void Start() {
objs = GameObject.FindGameObjectsWithTag(objectTag);
cam = GetComponent<Camera>();
}
public void Update() {
if (Input.GetButtonDown("Fire1")) {
objFocus = CheckObjects();
if (objFocus) {
Debug.Log("closest object in view: " + objFocus.name);
/* TODO: take actual photo here */
}
}
if (objFocus) {
Debug.DrawLine(transform.position,
objFocus.transform.position, Color.green);
}
}
GameObject CheckObjects() {
GameObject obj_closest = null;
float dist_closest = float.MaxValue;
foreach (GameObject o in objs) {
float dist = Vector3.Distance(
o.transform.position, transform.position);
if (dist < maxDistance && dist < dist_closest
&& InViewport(o.transform.position)
&& HasLineOfSight(o.transform)) {
dist_closest = dist;
obj_closest = o;
}
}
return obj_closest;
}
bool InViewport(Vector3 worldPos) {
Vector3 p = cam.WorldToViewportPoint(worldPos);
return (p.x > 0.0f && p.x <= 1.0f && p.y > 0.0f && p.y <= 1.0f
&& p.z > cam.nearClipPlane);
}
bool HasLineOfSight(Transform target) {
RaycastHit hit;
Vector3 dir = target.position - transform.position;
if (Physics.Raycast(transform.position, dir, out hit, maxDistance)) {
if (hit.collider.gameObject == target.gameObject) {
return true;
}
}
return false;
}
}
Side notes:
Another issue with this technique is, that there can be tagged objects right in front of the camera, but other tagged objects that are closer on the side will be picked up instead of the obvious one. Many small issues to fine-tune until the scripts fits the game, I guess. Instead of only using one Raycast per object, you could use multiple ones, and take the bounding box or the actual collider shape into account.
An improved version of the script could make use of the Physics.Overlap*() or Physics.*Cast*() functions, documented here.

JavaFX Changing Text Displaying Score to Animation and Setting Back to Score

I am currently making a 2-player game of pong that displays the score of each side. I am looking to display the score throughout the entire game, but when someone scores, I would like a "GOAL!" (letter by letter to a short pause at the end) animation to be set as the text rather than the team's points (just how it shows in some sports shown on tv), and then be set back to the new score after. I am having troubles with this section, here is what I have:
This is my code for displaying the animation(not sure if this is right, haven't been able to test it because it won't display!):
public void displayGoal(int homeOrAway)
{
String str = "GOAL!";
if(homeOrAway == 0)
{
final IntegerProperty i = new SimpleIntegerProperty(0);
Timeline timeline = new Timeline();
KeyFrame keyFrame = new KeyFrame(
Duration.seconds(1),
event -> {
if (i.get() > str.length()) {
timeline.stop();
textHomeScore.setText("" + getHomeScore() + "");
} else {
textHomeScore.setText(str.substring(0, i.get()));
i.set(i.get() + 1);
}
});
timeline.getKeyFrames().add(keyFrame);
timeline.play();
}
else
{
final IntegerProperty i = new SimpleIntegerProperty(0);
Timeline timeline = new Timeline();
KeyFrame keyFrame = new KeyFrame(
Duration.seconds(1),
event -> {
if (i.get() > str.length()) {
timeline.stop();
textAwayScore.setText(""+ getAwayScore() + "");
} else {
textAwayScore.setText(str.substring(0, i.get()));
i.set(i.get() + 1);
}
});
timeline.getKeyFrames().add(keyFrame);
timeline.play();
}
}
textAwayScore and textHomeScore are just normal Texts, not TextFields.
Without this function, the scores display perfectly fine and they count up everytime the ball goes past the paddle. It would be nice to add this animation to whichever side gets the point, so any help to accomplish this is greatly appreciated. If anyone needs any clarification please let me know and I will clarify something! Thank you!

SFML Animation on Keypress only moves one frame

I am trying to get my animation to cycle through two images when a directional key is pressed. Currently it switches images on each key press. I have been looking at tutorials and understand that I need some kind of timer to measure each frame time but everything I have tried to implement in to my code so far has failed so I am just posting my code that works at the moment.
Could anyone explain to me how to go about implementing this please?
void Frog::up(sf::Event event)
{
sf::IntRect frogUpAnimation[iNumFrames];
frogUpAnimation[0] = sf::IntRect(13, 362, 21, 23);
frogUpAnimation[1] = sf::IntRect(46, 367, 21, 23);
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Up)
{
audio.frogjumpsound();
frogSprite.move(0.0f, -55.0f);
iScoreCounter = iScoreCounter + 10;
iCurrentFrame++;
if (iCurrentFrame >= iNumFrames) iCurrentFrame = 0;
frogSprite.setTextureRect(frogUpAnimation[iCurrentFrame]);
}
}
int main()
{
sf::RenderWindow window(sf::VideoMode(800, 800), "Frogger");
window.setFramerateLimit(60);
sf::Clock timer;
float fFrameTime = 1.0f / 60.0f;
float fElapsedTime;
sf::Event event;
Game game;
Frog frog;
Timer countdown;
Text text;
Audio gameaudio;
gameaudio.music();
while (window.isOpen())
{
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
{
window.close();
}
if (event.type == sf::Event::KeyPressed)
{
game.processKeyPress(event.key.code);
}
frog.up(event);
frog.down(event);
frog.left(event);
frog.right(event);
} // Event loop
countdown.gametime();
fElapsedTime = timer.getElapsedTime().asSeconds();
if (fElapsedTime > fFrameTime)
{
timer.restart();
}
//Update
game.checkPads(&frog);
game.checkWin();
game.gameOver();
frog.scorecounter(&countdown);
frog.update(fElapsedTime);
game.update(fElapsedTime);
text.update(fElapsedTime);
countdown.update(fElapsedTime);
game.collision(&frog);
// Drawing
window.clear();
window.draw(game);
window.draw(frog);
window.draw(countdown);
window.draw(text);
window.display();
} // main loop
}
First, I would only call 'frog.up()', 'frog.left()', etc. when the proper key is pressed. Otherwise, you're calling potentially 4 functions each iteration which do absolutely nothing.
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right) {
frog.right();
}
else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left) {
frog.left();
}
As for your timer issues, your fElapsedTime and fFrameTime look a little funky. Take a look at https://en.sfml-dev.org/forums/index.php?topic=10913.0 but I really recommend this guide: https://github.com/SFML/SFML/wiki/Source:-AnimatedSprite

Need to correct the "friction"/travel distance of balls in a billiard game, in processing

Hey guys I am in the process of building this simple billiard game, and I want the black ball, labeled bBall, to go the same distance as the white ball, labeled wBall, and no farther. ie, if the white ball travels 20 pixels before it hits the black ball, I want the black ball to travel 20 pixels and then stop. How might I go about accomplishing this? Thanks for the help guys.
processing 2.0.3
ball wBall, bBall;
int click;
String msg;
Boolean moving = false;
float difx, dify;
float cdistance;
int steps = 40;
void setup(){
click=0;
size(800,400);
background(16,77,27);
wBall = new ball(35,#ffffff);
bBall = new ball(35,#000000);
msg="";
}
void mouseClicked(){
if(!moving){
click++;
}
}
void draw(){
background(16,77,27);
String msg;
fill(0,0,0);
ellipse(15,15,30,30);
ellipse(785,15,30,30);
ellipse(15,385,30,30);
ellipse(785,385,30,30);
ellipse(410,15,30,30);
ellipse(410,385,30,30);
msg="the count is "+click;
println("the count is "+click);
//Moving Balls\\
fill(255,255,255);
noStroke();
if(click==0){
wBall.xpos=mouseX;
wBall.ypos=mouseY;
}else if(click==1){
bBall.xpos=mouseX;
bBall.ypos=mouseY;
}else if(click==2){
difx = wBall.xpos-bBall.xpos;
dify = wBall.ypos-bBall.ypos;
}
else if(click==3){
cdistance = dist(wBall.xpos,wBall.ypos,bBall.xpos,bBall.ypos);
if (cdistance>bBall.ballDiam/2){
moving = true;
wBall.xpos-=difx/steps;
wBall.ypos-=dify/steps;
}
else{
moving = false;
click=4;
println("click"+click);
}
}else if(click==4){
if(cdistance<bBall.ballDiam){
moving = true;
bBall.xpos-=difx/steps;
bBall.ypos-=dify/steps;
}
}
wBall.update();
bBall.update();
}
class ball{
float xpos, ypos;
color myColor;
int ballDiam;
boolean visible = true;
ball(int tempdiam, color tempColor){
myColor=tempColor;
ballDiam=tempdiam;
}
void update(){
if(visible){
fill(myColor);
ellipse(xpos,ypos,ballDiam,ballDiam);
}
}
}
void keyPressed(){
if (key =='c'){
setup();
}
}
One way would be to make your
else if (click==4) {
if (cdistance<bBall.ballDiam) {
moving = true;
bBall.xpos-=difx/steps;
bBall.ypos-=dify/steps;
}
}
to
else if (click==4) {
if (cdistance<bBall.ballDiam) {
if (dist(wBall.xpos, wBall.ypos, bBall.xpos, bBall.ypos) < sqrt(sq(difx)+sq(dify))) {
moving = true;
bBall.xpos-=difx/steps;
bBall.ypos-=dify/steps;
}
}
}
essentially only moving the ball as long as its distance from the white one is less than the original difference...
Despite this though I feel you are approaching this the hard way. Maybe take a look to introduce speed and acceleration (and maybe even friction) calculations to your game and things will make more sense...

Resources