Libgdx's World Units - java

I've been trying to learn libgdx a little but now i'm kinda confused about world units there that i don't even know what exact question i should be asking.
Anyway, i've been reading this example game code here
and from what i understand, when there's no camera, SpriteBatch renders everything in relation to device resolution, so pixels, but when we make a camera, set it's "size", position and then use batch.setProjectionMatrix(camera.combined), batch translates pixels to units and then it knows where to render , but in this game there's collision detection between Rectangle objects, and when you're setting position of this Rectangle with rect.set(x, y, 1, 1); where x and y are world units and not pixels, how does rectangle know if it should use those x and y as units and not as pixels if there's nothing used like setProjectionMatrix to let it know that now we're working in units and not in pixels, and then there's this 1, 1); at the end, is it in units too? if so, then how does Rectangle know how big those units shoud be (Scale in game is 1 / 16 for example) renderer = new OrthogonalTiledMapRenderer(map, 1 / 16f);.
I don't even know if this question really makes sense, but that's where i'm at and i'm really lost here
EDIT: Ok, i understand how units work now, but collision still confuses me a bit, here's example, i created this
// onCreate
mapSprite = new Sprite(new Texture(Gdx.files.internal("space.png")));
planetTexture = new Texture(Gdx.files.internal("planet.png"));
shapeRenderer = new ShapeRenderer();
//Planet(X,Y,Radius)
planet = new Planet(20, 30, 7);
planet2 = new Planet(70, 50, 8);
circle = new Circle(planet.getPosition().x + planet.radius, planet.getPosition().y + planet.radius, planet.radius);
circle2 = new Circle(planet2.getPosition().x + planet2.radius, planet2.getPosition().y + planet2.radius, planet2.radius);
mapSprite.setPosition(0,0);
mapSprite.setSize(133, 100);
stateTime = 0;
camera = new OrthographicCamera();
camera.setToOrtho(false, 133, 100);
camera.position.set(133 /2, 100 /2, 0);
camera.update();
batch = new SpriteBatch();
...
// Render Method
batch.setProjectionMatrix(camera.combined);
batch.begin();
mapSprite.draw(batch);
batch.draw(planetTexture, planet.getPosition().x, planet.getPosition().y, planet.radius * 2, planet.radius * 2);
batch.draw(planetTexture, planet2.getPosition().x, planet2.getPosition().y, planet2.radius * 2, planet2.radius * 2);
batch.end();
shapeRenderer.setProjectionMatrix(camera.combined);
shapeRenderer.setColor(Color.RED);
shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);
shapeRenderer.circle(circle.x, circle.y, circle.radius + 1);
shapeRenderer.end();
And everything renders fine, the ShapeRenderer circle is where it's supposed to be after setting ShapeRenderer's projection matrix shapeRenderer.setProjectionMatrix(camera.combined) :
But, i still don't understand how to check collision on this circle, when i do this in render method and press on the circle nothing is happening, like i don't get that log and i assume that's because the circle doesn't know the world scale (no nothing like setprojectionmatrix ) and render coordinates and where the actual circle "thinks" it is don't match. How do i check collision on that circle?
if(Gdx.input.justTouched()) {
if(circle.contains(Gdx.input.getX(), Gdx.input.getY())) {
Gdx.app.log("SCREEN", "TOUCHED AND CONTAINS");
}
}
EDIT2: I get it now, everything works, i just wasn't using camera.unproject on touch coordinates

Imagine you have a device 1200 x 800 pixel big.
Now you say you will have a box2d rectangle on position: 100, 100 and size: 100,100
When you now use your SpriteBatch (Box2dDebugRenderer) to render this you will see a world 1200 x 800 units big. You must try to forget to think in pixels. Important is that the SpriteBatch always draw 1200 x 800 pixels but not always 1200 x 800 units, later more.
So this is what you see:
When we now use a Camera we say we will only our world of 600 x 400 units.
We create a Camera with size: 600 x 400 units!
camera = new OrthographicCamera(600, 400);
The batch still draw 1200 x 800 units and 1200 x 800 pixels
Now with batch.setProjectionMatrix(camera.combined); you give the batch a Matrix to calculate the size of a unit. With this Matrix he can calculate how big it musst draw 1 unit.
And after that the SpriteBatch draw 600 x 400 units but still 1200 x 800 pixels.
And then you see:
For this reason you can forget to think in pixels the Matrix of camera makes the calculation for you from pixel to units and you can focus on thinking in units.
The next thing why you must think in units is that box2D calculates in meters. So the rectangle is 100 x 100 meters big and 100 meter above the bottom. So by gravity of g = 9,81m/s the rectangle falls not so fast as you might expect.
The SpriteBatch always draw 1200 x 800 pixels otherwise you only see the game on the half of your screen. But with the Matrix of camera the batch know how many pixel he must draw 1 unit and so you see a World of 600 x 400 units on a Screen of 1200 x 800 pixels.
Attention: 1 unit is not always equal 1 meter. For example in a spaceship game there are for example 5 kilometre between two ships. So please don't create a World of 10000 x 10000 units because Box2d won't calculate that.
Then you can say 1 unit = 100 meter, so you have a World of 100 x 100 units.
Now you must remember when a ship should be 200 m/s fast you must set body.setLinearVelocity(2,0) because 1 unit = 100 meter.
So always think in units!
Edit:
Sometimes we do not come around pixel for example Gdx.input
Gdx.input return the position in pixel.
Now our camera have two methods to convert from pos in pixel to pos of our units and vice versa.
float x = Gdx.input.getX();
float y = Gdx.input.getY();
camera.unproject(new Vector3(x, y, 0));//this converts a Vector3 position of pixels to a Vector3 position of units
camera.project(new Vector3(10,10,0));//this converts a Vector3 position of units to a Vector3 position of pixels
I hope I could help you

When rendering things using a camera, you should be setting the projection matrix of the sprite batch to the combined view of the camera.
The world is completely separate from the camera. The world has coordinates both positive and negative. The camera is your view of the world, and like a literal camera, it can move around in space. When you move the game camera, you are merely changing what part of the world you can see.
All physics is done on pixel level (in your case) relative to the world origin. It is not changed in any way by the camera.

Related

Rotating Coordinates (Java and Geometry)

I am working on a 2D java game engine using AWT canvas as a basis. Part of this game engine is that it needs to have hitboxes with collision. Not just the built in rectangles (tried that system already) but I need my own Hitbox class because I need more functionality. So I made one, supports circular and 4-sided polygon shaped hitboxes. The way the hitbox class is setup is that it uses four coordinate points to serve as the 4 corner vertices that connect to form a rectangle. Lines are draw connecting the points and these are the lines that are used to detect intersections with other hitboxes. But I now have a problem: rotation.
There are two possibilities for a box hitbox, it can just be four coordinate points, or it can be 4 coordinate points attached to a gameobject. The difference is that the former is just 4 coordinates based on 0,0 as the ordin while the attached to gameobject stores offsets in the coordinates rather than raw location data, so (-100,-100) for example represents the location of the host gameobject but 100 pixels to the left, and 100 pixels up.
Online I found a formula for rotating points about the origin. Since Gameobject based hitboxes were centered around a particular point, I figured that would be the best option to try it on. This code runs each tick to update a player character's hitbox
//creates a rectangle hitbox around this gameobject
int width = getWidth();
int height = getHeight();
Coordinate[] verts = new Coordinate[4]; //corners of hitbox. topLeft, topRight, bottomLeft, bottomRight
verts[0] = new Coordinate(-width / 2, -height / 2);
verts[1] = new Coordinate(width / 2, -height / 2);
verts[2] = new Coordinate(-width / 2, height / 2);
verts[3] = new Coordinate(width / 2, height / 2);
//now go through each coordinate and adjust it for rotation
for(Coordinate c : verts){
if(!name.startsWith("Player"))return; //this is here so only the player character is tested
double theta = Math.toRadians(rotation);
c.x = (int)(c.x*Math.cos(theta)-c.y*Math.sin(theta));
c.y = (int)(c.x*Math.sin(theta)+c.y*Math.cos(theta));
}
getHitbox().vertices = verts;
I appologize for poor video quality but this is what the results of the above are: https://www.youtube.com/watch?v=dF5k-Yb4hvE
All related classes are found here: https://github.com/joey101937/2DTemplate/tree/master/src/Framework
edit: The desired effect is for the box outline to follow the character in a circle while maintaining aspect ratio as seen here: https://www.youtube.com/watch?v=HlvXQrfazhA . The current system uses the code above, the effect of which can be seen above in the previous video link. How should I modify the four 2D coordinates to maintain relative aspect ratio throughout a rotation about a point?
current rotation system is the following:
x = x*Cos(theta) - y *Sin(theta)
y = x*Sin(theta) + y *Cos(theta)
where theta is degree of rotation in raidians
You made classic mistake:
c.x = (int)(c.x*Math.cos(theta)-c.y*Math.sin(theta));
c.y = (int)(c.x*Math.sin(theta)+c.y*Math.cos(theta));
In the second line you use modified value of c.x. Just remember tempx = c.x
before calculations and use it.
tempx = c.x;
c.x = (int)(tempx*Math.cos(theta)-c.y*Math.sin(theta));
c.y = (int)(tempx*Math.sin(theta)+c.y*Math.cos(theta));
Another issue: rounding coordinates after each rotation causes distortions and shrinking after some rotations. It would be wise to store coordinates in floats and round them only for output, or remember starting values and apply rotation by accumulated angle to them.

libGDX: Box2D velocity not increasing enough

If got a game project where I'm using box2d. Now in my MovementSystem (I'm using a Entity-Component-Based-Approach), I want Box2D to move my objects arround, according to the desired velocities which are set by the controls.
Unfortunately the velocities seems never to get high enough. Even when doing an applyLinearImpulse with a velocity-vector (the desired velocity) of 245044.23 for each axis for example, the resulting velocity of the body just became something about 90.0. What am I'm doing wrong? Is there a limitation or something?
Here's my code for running the velocity-update and world-step:
//************************
// physics-system
//************************
public void update(float deltaTime) {
float frameTime = Math.min(deltaTime, 0.25f);
accumulator += frameTime;
if (accumulator >= MAX_STEP_TIME) {
world.step(MAX_STEP_TIME, 6, 2);
accumulator -= MAX_STEP_TIME;
for (Entity entity : entities) {
TransformComponent transform = tim.get(entity);
BodyComponent bodyComp = bod.get(entity);
VelocityComponent velocity = vel.get(entity);
Vector2 bodyVelocity = bodyComp.body.getLinearVelocity();
float velChangeX = velocity.horizontalVelocity - bodyVelocity.x;
float velChangeY = velocity.verticalVelocity - bodyVelocity.y;
float impulseX = bodyComp.body.getMass() * velChangeX;
float impulseY = bodyComp.body.getMass() * velChangeY;
bodyComp.body.applyLinearImpulse(new Vector2(impulseX, impulseY), bodyComp.body.getWorldCenter(),
false);
// update transform
Vector2 position = bodyComp.body.getPosition();
transform.x = (int) position.x;
transform.y = (int) position.y;
// slowingdownVelocitys(velocity);
}
}
}
And here the definiton of my currently only entity with a box2D-Component (called a BodyComponent):
Entity entity = new Entity();
//...
BodyComponent bodyComponent = new BodyComponent();
BodyDef bodyDef = new BodyDef();
bodyDef.type = BodyDef.BodyType.DynamicBody;
bodyDef.position.set(transformComponent.getX(), transformComponent.getY());
bodyComponent.body = GameManager.getB2dWorld().createBody(bodyDef);
bodyComponent.body.applyAngularImpulse(50f, true);
CircleShape circle = new CircleShape();
circle.setRadius(2f);
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = circle;
fixtureDef.density = 10f;
fixtureDef.friction = 0.4f;
fixtureDef.restitution = 0.6f; // Make it bounce a little bit
bodyComponent.body.createFixture(fixtureDef);
circle.dispose();
entity.add(bodyComponent);
//...
Box2D does limit velocities yes. The limit is there basically to avoid inaccuracies of floating point arithmetic. A velocity of 245044.23 with a step time of 1/60th of a second is well above this limit as you've discovered. If you could lower your step time to say 1/200000th of a second you could simulate 245044.23 meters per second but I think most of us would have trouble getting that to run in real-time. At a step rate of 60 steps per second (each step being only 1/60 of a simulated second), the velocity you've stated is approximately 4084 meters per step since Box2D units are basically MKS units (meters, kilograms, seconds). The velocity limit meanwhile is limited per step to 2 meters per step (by b2_maxTranslation). This limit can be increased but as it's increased you're more likely to see less physical-like behaviors like tunneling.
As to what you're doing wrong, besides trying to use a velocity way higher than Box2D can handle, usually problems like you've described are the result of the visual scaling used. Keep in mind that Box2D positions are in units of meters while most of our monitors are significantly smaller (in either horizontal or vertical direction) than a meter. The Box2D FAQ has this to say about scaling in terms of pixels:
Suppose you have a sprite for a character that is 100x100 pixels. You
decide to use a scaling factor that is 0.01. This will make the
character physics box 1m x 1m. So go make a physics box that is 1x1.
Now suppose the character starts out at pixel coordinate (345,679). So
position the physics box at (3.45,6.79). Now simulate the physics
world. Suppose the character physics box moves to (2.31,4.98), so move
your character sprite to pixel coordinates (231,498). Now the only
tricky part is choosing a scaling factor. This really depends on your
game. You should try to get your moving objects in the range 0.1 - 10
meters, with 1 meter being the sweet spot.
The question that you'll want to answer is what scaling to use such that the physical velocities which Box2D can handle can translate to the visual effect in a way that can still be seen. At the step simulation of 1/60 of second (per step), the limit of velocity that Box2D will deal with is +/- 120 meters per second. But with clever use of scaling between world coordinates and graphical coordinates, that can be like 120 units-of-distance per second where you can sort of make the units-of-distance to be kilometers or tera-meters or whatever.
Beware that going really slow can pose problems too - like running into the velocity threshold for collision responses (b2_velocityThreshold).
Hope this helps!

Libgdx Box2D - Draw Sprite on Body - Heavy problems

I'm heaving heavy problems with drawing a Sprite on a Box2D body.
I'm creating a platformer and I did draw a sprite on a body before but then realized that my gravity is really floaty. After googling I found out that I should work with meters when using Box2D and I changed my code to work with a pixel to meter conversion ratio of 25.
Since then I can't get everything to work though, my sprite just won't draw on my body.
Camera:
float width = Gdx.graphics.getWidth() * PIXELS_TO_METERS;
float height = Gdx.graphics.getHeight() * PIXELS_TO_METERS;
camera = new OrthographicCamera(width / 2, height / 2);
camera.position.set(camera.viewportWidth / 2, camera.viewportHeight / 2, 0);
camera.update();
Here is the code for my body:
idleRegion = new TextureRegion(xeonTexture, 20, 13, 50, 65);
xeonSprite = new Sprite(idleRegion);
//Physics
bodyDef = new BodyDef();
bodyDef.type = BodyDef.BodyType.DynamicBody;
bodyDef.position.set(100 * PIXELS_TO_METERS, 100 * PIXELS_TO_METERS);
bodyDef.fixedRotation = true;
body = world.createBody(bodyDef);
PolygonShape shape = new PolygonShape();
shape.setAsBox((xeonSprite.getWidth() / 2) * PIXELS_TO_METERS, (xeonSprite.getHeight() / 2) * PIXELS_TO_METERS);
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = shape;
fixtureDef.density = 1f;
fixtureDef.friction = 1f;
fixtureDef.restitution = 0f;
fixtureDef.isSensor = false;
physicsFixture = body.createFixture(fixtureDef);
Here is how I set the position of my sprite:
final float widthD2 = (xeonSprite.getWidth() / 2);
final float heightD2 = (xeonSprite.getHeight() / 2);
final float angle = this.getBodyAngle();
xeonSprite.setOrigin(widthD2, heightD2);
xeonSprite.setPosition(body.getPosition().x - xeonSprite.getWidth() / 2, body.getPosition().y - xeonSprite.getHeight() / 2);
xeonSprite.setRotation((float) Math.toRadians(angle));
I also tried the following:
xeonSprite.setPosition(body.getPosition().x - xeonSprite.getWidth() / 2 * METERS_TO_PIXELS, body.getPosition().y - xeonSprite.getHeight() / 2 * METERS_TO_PIXELS);
And here is how I draw my Sprite:
penguinBatch.begin();
xeon.getPenguinSprite(stateTime, Gdx.graphics.getDeltaTime()).draw(penguinBatch);
penguinBatch.end();
This is yet another case of "Pixels do not exist in your game world", they are only there to represent you game world to your client. A camera man for TV need to know as much about your TV as you need to know about your clients screens. Just capture what you want to show and let LibGDX do the rest.
So, you should never, ever work with pixels. In the case you want pixel perfect drawing you might want to setup your camera to the amount of pixels of the screen you are targeting but after that you treat these "pixels" as units.
Box2D does not work with pixel per meter. It works with 1 unit == 1 meter and you should stick to that. But how much meters a pixel should represent is still up to you.
If you want to draw a 1m x 1m box you should not multiply by some number you create based of screen pixels like you do now, you should just give it 1 x 1 to represent the box.
shape.setAsBox(1, 1);
Your sprite should be exactly the same
xeonSprite.setSize(1, 1);
And you should position them on the same position. It's really that simple, 1 UNIT == 1m and there is nothing more to it, the camera does the rest. If you want to show 9m x 16m of your game world you setup your camera like this.
camera = new OrthographicCamera(9, 16);
If you want to represent the empire state building you would give the body and sprite a size of 57, 443 and then your camera needs to represent a much larger area, if you don't want to render a small portion of it. If you want to fit the height exactly on screen without stretching you need your own conversion.
float camHeight = 443;
float aspectRatio = Gdx.graphics.width / Gdx.graphics.height
float camWidth = aspectRatio * 443;
Although those Gdx calls give the pixels your clients are running you should still not treat these as pixels about these since you don't know at what screen I will be playing your game.
Now forget about the empire state building example and your camera's center is positioned at 0, 0 world space so bottom left is at -4.5, -8 so you might want to translate it and don't forget to .update the camera. You are currently doing this in your code.
If you start drawing your sprite and updating Box2D you will see a ball drop from the center of your screen. Of course you need to keep updating your sprites position to match the body position for it to move along with the Box2D body.
Just forget pixels, unless you want to code your own camera or viewports, which you probably do not want because you chose LibGDX. The rest of your game code does not need to work with pixels in any way. The OrthographicCamera can calculate world position to screen position for you by camera.unproject(touchVector).

Libgdx Box2D pixel to meter conversion?

When trying to program a game using Box2D, I ran into a problem with Box2D. I filled in pixel numbers for the lengths of the the textures and sprites to create a box around it. Everything was at the right place, but for some reason everything went very slowly. By looking on the internet I found out that if you didn't convert pixels to meters box2d might handle shapes as very large objects. this seemed to be a logical cause of everything moving slowly.
I found similar questions on this site, but the answers didn't really seem to help out. in most of the cases the solution was to make methods to convert the pixel numbers to meters using a scaling factor. I tried this out, but everything got misplaced and had wrong sizes. this seemed logical to me since the numbers where changed but had the same meaning.
I was wondering if there is a way to make the pixels mean less meters, so everything whould be at the same place with the same (pixel) size, but mean less meters.
If you have a different way which you think might help, I whould also like to hear it..
Here is the code i use to create the camera
width = Gdx.graphics.getWidth() / 5;
height = Gdx.graphics.getHeight() / 5;
camera = new OrthographicCamera(width, height);
camera.setToOrtho(false, 1628, 440);
camera.update();
This is the method I use to create an object:
public void Create(float X, float Y, float Width, float Height, float density, float friction, float restitution, World world){
//Method to create an item
width = Width;
height = Height;
polygonDef = new BodyDef();
polygonDef.type = BodyType.DynamicBody;
polygonDef.position.set(X + (Width / 2f), Y + (Height / 2f));
polygonBody = world.createBody(polygonDef);
polygonShape = new PolygonShape();
polygonShape.setAsBox(Width / 2f, Height / 2f);
polygonFixture = new FixtureDef();
polygonFixture.shape = polygonShape;
polygonFixture.density = density;
polygonFixture.friction = friction;
polygonFixture.restitution = restitution;
polygonBody.createFixture(polygonFixture);
}
To create an item, in this case a table, I use the following:
Table = new Item();
Table.Create(372f, 60f, 152f, 96f, 1.0f, 0.2f, 0.2f, world);
The Sprites are drawn on the item by using the following method:
public void drawSprite(Sprite sprite){
polygonBody.setUserData(sprite);
Utils.batch.begin();
if(polygonBody.getUserData() instanceof Sprite){
Sprite Sprite = (Sprite) polygonBody.getUserData();
Sprite.setPosition(polygonBody.getPosition().x - Sprite.getWidth() / 2, polygonBody.getPosition().y - Sprite.getHeight() / 2);
Sprite.setRotation(polygonBody.getAngle() * MathUtils.radiansToDegrees);
Sprite.draw(Utils.batch);
}
Utils.batch.end();
}
The sprites also have pixel sizes.
Using this methods it displays the images at the right places, but everything moves slowly.
I was wondering how or if I whould have to change this to make the objects move correctly, and / or mean less. Thanks in advance.
Box2D is an entirely independent of the graphics library that you use. It doesn't have any notion of sprites and textures. What you read online is correct, you'll have to convert pixels to metres, as Box2D works with metres(the standard unit for distance).
For example, if you drew a sprite of size 100x100 pixels, that's the size of the sprite that you want the user to see on the screen. In real world the size of the object should be in metres and not in pixels - so if you say 1px = 1m, then that'll map the sprite to a gigantic 100x100 meter object. In Box2D, large world objects will slow down calculations. So what you need to do is map the 100 pixels to a smaller number of meters, say, 1 meter - thus 100x100px sprite will be represented in Box2D world by a 1x1 meter object.
Box2D doesn't work well with very small numbers and very large numbers. So keep it in between, say between 0.5 and 100, to have good performance.
EDIT:
Ok. Now I get your question.
Don't code to pixels. Its as simple as that. I know it'll take some time to understand this(it took for me). But once you get the hang of it, its straight forward.
Instead of pixels, use a unit, say, you call it meter.
So we decide our viewport should be say 6mx5m.
So initialization is
Constants.VIEWPORT_WIDTH = 6;
Constants.VIEWPORT_HEIGHT = 5;
...
void init() {
camera = new OrthographicCamera(Constants.VIEWPORT_WIDTH, Constants.VIEWPORT_HEIGHT);
camera.position.set(Constants.VIEWPORT_WIDTH/2, Constants.VIEWPORT_HEIGHT/2, 0);
camera.update();
}
Once you know the actual width and height, you call the following function in order to maintain aspect ratio:
public void resize(int width, int height) {
camera.viewportHeight = (Constants.VIEWPORT_WIDTH / width) * height;
camera.update();
}
resize() can be called anytime you change your screen size(eg: when you screen orientation changes). resize() takes the actual width and height (320x480 etc), which is the pixel value.
Now you specify you sprite sizes, their positions etc. in this new world of size 6x5. You can forget pixels. The minimum size of the sprite that'll fill the screen will be 6x5.
You can now use the same unit with Box2D. Since the new dimensions will be smaller, it won't be a problem for Box2D. If I remember correctly Box2D doesn't have any unit. We just call it meter for convenience sake.
Now you might ask where you specify the dimensions of the window. It depends on the platform. Following code shows a 320x480 windowed desktop game:
public class Main {
public static void main(String[] args) {
LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
cfg.title = "my-game";
cfg.useGL20 = false;
cfg.width = 480;
cfg.height = 320;
new LwjglApplication(new MyGame(), cfg);
}
}
Our camera will intelligently map the 6x5 viewport to 480x320.

Java LWJGL - Vertical Shooting Bugs Out

So i've made my own FPS, graphics and guns and all of that cool stuff; When we fire, the bullet should take a random direction inside the crosshair, as defined by:
float randomX=(float)Math.random()*(0.08f*guns[currentWeapon].currAcc)-(0.04f*guns[currentWeapon].currAcc);
float randomY=(float)Math.random()*(0.08f*guns[currentWeapon].currAcc)-(0.04f*guns[currentWeapon].currAcc);
bulletList.add(new Bullet(new float[]{playerXpos, playerYpos, playerZpos}, new float[]{playerXrot+randomX, playerYrot+randomY}, (float) 0.5));
We calculate the randomness in X and Y (say you had a crosshair size (guns[currentWeapon].currAcc) of 10, then the bullet could go 0.4 to any side and it would remain inside the crosshair.
After that is calculated, we send the player position as the starting position of the bullet, along with the direction it's meant to take (its the player's direction with that extra randomness), and finally it's speed (not important atm, tho).
Now, each frame, the bullets have to move, so for each bullet we call:
position[0] -= (float)Math.sin(direction[1]*piover180) * (float)Math.cos(direction[0]*piover180) * speed;
position[2] -= (float)Math.cos(direction[1]*piover180) * (float)Math.cos(direction[0]*piover180) * speed;
position[1] += (float)Math.sin(direction[0]*piover180) * speed;
So, for X and Z positions, the bullet moves according to the player's rotation on the Y and X axis (say you were looking horizontally into Z, 0 degrees on X and 0 on Y; X would move 0*1*speed and Z would move 1*1*speed).
For Y position, the bullet moves according to the rotation on X axis (varies between -90 and 90), meaning it stays at the same height if the player's looking horizontally or moves completely up if the player is looking vertically.
Now, the problem stands as follows:
If i shoot horizontally, everything works beautifully. Bullets spread around the cross hair, as seen in https://dl.dropbox.com/u/16387578/horiz.jpg
The thing is, if i start looking up, the bullets start concentrating around the center, and make this vertical line the further into the sky i look.
https://dl.dropbox.com/u/16387578/verti.jpg
The 1st image is around 40ยบ in the X axis, the 2nd is a little higher and the last is when i look vertically.
What am i doing wrong here? I designed this solution myself can im pretty sure im messing up somewhere, but i cant figure it out :/
Basicly the vertical offset calculation (float)Math.cos(direction[0]*piover180) was messing up the X and Z movement because they'd both get reduced to 0. The bullets would make a vertical line because they'd rotate on the X axis with the randomness. My solution was to add the randomness after that vertical offset calculation, so they still go left and right and up and down after you fire them.
I also had to add an extra random value otherwise you'd just draw a diagonal line or a cross.
float randomX=(float)Math.random()*(0.08f*guns[currentWeapon].currAcc)-(0.04f*guns[currentWeapon].currAcc);
float randomY=(float)Math.random()*(0.08f*guns[currentWeapon].currAcc)-(0.04f*guns[currentWeapon].currAcc);
float randomZ=(float)Math.random()*(0.08f*guns[currentWeapon].currAcc)-(0.04f*guns[currentWeapon].currAcc);
bulletList.add(new Bullet(new float[]{playerXpos, playerYpos, playerZpos}, new float[]{playerXrot, playerYrot}, new float[]{randomX,randomY, randomZ},(float) 0.5));
And the moving code...
vector[0]= -((float)Math.sin(dir[1]*piover180) * (float)Math.cos(dir[0]*piover180)+(float)Math.sin(random[1]*piover180)) * speed;
vector[1]= ((float)Math.sin(dir[0]*piover180)+(float)Math.sin(random[0]*piover180)) * speed;
vector[2]= -((float)Math.cos(dir[1]*piover180) * (float)Math.cos(dir[0]*piover180)+(float)Math.sin(random[2]*piover180)) * speed;
You didn't need to bust out any complex math, your problem was that when you were rotating the bullet around the y axis for gun spread, if you were looking directly up (that is, through the y axis, the bullet is being rotated around the path which its going, which means no rotation whatsoever (imagine the difference between sticking your arm out forwards towards a wall and spinning in a circle, and sticking you arm out towards the sky and spinning in a circle. Notice that your hand doesn't move at all when pointed towards the sky (the y-axis)) and so you get those "diagonal" bullet spreads.
The trick is to do the bullet spread before rotating by the direction the player is looking in, because that way you know that when you are rotating for spread, that the vector is guaranteed to be perpendicular to the x and y axes.
this.vel = new THREE.Vector3(0,0,-1);
var angle = Math.random() * Math.PI * 2;
var mag = Math.random() * this.gun.accuracy;
this.spread = new THREE.Vector2(Math.cos(angle) * mag,Math.sin(angle) * mag);
this.vel.applyAxisAngle(new THREE.Vector3(0,1,0),this.spread.y / 100); //rotate first when angle gaurenteed to be perpendicular to x and y axes
this.vel.applyAxisAngle(new THREE.Vector3(1,0,0),this.spread.x / 100);
this.vel.applyAxisAngle(new THREE.Vector3(1,0,0),player.looking.x); //then add player looking direction
this.vel.applyAxisAngle(new THREE.Vector3(0,1,0),player.looking.y);
this.offset = this.vel.clone()
I don't use java but I hope you get the main idea of what im doing by this javascript. I am rotating a vector going in the negative z direction (default direction of camera) by the spread.y, and spread.x, and then I am rotating by the pitch and yaw of the angle at which the player is facing.

Categories

Resources