'Pong' ball not resetting - c#

I'm writing my own Pong game in C# and I'm having some issues with my ball not resetting correctly. I've spent a good hour or two debugging the code but I can't figure it out.
Basically what's supposed to happen is that when the ball is detected to leave the bounds of the window then the Center() method is going to get called and Center() then resets the ball Point by accessing the backing field directly. Now this works, I've verified that it does what it's supposed to by stepping through the code.
Now the weird that happens is that after Center() gets called the ball position reverts back to what it used to be pre-centering. Now the weird thing is that, this reset happens before the set accessor of the Point property is even called. And I'm 100% sure that I'm not accesing the backing field directly in any other place than Center() so I can't figure it out.. Here's the code
namespace Pong
{
internal enum CollisionType
{
Paddle, Boundary
}
class Ball
{
private readonly IGameView view;
private readonly IGameController controller;
private int velocity = 10;
private double angle;
private event CollisionHandler Collision;
private Point _point;
public Point Point
{
get { return _point; }
set
{ // If UpdatePosition() tries to move the ball beyond the boundaries in one tick move the ball to the boundaries
if(value.Y > view.Boundaries.Height || value.Y < 0)
{
_point = new Point(value.X, view.Boundaries.Height);
Collision(CollisionType.Boundary); // Also raise the collision event
}
//If the ball is going to pass the X boundaries of the map then a player should score and the ball should reset
if (value.X > view.Boundaries.Width || value.X < 0)
{
if (angle > 90) // If the angle of the ball of the ball is above 90 degrees then the left paddle was the shooter
{ // So he should score
var scoringPlayer = Array.Find(controller.Players, player => player.Paddle.Orientation.Equals(Orientation.Left));
controller.PlayerScore(scoringPlayer);
Center(scoringPlayer);
}
else // If not, then it's the right paddle
{
var scoringPlayer = Array.Find(controller.Players, player => player.Paddle.Orientation.Equals(Orientation.Right));
controller.PlayerScore(scoringPlayer);
Center(scoringPlayer);
}
}
// If the ball will collide with a player paddle then raise collision event
if (controller.Players.Any(player => player.Paddle.Position.Equals(value)))
{
Collision(CollisionType.Paddle);
_point = value;
}
_point = value;
}
}
public Ball(IGameView view, IGameController controller)
{
this.view = view;
this.controller = controller;
}
public void Center(Player server)
{
//Center the ball, acccess the backing field directly to avoid all the conditional logic in the property
_point = new Point(view.Boundaries.Width / 2, view.Boundaries.Height / 2);
//The ball will start moving from the center Point towards one of the different sides
/*TODO: Apparently the ball moves sideways down towards one of the player paddles, so we must implement more complex
logic to calculate the starting angle of the ball */
angle = (server.Paddle.Orientation.Equals(Orientation.Left)) ? 0 : 180;
}
public void UpdatePosition()
{
//Called to update ball position based on velocity and angle of ball
//For now let's just implement a very primitive movement algorithm
if (angle < 90)
{
Point = new Point(Point.X + velocity, Point.Y);
}
else
{
Point = new Point(Point.X - velocity, Point.Y);
}
}
}
//TODO: Add collision detection through BallCollision Event and implement BallCollisionHandler(CollisionType as Object args)

You set _point = value immediately after you call Center - you need to ensure that your backing field isn't updated after you set it during Center
public Point Point
{
get { return _point; }
set
{ // If UpdatePosition() tries to move the ball beyond the boundaries in one tick move the ball to the boundaries
if(value.Y > view.Boundaries.Height || value.Y < 0)
{
_point = new Point(value.X, view.Boundaries.Height);
Collision(CollisionType.Boundary); // Also raise the collision event
}
//If the ball is going to pass the X boundaries of the map then a player should score and the ball should reset
if (value.X > view.Boundaries.Width || value.X < 0)
{
if (angle > 90) // If the angle of the ball of the ball is above 90 degrees then the left paddle was the shooter
{ // So he should score
var scoringPlayer = Array.Find(controller.Players, player => player.Paddle.Orientation.Equals(Orientation.Left));
controller.PlayerScore(scoringPlayer);
Center(scoringPlayer);
}
else // If not, then it's the right paddle
{
var scoringPlayer = Array.Find(controller.Players, player => player.Paddle.Orientation.Equals(Orientation.Right));
controller.PlayerScore(scoringPlayer);
Center(scoringPlayer);
}
}
// If the ball will collide with a player paddle then raise collision event
if (controller.Players.Any(player => player.Paddle.Position.Equals(value)))
{
Collision(CollisionType.Paddle);
_point = value;
}
_point = value; // <--- This always gets set - even after you call Center above
}
}
Edit:
As Sayse pointed out whilst this is the issue you are getting, it might be useful to move this logic into an 'update' part of the code - this logic seems like it should be part of the IGameController implementation or a IBallController maybe; either that or put an Update method into your Ball class which is called by the game controller when it wants to update all game objects
Property setters ideally shouldn't be side-effecting

Related

Setting Up Skill in Unity

These past month+ I learned many things by making a game in Unity.I have a lot of fun doing so. But some thing are still confusing me. I'm trying to setup a skill to the character and it goes almost well. When the character is casting the skill, the skill goes behind the character and not in front. So i thought to play with positions and rotations to make it work but still nothing. Worth to mention that the prefab has it's own motion. So my code so far is this. So help would be great and some teaching about the logic behind the skills system would be much appreciated. So take a look:
using UnityEngine;
public class MagicSkill : MonoBehaviour
{
public GameObject hand; // Players right hand
public GameObject fireballPrefab; // Fireball particle
Vector3 fireballPos; // Adding the transform.position from the players hand
Quaternion fireballRot; // Adding the rotation of the players hand
private bool isPressed = false; //Skill button (mobile)
public Animator animator; // Casting spell animation
void Update()
{
fireballPos = hand.transform.position; // Getting the position of the hand for Instatiating
fireballRot.x = hand.transform.rotation.x; // Getting the rotation of the hand for x Axis
fireballRot.y = hand.transform.rotation.y; // Getting the rotation of the hand for y Axis (Here i try to modify the values but still nothing)
fireballRot.z = hand.transform.rotation.z; // Getting the rotation of the hand for z Axis
if (isPressed == true)
{
animator.SetBool("Magic", true);
if (hand.transform.position.y >= 20) // Here I got the exact position of the hand for when to
Instatiate the skill
{
for (int i = 0; i < 1; i++) // For some reason it instatiates too many prefabs (I think it's because it's in Update method)
{
Instantiate(fireballPrefab, fireballPos, Quaternion.Euler(fireballRot.x, fireballRot.y, fireballRot.z));
Invoke("Update", 2.0f); // I'm trying to slow down the pressed button so that it want spawn every frame
}
}
}
else
{
animator.SetBool("Magic", false);
}
}
public void MagicSkills()
{
if (isPressed == false)
{
isPressed = true;
}
else
{
isPressed = false;
}
}
}
After some playing around with the code I managed to find a solution.Here I post the working code for me at least.Maybe it will help someone else too.For this to work properly you must remove the Event Trigger OnPointerUp from your button.Many thanks for the help Guilherme Schaidhauer Castro
using UnityEngine;
public class MagicSkill : MonoBehaviour
{
public GameObject hand; // Players right hand
public GameObject fireballPrefab; // Fireball particle
public Animator animator; // Casting spell animation
Vector3 fireballPos; // Adding the transform.position from the players hand
private bool isPressed = false; //Skill button (mobile)
void Update()
{
fireballPos = hand.transform.position; // Getting the position of the hand for Instatiating
if (isPressed == true)
{
animator.SetBool("Magic", true);
if (hand.transform.position.y >= 20) // Here I got the exact position of the hand for when to Instatiate the skill
{
Instantiate(fireballPrefab, fireballPos, Quaternion.identity);
isPressed = false;
}
}
else
{
animator.SetBool("Magic", false);
}
}
public void MagicSkills()
{
if (isPressed == false)
{
isPressed = true;
}
else
{
isPressed = false;
}
}
}
Keep a reference to the character's transform and use that transform to instantiate the fireball instead of using the rotation from the hand. The rotation of the hand is not changing in relation to the body, so that's why the ball is always going in the same direction.

Counting a full rotation with a circular drive

I've been raking my brain for the last day on how to calculate a full rotation with how to count a full rotation of an object along the X axis thats using the base circular drive from SteamVR.
I thought a simple 3d cube, with the mesh turned off in the the path of the rotation with collision code on it would be a barebone way of doing it, but it doesn't even seem to be registering the detection when the object hits the placed cubes, and i know its not because of me being stupid, as its recycled code from a working part of the project.
Below i have a small piece of code that basically detects when the object has reached the end of the rotation, and then increments the Count by one.
My main problem is that sometimes it manages to clock more than once, and if you can find the right spot, you can just keep it there and it'll keep on adding the count up by. Im wondering how i can stop it and increment only by one, until another full rotation has been made?
EDIT: to be more clear in case there is any confusion, Once the angle is clocked in between 359 and 360, i want it to increment once, whereas currently if you get the angle to sit anywhere in between 359-360 it will carry on adding one to the rotation count, despite no full rotation having been made, so im trying to figure out how to make my code only increment once, and once it does increment once it resets the position to zero, so therefore no more Increments can happen. It's a crank mechanism in VR, along the X axis.
Any help is appreciated, Thanks!
float Test;
float RotationCount = 0;
// Start is called before the first frame update
void Start()
{
// Test = transform.localRotation.eulerAngles.x;
}
// Update is called once per frame
void Update()
{
if (Test > 359 && Test < 360)
{
Debug.Log("Clocked");
count();
}
else
{
// Debug.Log("Nope");
}
if (Test == 0)
{
Debug.Log("Yes");
}
Test = transform.localRotation.eulerAngles.x;
}
void count()
{
RotationCount++;
}
sometimes it manages to clock more than once, and if you can find the right spot, you can just keep it there and it'll keep on adding the count up by.
well in
if (Test > 359 && Test < 360)
{
Debug.Log("Clocked");
count();
}
what happens if your angle is e.g. 359.5?
It is very difficult to just take a current rottaion and know whether it was turned more or less than a certain angle.
I'ld rather store the last rotation, compare it to the current one and add the difference to a variable. Than if the variable exceeds 360 a full rotation was done. Since I also don't like to calculate anything with Quaternion and eulerAngles, way simplier is to use the methods provided by Vector3.
For the local rotation around X (== transform.right) I would use the angle between the current and the last transfrm.up vector.
Something like
public class RotationCheck : MonoBehaviour
{
public int RotationCount;
public float rotatedAroundX;
public Vector3 lastUp;
public UnityEvent On3TimesRotated;
private void Awake()
{
rotatedAroundX = 0;
// initialize
lastUp = transform.up;
}
private void Update()
{
var rotationDifference = Vector3.SignedAngle(transform.up, lastUp, transform.right);
rotatedAroundX += rotationDifference;
if (rotatedAroundX >= 360.0f)
{
Debug.Log("One positive rotation done", this);
RotationCount++;
rotatedAroundX -= 360.0f;
}
else if (rotatedAroundX <= -360.0f)
{
Debug.Log("One negative rotation done", this);
RotationCount--;
rotatedAroundX += 360.0f;
}
// update last rotation
lastUp = transform.up;
// check for fire the event
if (RotationCount >= 3)
{
On3TimesRotated?.Invoke();
RotationCount = 0;
}
}
}
You can use a UnityEvent to get the same thing the Button uses for onClick so you can reference callbacks there via the inspector.
BUT if you don't care about the single rotations but actually only wnat the final RotationCount >= 3 I would actually use
private void Update()
{
var rotationDifference = Vector3.SignedAngle(transform.up, lastUp, transform.right);
rotatedAroundX += rotationDifference;
RotationCount = Mathf.RoundToInt(rotatedAroundX / 360.0f);
// update last rotation
lastUp = transform.up;
// check for fire the event
if (RotationCount >= 3)
{
On3TimesRotated?.Invoke();
RotationCount = 0;
rotatedAroundX = 0;
}
}
which directly reduces the value by one if rotated under the 360 mark instead of waiting for a full negative rotation
*as you can see instead of reaching 3 the RotationCount is reset to 0. This is where the On3TimesRotated event is/would get fired.

XNA/Monogame: Platformer Jumping Physics and Collider Issue

I have a sprite, Player. I update Player's position the following way:
_position += (_direction * _velocity) * (float)gameTime.ElapsedGameTime.TotalSeconds;
Where _position, _direction and _velocity are Vector2s.
I have a simple collider(BoxCollider) which simply generates a rectangle (BoundingBox) given the position and dimensions of the collider.
In Initialize(), I create a new List<BoxCollider> and fill it with colliders for the level.
On Update(), I pass the list to Player to check for collisions.
The collision check method:
public void CheckPlatformCollision(List<BoxCollider> colliders, GameTime gameTime)
{
var nextPosition = _position + (_direction * _velocity) * (float)gameTime.ElapsedGameTime.TotalSeconds;
Rectangle playerCollider = new Rectangle((int)nextPosition.X, (int)nextPosition.Y, BoundingBox.Width, BoundingBox.Height);
foreach(BoxCollider collider in colliders)
{
if(playerCollider.Intersects(collider.BoundingBox))
{
nextPosition = _position;
}
}
Position = nextPosition;
}
Right now, every way I've tried to implement gravity has failed. If Player is dropped from too high, nextPosition becomes too far away from Player and leaves it stuck in mid-air.
I'm also having problems with horizontal collisions as well, the issue being similar: Player stops too soon, leaving a space between. Sometimes I've had Player stick to the side of the collider.
What I would like to know is:
How do I properly implement gravity & jumping with _position, _direction, and _velocity? How do I properly handle collisions both horizontally and vertically?
For gravity, add this just before you update _position:
_velocity += gravity * (float)gameTime.ElapsedGameTime.TotalSeconds;
Where gravity is something like new Vector2(0, 10).
For jumping, you need to set the vertical component of the velocity when the player presses the jump button:
if (jumpPressed && jumpAvailable)
{
_velocity.Y = -10; // Numbers are example. You need to adjust this
jumpAvailable = false;
}
And you need to reset jumpAvailable when the player touches the floor.
Colliding is a much complicated thing. But if you look for "XNA implement collision" on the internet you will find a lot of answers.
There are many ways. One of them is pushing back the player to the border of the boxcollider, instead of not letting him move, like you did in your code. The code would be:
if (IntersectsFromTop(playerCollider, collider.BoundingBox))
{
_position.Y = collider.BoundingBox.Y - BoundingBox.Height;
}
else if (IntersectsFromRight(playerCollider, collider.BoundingBox))
{
_position.X = collider.BoundingBox.X + collider.BoundingBox.Width;
}
// And so on...
The helper methods can implemented like:
private static bool IntersectsFromTop(Rectange player, Rectangle target)
{
var intersection = Rectangle.Intersect(player, target);
return player.Intersects(target) && intersection.Y == target.Y && intersection.Width >= intersection.Height;
}
private static bool IntersectsFromRight(Rectange player, Rectangle target)
{
var intersection = Rectangle.Intersect(player, target);
return player.Intersects(target) && intersection.X + intersection.Width == target.X + target.Width && intersection.Width <= intersection.Height;
}
// And so on...
The rationnale behind that code can be explained with a picture:
In that picture width and height correspond to the intersection, in purple.

XNA, smoother jumping animations

I'm trying to make jumping functionality in my Movement test. My character jumps and comes back down, but it's very choppy and not smooth at all.
What happens is he juts up to his max height, then comes down smoothly.
I can spot the problem, the for loop doesn't want to play nicely with the code. However, I don't know how to circumvent this. Is there any way to keep the button press and have him jump up nicely?
Code:
if (leftStick.Y > 0.2f && sprite.Position.Y == position.Y || isPressed(Keys.Up) == true && sprite.Position.Y == position.Y)
{
if (wasLeft == true)
{
sprite.CurrentAnimation = "JumpLeft";
}
else if (wasLeft == false)
{
sprite.CurrentAnimation = "JumpRight";
}
//This for loop is my issue, it works but it's jumpy and not smooth.
for (movement.PlayerHeight = 0; movement.PlayerHeight < movement.PlayerMaxHeight; movement.PlayerJump())
{
sprite.Position.Y -= movement.PlayerJump();
}
}
sprite.StartAnimation();
}
else
{
leftStick = NoInput(leftStick);
}
private Vector2 NoInput(Vector2 leftstick)
{
if (sprite.Position.Y < position.Y) //(movement.PlayerSpeed > 0)
{
sprite.Position.Y += movement.PlayerHeight;
movement.PlayerHeight -= movement.Player_Gravity;
//sprite.Position.Y += movement.PlayerSpeed;
//movement.PlayerSpeed -= movement.Player_Decel;
}
else
{
sprite.Position.Y = position.Y;
}
}
Movement class:
public float PlayerMaxHeight = 15f;
public float PlayerHeight = 0;
public float Player_Gravity = 0.01f;
private const float Player_Jump = 0.35f;
public float PlayerJump()
{
PlayerHeight += Player_Jump + Player_Gravity;
if (PlayerHeight > PlayerMaxHeight)
{
PlayerHeight = PlayerMaxHeight;
}
return PlayerHeight;
}
The best way to do jumping I found is to implement a property that will deal with acceleration.
A brief list of what to do:
Create a property that stores the current Y velocity.
Increment the Y velocity by a set amount each step - generally represented by a gravity property somewhere.
Increment1 the Y position by the Y velocity each step.
When you jump, simply subtract1 a said amount from the Y velocity - which will cause your player to jump up in an easing-out motion (start fast and slow down as he reaches the high of the jump). Because you're always incrementing the Y velocity, you will eventually reverse direction and return back to the surface.
When touching a surface, reset the Y velocity to zero.
1 Pretty sure that the Y axis is inverted in XNA (I work in Flash), so where I say increment the Y velocity you may need to decrement it instead - same deal for subtracting from it to jump.
My general approach to get a jump really quickly is to use a bleed off value to make slightly smoother looking movement. I can't look at any code/xna right now but my first thought would be something like below.
Define variables:
float bleedOff = 1.0f;
bool jumping = false;
Input update:
if(input.JumpKey())
{
jumping = true;
}
Jumping update:
if(jumping)
{
//Modify our y value based on a bleedoff
//Eventually this value will be minus so we will start falling.
position.Y += bleedOff;
bleedOff -= 0.03f;
//We should probably stop falling at some point, preferably when we reach the ground.
if(position.Y <= ground.Y)
{
jumping = false;
}
}
bleedOff = MathHelper.Clamp(bleedOff, -1f, 1f);
Obviously the bleedOff value should be calculated with a bit more randomness, probably using a gravity value, to it to make it look right but this will give the illusion of acceleration/decceleration with the jump as they rise and fall.
Rising very fast to begin with and slowing down and eventually starting to fall again and that will speed up. The clamp at the bottom will be your maximum vertical velocities.
I just wrote this off the top of my head at work so apologies if it's not quite what your looking for but I tried to keep it a bit more general. Hope it helps.

problem designating a bound for my sprite in XNA

i just got into game development and XNA and was following a tutorial and decided to try and add in a bound area for a floor. In the tutorial the sprite could move freely and i wanted to have a stopping point for it, so i added a statement to part of the input method
if (aCurrentKeyboardState.IsKeyDown(Keys.Down) == true)
{
if (this.Position.Y == 420)
{
MOVE_DOWN = 0;
mDirection.Y = MOVE_DOWN;
}
else
{
mSpeed.Y = PLAYER_SPEED;
MOVE_DOWN = 1;
mDirection.Y = MOVE_DOWN;
}
}
MOVE_DOWN is my variable for the y change, if it = 0, there is no movement, 1 it moves down, -1 it moves up.
this worked only if the position of the bounds(420) was equal to the position that my sprite started out at, other than that it doesnt work.
i think its because the position isnt updating correctly. i dont know ive tried a lot of things and am pretty new with XNA and game development. Any help would be greatly appreciated.
Here is the update method for my player sprite
public void Update(GameTime theGameTime)
{
KeyboardState aCurrentKeyboardState = Keyboard.GetState();
UpdateMovement(aCurrentKeyboardState);
mPreviousKeyboardState = aCurrentKeyboardState;
base.Update(theGameTime, mSpeed, mDirection);
}
and here is the update for the base class
public void Update(GameTime theGameTime, Vector2 theSpeed, Vector2 theDirection)
{
Position += theDirection * theSpeed * (float)theGameTime.ElapsedGameTime.TotalSeconds;
}
If 'Position' is a Vector2, then it is using floats of the X & Y components.
For practical purposes, a float will rarely equal a whole number due to floating point rounding errors.
if (this.Position.Y == 420)//will never return true
should be changed to:
if (this.Position.Y < 420)
{
this.Position = 420;
//other stuff you have
}

Categories

Resources