XNA Controls Rotation in Addition to Camera/Model - c#

Before I start: I tried gamedev.stackexchange and nobody replied so I thought I'd test my luck here :)
I'm relatively new to 3D programming and I'm trying to make a game where the player can control a character around a maze.
My current problem isn't really getting the mouse to be centre and reading it to rotate the camera - although I'd like to in the future, right now it's getting the WASD movement to also rotate, I assume this means I'd need to rotate the axis. I've been trying for about 1-2 hours looking around, some say to use Quaternions and others Matrices, but I haven't been able to get anything to work so far. To clarify, when I hold W, it moves forward. But if I then rotate the camera to look to the right and hold W, it'll move left of the camera <--- I want to fix this if possible
I'm very lost at what to do or how best to do it, I'll provide my Camera class and Game1 class, although I'll warn you - I'm not very good at laying things out nicely. I'm not a highly experiences programmer so asking me to apply knowledge to my project may take me a while to get right, thanks :D
public class Camera
{
float angle = 1f;
float currentAngle;
public Vector3 cameraPosition;
public Vector3 cameraTarget;
public Matrix worldMatrix { get; set; }
public Matrix viewMatrix { get; set; }
public Matrix projectionMatrix { get; set; }
public Matrix rotationMatrix;
Matrix playerPos;
Matrix movement;
Vector3 position;
public void SetMovement(Matrix inMovement)
{
movement = inMovement;
}
public void SetPosition(Vector3 inPosition)
{
position = inPosition;
}
public Camera(GraphicsDevice graphics, Vector3 inTarget)
{
rotationMatrix = Matrix.CreateRotationY(MathHelper.ToRadians(0f));
cameraTarget = new Vector3(0f, 0f, 0f);
cameraPosition = new Vector3(0f, -1f, 3.5f);
playerPos = Matrix.CreateScale(0.01f) *
Matrix.CreateTranslation(position);
projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45f),
graphics.DisplayMode.AspectRatio,
0.03f, 100f);
worldMatrix = Matrix.CreateWorld(inTarget, Vector3.Forward, Vector3.Up);
viewMatrix = Matrix.CreateLookAt(position, inTarget, Vector3.Up);
}
public virtual void Update()
{
if(Keyboard.GetState().IsKeyDown(Keys.Right))
{
currentAngle += angle;
rotationMatrix = Matrix.CreateRotationY(MathHelper.ToRadians(currentAngle)) * rotationMatrix;
currentAngle = 0;
}
if (Keyboard.GetState().IsKeyDown(Keys.Left))
{
currentAngle += angle * -1;
rotationMatrix = rotationMatrix * Matrix.CreateRotationY(MathHelper.ToRadians(currentAngle));
currentAngle = 0;
}
viewMatrix = Matrix.CreateLookAt(new Vector3(cameraPosition.X + position.X, cameraPosition.Y + position.Y + 1.02f, //FIRST PERSON CAMERA
position.Z + 0.013f), cameraTarget + position + new Vector3(0f,1f,0f), Vector3.Up) * rotationMatrix;
//viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraTarget, Vector3.Up); //MAP VIEW CAMERA
}
}
Here's Game1
public class Game1 : Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Model map, player;
Camera playerCamera;
Vector3 playerPosition;
Matrix playerCurrentPosition, cameraCurrentPosition;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.PreferredBackBufferHeight = 1080;
graphics.PreferredBackBufferWidth = 1920;
}
protected override void Initialize()
{
player = Content.Load<Model>("ball2");
playerPosition = new Vector3(-0.84f, 0.86f, 0.02f);
playerCamera = new Camera(GraphicsDevice, new Vector3(0f, 0f, 0f));
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
map = Content.Load<Model>("newMap");
}
protected override void UnloadContent()
{
}
protected override void Update(GameTime gameTime)
{
playerCamera.SetMovement(playerCurrentPosition);
playerCamera.SetPosition(playerPosition);
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
if (Keyboard.GetState().IsKeyDown(Keys.W))
playerPosition.Y += 0.01f;
if (Keyboard.GetState().IsKeyDown(Keys.A))
playerPosition.X -= 0.003f;
if (Keyboard.GetState().IsKeyDown(Keys.S))
playerPosition.Y -= 0.003f;
if (Keyboard.GetState().IsKeyDown(Keys.D))
playerPosition.X += 0.003f;
playerCurrentPosition =
Matrix.CreateScale(0.01f) *
Matrix.CreateTranslation(playerPosition);
cameraCurrentPosition = Matrix.CreateTranslation(playerPosition - new Vector3(0f, 0.1f, 0f));
playerCamera.Update();
base.Update(gameTime);
}
public void DrawModel(Model model, Matrix world, Matrix view, Matrix projection)
{
foreach (ModelMesh mesh in model.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.View = view;
effect.Projection = projection;
effect.World = world;
effect.EnableDefaultLighting();
}
mesh.Draw();
}
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Crimson);
DrawModel(map, playerCamera.worldMatrix, playerCamera.viewMatrix, playerCamera.projectionMatrix);
DrawModel(player, playerCamera.worldMatrix * playerCurrentPosition, playerCamera.viewMatrix, playerCamera.projectionMatrix);
base.Draw(gameTime);
}
}

There are obviously different approaches to this, so this might not be the most elegant:
Let's assume that you store your camera rotation as an angle rotationY (you will need a second angle, but that probably won't influence the walking direction).
I assume that you currently have some code like this:
if (kbState.IsKeyDown(Keys.W))
position += new Vector3(1, 0, 0) * elapsedTime;
What we now want to do is rotate that direction vector the same amount we rotated the camera. We can use Matrix transformations for that (writing the rotation with sin and cos would be faster, but as this only runs once per frame, that should not be a major concern):
Matrix rotation = Matrix.CreateRotationY(rotationY);
Vector3 forward = Vector3.Transform(new Vector3(1, 0, 0), rotation);
if (kbState.IsKeyDown(Keys.W))
position += forward * elapsedTime;
That's basically it. For backward movement, use -forward and for sideways motion, rotate a second vector using the same matrix.
There are different ways of doing this: You can compute the forward and sideways vector from the camera's point of view (I think it is quite fast to get them from the view matrix). That will probably be faster, but once again, performance shouldn't matter too much here.

Related

Ball is getting more and more energi for each collision

Im trying to write some simple 2d physics in monogame.
I release a ball from a given start position with a given velocity and I want it to bounce back up when it is colliding with the floor.
My problem is that I seem to give the ball more energi for each bounce i.e. it bounces higher and higher for each collision with the floor. It should be the other way around.
I have:
float get_VelocityX(float _speed, double _angle)
{
return velocity_x = velocity_x +_speed * (float)Math.Cos(_angle);
}
public float get_VelocityY(float _speed, double _angle, float _t, float gravity)
{
return velocity_y = velocity_y + _speed * (float)Math.Cos(_angle); // - (float)(-gravity * _t);
}
And in my Update function I have this:
if (speed > 0)
{
timeCount += (float)gameTime.ElapsedGameTime.TotalSeconds;
t += timeCount;
}
else
{
return;
}
Vx = ball.get_VelocityX(speed, angle);
Vy = ball.get_VelocityY(speed, angle, t, gravity);
if (posX >= windowMAX)
{
posX = posX + -Vx * friction * t;
}
if (posY > windowMIN)
{
posY = posY + -Vy * friction * t;
}
else
{
posY += gravity;
}
ballRect.X = (int)posX;
ballRect.Y = (int)posY;
Where posX, posY and speed are user inputs for start position and velocity.
Gravity is just a float = 9.82f;
Right now Im not doing anything with the posX except setting the balls starting position. Next step will be to implement a throwing motion.
EDIT:
Friction = 0.001f;
t is deltatime.
I went through your logic and have prepared a sample code. Please read the following before you go through it.
In order to simulate real-life motion, you need to implement the physics accurately. Although your implemented velocity and position seems mostly correct, the gravity needs to be treated as acceleration, and therefore adding its value to the position (as done in your code) is incorrect. I assume that this is the reason why you aren't getting your expected result since the value of increment on the Y-component of position is far greater than it should be.
Instead of keeping PosX, PosY for the position, Velocity_X..(), Velocity_Y..() for velocity, I would advise you to use struct Vector2 as shown below in my code, which is included in the Monogame framework and has a lot more helping functions built-in. This will help in making your code shorter and cleaner.
I did not understand why you used the Cosine of the given angle in your implementation of Velocity for both its X and Y components. My code below is ignoring this.
You can see my code below. Here the bouncing object Box is of the type struct PhyObj with all the needed Physics of motion implemented within it.
public class Game1 : Game
{
private SpriteBatch _batch;
internal Texture2D Texture;
public static Vector2 GravityAcceleration => new Vector2(0, 9.8f);//Constant accerleration along Y-Axis
internal Rectangle Ground;
internal PhyObj Box;
public Game1()
{
_ = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
IsMouseVisible = true;
}
protected override void LoadContent()
{
Point center = Window.ClientBounds.Center;
Box = new PhyObj(new Vector2(center.X, 0), Vector2.Zero, 30, 30);
Ground = new Rectangle(0, center.Y, center.X * 2, 10);
_batch = new SpriteBatch(GraphicsDevice);
Texture = new Texture2D(GraphicsDevice, 1, 1);
Texture.SetData(new[] { Color.White });
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
Box.Accelerate(GravityAcceleration, gameTime);
if (Box.Pos.Y > Ground.Top - Box.Dest.Height)//Check if bounce needed
{
Box.Pos.Y = Ground.Top - Box.Dest.Height;//Clipping
Box.Vel.Y *= -1; //Bouncing
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
_batch.Begin();
_batch.Draw(Texture, Ground, Color.Black);
_batch.Draw(Texture, Box.Dest, Color.White);
_batch.End();
base.Draw(gameTime);
}
}
public struct PhyObj
{
internal static float friction => 0.005f;
public PhyObj(Vector2 x, Vector2 v, int width, int height)
{
Pos = x;
Vel = v;
Dest = new Rectangle(0, 0, width, height);
(Dest.X, Dest.Y) = ((int)Pos.X, (int)Pos.Y);
}
internal Vector2 Pos, Vel;
internal Rectangle Dest;
public void Accelerate(Vector2 acc, GameTime time)
{
Vel += acc - Vel * friction;
Pos += Vel * (float)time.ElapsedGameTime.TotalSeconds;
(Dest.X, Dest.Y) = ((int)Pos.X, (int)Pos.Y);
}
}
As shown in the Update() function, the PhyObj Box is being accelerated externally (In this case gravity, but you can add your custom external force), and the velocity/position it needs to attain is calculated internally.
The bouncing logic is simple: The Y-component of velocity is inverted.
The "Clipping" process here makes sure that the Box does not cross the Ground object even when the downward acceleration is acting upon it.
The next subsequent bounces have their height reduced due to the friction value (Done internally by the struct PhyObj).

Mouse position is offset in MonoGame

So I'm implementing shooting in my game, but when I resize the window or move (because of my camera) the mouse position gets offset and the shots obviously don't hit where I want them to, and I don't know how to fix it. Here's the relevant code:
Shot.cs
public class Shot : Sprite {
private float Speed = 500;
private Vector2 Dir;
private Vector2 Velocity;
public Shot(Texture2D texture, Vector2 position)
: base(texture) {
this.Position = position;
this.Dir = Input.GetMousePos() - Position;
}
void Start() {
}
public void Update(GameTime gameTime) {
var delta = (float)gameTime.ElapsedGameTime.TotalSeconds;
Dir.Normalize();
Velocity = Dir * Speed;
Position += Velocity * delta;
}
}
Camera.cs GetTransform() (please note that I didn't write this)
public Matrix GetTransform(GraphicsDevice graphicsDevice) {
transform = Matrix.CreateTranslation(new Vector3(-Position.X, -Position.Y, 0)) *
Matrix.CreateRotationZ(Rotation) *
Matrix.CreateScale(new Vector3(Zoom, Zoom, 1)) *
Matrix.CreateTranslation(new Vector3(graphicsDevice.Viewport.Width * 0.5f, graphicsDevice.Viewport.Height * 0.5f, 1));
return transform;
}
spriteBatch.Begin();
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.PointClamp, null, null, null, camera.GetTransform(GraphicsDevice));
Also wanted to add that this isn't a shooting specific problem, the position also gets offset when I try to drag a GUI window, so I think I need to do modify my GetMousePosition method, but I'm not sure how.
EDIT: Here's GetMousePos()
public static Vector2 GetMousePos() {
return new Vector2(mouseState.X, mouseState.Y);
}

In Unity3D, what is the best way to offset the viewport with multiple resolutions so the player is always to the left edge?

I'm working on my latest project, which is a action sidescroller, primarily 2D but using a perspective camera for 3D effect. I've gotten the basics working - design, etc, and now fixing the camera so that it follows correctly. However, I'd like to keep the player on the left side of the viewport - not actually modifying the player's position, but the camera following an offset. I have it partially worked out, but it broke the moment I started playing around with other alternative resolutions - say, from web standard, standalone, to mobile devices. This is what I want to do:
This is my code attempt thus far. The camera itself works perfectly for basic up/down right scrolling, but when the resolution changes the player can vanish:
public Transform TrackTarget;
public float dampTime = 0.15f;
public bool Ready = false;
private Vector3 velocity = Vector3.zero;
private Vector3 TargetOffset = Vector3.zero;
void Start()
{
TargetOffset = new Vector3(16,0,0);
Vector3 point = TrackTarget.position + TargetOffset;
Vector3 Destination = new Vector3(point.x, transform.position.y, transform.position.x);
transform.position = Destination;
}
void FixedUpdate () {
if (Ready) {
if (TrackTarget)
{
Vector3 point = TrackTarget.position + TargetOffset;
Vector3 Destination = new Vector3(point.x, transform.position.y, transform.position.x);
transform.position = Vector3.SmoothDamp(transform.position, Destination, ref velocity, dampTime);
}
}
}
void LateUpdate()
{
if (Ready) {
Vector3 CameraPosition = transform.position;
CameraPosition.z = -30.00f;
transform.position = CameraPosition;
}
}
This may be helpful.
public class CameraController : MonoBehaviour {
public Transform target;
public float distance = 5;
public float heigt = 1;
public float width = 6;
public float damping = 5;
void Start()
{
Vector3 unicornPos = camera.WorldToViewportPoint(target.position);
width = camera.ViewportToWorldPoint(new Vector3(1, 1, camera.nearClipPlane)).x/3;
}
void LateUpdate () {
Vector3 wantedPosition = target.TransformPoint (width, heigt, -distance);
transform.position = Vector3.Lerp (transform.position, wantedPosition, Time.deltaTime * damping);
}
}

Rotate the player in XNA

I've been looking around for a tutorial on how to do this properly and Google keeps coming up empty. Maybe this is a really simple subject, but I'm not sure what to do.
What I've found so far gives me code that looks like this:
if (currentKeyboardState.IsKeyDown(Keys.A))
{
RotationAngle += 0.01f;
float circle = MathHelper.Pi * 2;
RotationAngle = RotationAngle % circle;
}
Only, this doesn't do much. It came from MSDN. That was the best-looking solution I could find.
All I want to do is allow the player to spin their ship around and shoot in another direction.
I'm making a game that bears a fair resemblance to asteroids, but I can only shoot in one direction at the moment.
Any help would be appreciated :)
Edit: This is my current Player.cs:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace GameTest
{
class Player
{
public Texture2D PlayerTexture;
public Vector2 Position;
public bool Alive;
public int Health;
public Vector2 origin;
public float RotationAngle;
KeyboardState currentKeyboardState;
KeyboardState previousKeyboardState;
public int Width
{
get { return PlayerTexture.Width; }
}
public int Height
{
get { return PlayerTexture.Height; }
}
public void Initialize(Texture2D texture, Vector2 position)
{
PlayerTexture = texture;
Position = position;
Alive = true;
Health = 10;
origin.X = PlayerTexture.Width / 2;
origin.Y = PlayerTexture.Height / 2;
}
public void Update(GameTime gameTime)
{
RotationAngle += 10f;
previousKeyboardState = currentKeyboardState;
currentKeyboardState = Keyboard.GetState();
float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
if (currentKeyboardState.IsKeyDown(Keys.A))
{
//float circle = MathHelper.Pi * 2;
//RotationAngle = RotationAngle % circle;
}
if (currentKeyboardState.IsKeyDown(Keys.D))
{
//rotation
}
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(PlayerTexture, Position, null, Color.White, RotationAngle, origin, 1f, SpriteEffects.None, 0f);
}
}
}
My guess is you are missing the 'origin', or the center of rotation:
RotationAngle += .5f;
float circle = MathHelper.Pi * 2;
RotationAngle = RotationAngle % circle;
Vector2 origin = new Vector2(texture.Width / 2, texure.Height / 2);
spriteBatch.Draw(texture, position, null, Color.White, RotationAngle, origin, 1.0f, SpriteEffects.None, 0f);
As for the angle you want to shoot at, you'll need some geometry (something like this, not tested... but if I remember correctly):
Vector2 velocity = new Vector2(speed*Math.Cos(RotationAngle), speed*Math.Sin(RotationAngle));
Also, use the debugger to make sure that the rotation angle is properly changing.

Sprite vibrates. What is wrong?

My enemy sprite vibrates all the time after it has reached the variable endposition. What is wrong? Why is it vibrating?
In addition, the variable next_position is never true. But why? I want that the enemy sprite moves to a new random endposition after it reached the current endposition.
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D enemy;
Vector2 position, endposition;
bool next_position = false;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.PreferredBackBufferWidth = 1280;
graphics.PreferredBackBufferHeight = 720;
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
enemy = Content.Load<Texture2D>("zombie");
Random randomstart = new Random();
position = new Vector2(randomstart.Next(100, 200), randomstart.Next(100, 200));
endposition = new Vector2(randomstart.Next(100, 600), randomstart.Next(100, 400));
}
protected override void Update(GameTime gameTime)
{
float delta = (float)gameTime.ElapsedGameTime.TotalSeconds;
Random random = new Random();
position.X += 5 * delta;
position.Y += 3 * delta;
if (next_position == true)
{
endposition = new Vector2(random.Next(100, 600), random.Next(100, 400));
next_position = false;
}
if (Vector2.Dot(endposition - position, endposition - position) > 0 || Vector2.Dot(endposition - position, endposition - position) < 0)
{
Vector2 enemyDirection = Vector2.Normalize(endposition - position) * 100f;
position += enemyDirection * delta;
}
else
{
next_position = true;
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
spriteBatch.Draw(enemy, position, Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
}
I am slightly confused by what the line
if (Vector2.Dot(endposition - position, endposition - position) > 0 || Vector2.Dot(endposition - position, endposition - position) < 0)
is supposed to do exactly.
From the rest of the code what you are trying to achieve with it, is checking whether the sprite's position is the enemy's position.
What the line actually does is calculating dot products of vectors in opposite directions, which will never be zero, unless the vectors are zero. So in theory this might actually work, but only if the position equals the enemy's position precisely.
However, you are moving the sprite a fixed distance per unit of time, and so it actually does not reach the enemy but overshoots it. Finding itself then on the other side it moves back, where it came from, and overshoots it again. This continues and results in the vibration you describe.
Now, the way the behaviour you want to implement is usually done is calculating the distance between the two objects(sprite and enemy), using Pythagoras' theorem, and then accepting the arrival of the object when the distance is either below a certain threshold(easy but prone to error), or shorter than the distance the object could travel this particular update(in your case, since you move by a normalized vector times 100f * delta, this distance is equal to 100f * delta).
I hope my explanation is helpful.
Of course, feel free to ask for clarifications.
Edit(response to comment):
The limit vector you are creating is relatively meaningless, as it is just the position offset by max_distance in both x and y direction. There is no need for anything like that.
However, the other if() includes pretty much the code you want, check this out:
// first you calculate the distance we can move this update
float max_distance = 100f * delta;
// now, we compare the actual distance to the end position to that maximum
// if the actual distance is larger, then we are not there yet
// otherwise, (if the actual distance is smaller) we will overshoot the target,
// thus we are in fact there! (=close enough to continue)
// (note: we compare the distances squared to avoid a square root,
// which makes this check much faster, which is good practice,
// in case we ever scale this up to more objects)
if ((position - endposition).LengthSquared() > max_distance * max_distance)
{
/* we are not yet there, continue moving! */
}
else
{
/* we are there! */
}
It really is this simple and no other check is needed. Let me know if this works out for you!
It’s better now, but not perfect. The enemy sprite doesn’t vibrate anymore, but it always moves some pixels down right if it has reached the end position. Why does it always move some pixels down right?
The rest works fine.
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D enemy;
Vector2 position, endposition;
bool next_position = false;
int count_nextposition;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.PreferredBackBufferWidth = 1280;
graphics.PreferredBackBufferHeight = 720;
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
enemy = Content.Load<Texture2D>("zombie");
Random randomstart = new Random();
position = new Vector2(randomstart.Next(100, 200), randomstart.Next(100, 200));
endposition = new Vector2(randomstart.Next(100, 600), randomstart.Next(100, 400));
count_nextposition = randomstart.Next(90, 121);
}
protected override void Update(GameTime gameTime)
{
float delta = (float)gameTime.ElapsedGameTime.TotalSeconds;
count_nextposition -= 1;
Random random = new Random();
position.X += 5 * delta;
position.Y += 3 * delta;
if (count_nextposition <= 0)
{
if (next_position == true)
{
endposition = new Vector2(random.Next(100, 600), random.Next(100, 400));
next_position = false;
}
float max_distance = 100f * delta;
if ((position - endposition).LengthSquared() > max_distance * max_distance)
{
Vector2 enemyDirection = Vector2.Normalize(endposition - position) * 100f;
position += enemyDirection * delta;
}
else
{
next_position = true;
count_nextposition = random.Next(90, 121);
}
}
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
spriteBatch.Draw(enemy, position, Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
}

Categories

Resources