How to make the camera follow 2 objects? - java

I'm making my first 3D game and it's a sort of streetfighter/tekken game. I've seen how the some of the camera modes works ie. chasecam and camnode. What I dont know is how to make the camera follow both players at the same time. I want the camera to zoom in when the players are near each others, and zoom out when they are not.
I would appreciate any help with ideas or possible solutions.
Thanks

You can achieve this fairly easily be manually setting the camera position provided you know two pieces of information, where you want the camera to be, and where you want it to look
Where the camera should look
This is the easiest thing to determine, the camera should be looking at the midway position of the two objects. Once you know that you can set where the camera is looking using
Vector3f boxsCentre=box1.getWorldTranslation().add(box2.getWorldTranslation()).mult(0.5f);
cam.lookAt(boxsCentre, Vector3f.UNIT_Y);
Where the camera should be
Where the camera should be is more tricky. You know it should be somewhere on the line that extends from the centre of the two objects in a direction perpendicular to the line between those two objects. Thankfully the cross product gives us this. We want the camera to always be on the same level as the objects so by crossing the seperation vector by a vector going straight up we get that perpendicular line
Vector3f seperationVector=box2.getWorldTranslation().subtract(box1.getWorldTranslation());
Vector3f perpendicularFromTheAction= seperationVector.cross(Vector3f.UNIT_Y);
perpendicularFromTheAction.normalizeLocal();
So, that gives us the line, but where on the line should we put the camera. I just played around with this and found that twice the distance between the objects gives a nice look, so
float distance=2*seperationVector.length();
Vector3f newCameraLocation=boxsCentre.add(perpendicularFromTheAction.mult(distance));
Then you can set the cameras position
cam.setLocation(newCameraLocation);
Putting it all together
I've used this code together with two boxes that are moving on a loop to demostrate this, as you can see you get the effect you want
import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.*;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
public class FightTest extends SimpleApplication {
Geometry box1;
Geometry box2;
public static void main(String[] args) {
FightTest app = new FightTest();
app.start();
}
#Override
public void simpleInitApp() {
//put in some reference boxes
for(int i=-20;i<=20;i+=20){
for(int j=-20;j<=20;j+=20){
if (j!=0||i!=0){
Geometry referenceBox = createBox(ColorRGBA.Red);
referenceBox.setLocalTranslation(i, 0, j);
rootNode.attachChild(referenceBox);
}
}
}
//put in our two players
box1 = createBox(ColorRGBA.Blue);
box1.setLocalTranslation(5, 0, 0);
box2 = createBox(ColorRGBA.Green);
rootNode.attachChild(box1);
rootNode.attachChild(box2);
}
private Geometry createBox(ColorRGBA color){
Box b = new Box(Vector3f.ZERO, 1, 1, 1);
Geometry box = new Geometry("Box", b);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", color);
box.setMaterial(mat);
return box;
}
#Override
public void simpleUpdate(float tpf) {
adjustCam();
movePlayers(tpf);
}
#Override
public void simpleRender(RenderManager rm) {
//TODO: add render code
}
private void adjustCam(){
//we want our camera to look at the centre of the boxes
Vector3f boxsCentre=box1.getWorldTranslation().add(box2.getWorldTranslation()).mult(0.5f);
cam.lookAt(boxsCentre, Vector3f.UNIT_Y);
//we also want our camera to move closer/further away as the boxes seperate.
//and move around so its always parallel to the action
//parallel to the action means on the line given by the cross product of the
//box seperation and the upwards vector
Vector3f seperationVector=box2.getWorldTranslation().subtract(box1.getWorldTranslation());
Vector3f perpendicularFromTheAction= seperationVector.cross(Vector3f.UNIT_Y);
perpendicularFromTheAction.normalizeLocal();
//we could (and you should) get complicated on exactly how far the camera should
//move backwards, but I'm just going to make the camera twice as far away as the
//objects are seperated
float distance=2*seperationVector.length();
Vector3f newCameraLocation=boxsCentre.add(perpendicularFromTheAction.mult(distance));
cam.setLocation(newCameraLocation);
}
float timeAccumulator=0;
private void movePlayers(float tpf){
//basic movement, just for demo
timeAccumulator+=tpf;
if (timeAccumulator<2){
box1.move(new Vector3f(5f*tpf,0,0));
box2.move(new Vector3f(0,0,5f*tpf));
}else if (timeAccumulator<4){
box1.move(new Vector3f(-5f*tpf,0,0));
box2.move(new Vector3f(0,0,-5f*tpf));
}else{
timeAccumulator=0;
}
}
}

Related

Is it possible to create a simple animation just with translation and rotation in libGDX?

I'm trying to develop a very simple game using libGDX with boxes (so 3D game) moving and rotating.
I have almost everything ready, but I'm not able to animate my boxes. I mean, when I touch the screen, I'd like my cube to move to the right by rotating 90 degrees and translating 1 (unit) to the right. As result, the right side of the box will be the new base, the old base will be in the left side, and the box is moved to the right.
So, the question is: now that I have the move set correctly (I or at least I hope so), but change is applied immediately; so how can I see the animation between first position and second position ?
Only reference to animation for 3D objects in documentation is about using obj files from blender (and similar), and for movement I need I do not consider it necessary.
Can anybody provide me some help? Thanks in advance!!
You can do that something like this:
public static class YourAnimation {
public ModelInstance instance;
public final Vector3 fromPosition = new Vector3();
public float fromAngle;
public final Vector3 toPosition = new Vector3();
public float toAngle;
public float speed;
public float alpha;
private final static Vector3 tmpV = new Vector3();
public void update(float delta) {
alpha += delta * speed;
if (alpha >= 1f) {
alpha = 1f;
// TODO: do whatever you want when the animation if complete
}
angle = fromAngle + alpha * (toAngle - fromAngle);
instance.transform.setToRotation(Vector3.Y, angle);
tmpV.set(fromPosition).lerp(toPosition, alpha);
instance.transform.setTranslation(tmpV);
}
}
YourAnimation animation = null;
void animate(ModelInstance instance) {
animation = new YourAnimation();
animation.instance = instance;
animation.instance.transform.getTranslation(animation.fromPosition);
animation.toPosition.set(animation.fromPosition).add(10f, 10f, 10f);
animation.fromAngle = 0;
animation.toAngle = 90f;
animation.speed = 1f; // 1 second per second
animation.alpha = 0;
}
public void render() {
final float delta = Math.min(Gdx.graphics.getDeltaTime(), 1/30f);
if (animation != null)
animation.update(delta);
// render model as usual etc.
}
Ofcourse this is just a quick example. The actual implementation will vary depending on the use case. For example you could also extend ModelInstance and keep track of the animation in there. Because it is very specific to the use-case, but very simple to implement, it is usually not worth using tools (like the Universal Tween Engine)
Here is another example I recently wrote for my latest tutorial, perhaps it helps as well. It rotates and moves the cards in this video.

Polygon is created far away the specified position, LibGDX

I've been looking for solutions in google with no success, I'm creating a small library (just a wrapper) for Box2D in LibGDX and I'm drawing a texture for each body, taking as base the Body.getPosition() vector, however, I see polygon's getPosition() is different respect to CircleShapes and the walls (which were created with setAsBox() method).
Here's an image:
http://i.stack.imgur.com/NzooG.png
The red points are the center of mass, the cyan circles are the geometric center (right?) given by body.getPosition(), as you can see I can adapt the texture to the body in terms of position, rotation and scale but this does not happen with polygons (except the ones made with setAsBox()).
Basically, what I want is to get the cyan circle in the AABB centre of the regular polygons. here's a runnable example:
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.math.Vector2;
public class MyGdxGame extends ApplicationAdapter {
Tabox2D t;
float w ,h;
#Override
public void create () {
w = Gdx.graphics.getWidth();
h = Gdx.graphics.getHeight();
t = Tabox2D.getInstance();
t.debug = true;
t.newBall("d", 100, 200, 25);// Ball.
t.newBox("s", 10, 10, w - 20, 50);// Floor.
t.newHeptagon("d", new Vector2(200, 200), 40);
}
#Override
public void render () {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
t.update(0);// Don't move anything just to see the cyan circle.
t.draw();
}
}
Tabox2D class is here: https://github.com/tavuntu/tabox2d
NOTE: this was tested with the last version of Android Studio and LibGDX.
Thanks in advance!
Looks to me like you are positioning the shapes on the centre point, instead of the body. The centre of the shapes should be 0,0, not center.x,y. Fixture positions are relative to the body.
OK, so I think I solved this maybe in an inelegant way. What I did was:
Create the polygons points around the origin
Get AABB center of polygon and centroid (not necessarily the same)
Translate points to make AABB center the shape center, so the image can be drawn respect to this and not the center of mass.
The code needs a lot of cleaning but it seems to work just well, updated code in github repo, thanks!
The change was basically translate points from the original centroid to AABB center:
for(int i = 0; i < pts.length - 1; i += 2) {
pts[i] -= aabbCenter.x;
pts[i + 1] -= aabbCenter.y;
}

How do i get which spot in a 3D world has been clicked in libGdx

I am trying to create a 3d world that behaves similarly to minecraft where a player can look in 360 degrees and if he trys to click on a spot (X,Y,Z coordinate in the 3D world), the model being drawn there gets deleted. I am very new to programming a 3D world in LibGdx so any help is useful. I do camera rotation with this:
float deltaX = -Gdx.input.getDeltaX() * player.degreesPerPixel;
float deltaY = -Gdx.input.getDeltaY() * player.degreesPerPixel;
if(deltaX>0)
player.camera.rotate(Vector3.Z, (float)1.5);
else if(deltaX<0)
player.camera.rotate(Vector3.Z, (float)-1.5);
player.tmp.set(player.camera.direction).crs(player.camera.up).nor();
player.camera.direction.rotate(player.tmp, deltaY);
player.setDir(player.camera.direction.x, player.camera.direction.y);
Thank you
In a 2D environment, you would usually just use Camera.unproject(...), which takes some screenspace point and transforms that back into gameworld coordinates.
In 3D it's not that easy, because of the additional dimension adding some depths to your world. That's why a click on the 2D plane (your screen) could hit basically infinitely many points in the 3D world. In libgdx, this ray of possibly clicked points is called the pick-ray.
If you want the intersection point on a certain plane, the code could look like his:
public void hitSomething(Vector2 screenCoords) {
// If you are only using a camera
Ray pickRay = camera.getPickRay(screenCoords.x, screenCoords.y);
// If your camera is managed by a viewport
Ray pickRay = viewport.getPickRay(screenCoords.x, screenCoords.y);
// we want to check a collision only on a certain plane, in this case the X/Z plane
Plane plane = new Plane(new Vector3(0, 1, 0), Vector3.Zero);
Vector3 intersection = new Vector3();
if (Intersector.intersectRayPlane(pickRay, plane, intersection)) {
// The ray has hit the plane, intersection is the point it hit
} else {
// Not hit
}
}
In your case, when you have a minecraft-like world, your code could look like the following:
public void hitSomething(Vector2 screenCoords) {
Ray pickRay = ...;
// A bounding box for each of your minecraft blocks
BoundingBox boundingBox = new BoundingBox();
Vector3 intersection = tmp;
if (Intersector.intersectRayBounds(pickRay, boundingBox, intersection)) {
// The ray has hit the box, intersection is the point it hit
} else {
// Not hit
}
}
Please not that in a minecraft world there are literally thousands of such blocks. Going this simple approach would not be very fast. You will probably have to end up with a hierarchical solution, that first checks big chunks (bounding boxes that include many blocks) for a possible hit, and then start checking individual blocks.

Touchevents using libgdx and scene2d

i am currently going through some tutorials on android development and libgdx/scene2d atm but got stuck on the touchevents:
For starters i am implementing a board game. By now i managed to draw the board and some meeples (player tokens) as well as adjust their position to the correct coordinates. When i started using libgdx i also successfully implemented a first test to see if the touchinput is working. The test looked something like this:
if(Gdx.input.isTouched()){
touchPos.set(Gdx.input.getX(), Gdx.input.getY(), 0);
camera.unproject(touchPos);
stone.x=touchPos.x - stone.width/2;
stone.y=touchPos.y - stone.height/2;
By now i rearranged my code to use scene2d because of some very convenient features. However it seems like i can't manage to get touchevents to work with scene2d despite i followed some rather simple tutorials. Since some of them reference some recent major changes to scene2d, i wondered if those tutorials might be outdated and hope you guys can help me fix my problem :) I will try and only paste the relevant parts of my code in here to present a minimal example of my non-working code.
Of course i am also glad about any hints on how to "improve" my code in general since i am still learning and probably break some conventions here ^^
Lets start with my actor-class:
//deleted: imports
public class Player extends Actor{
private int xval; //x Position of the Player on the board
private int yval; //y Position of the Player on the board
private Color color;
private TextureRegion playerTexture;
//deleted: some variables that are unimportant for touchevents
public Player(int x, int y, TextureRegion texture){
//initializing some Variables with default values
this.xval = x;
this.yval = y;
color = new Color(1,1,1,1);
playerTexture = texture;
this.setTouchable(Touchable.enabled); //this should be set by default, but i added it just to be sure.
//setBounds seems to be necessary in addition to the "touchable" flag to be able to "grab" or "touch" Actors.
//getX and getY are methods of the actor class which i do not overwrite. I use distinct getters and setters to modify the Variables xval and yval.
setBounds(getX(), getY(), playerTexture.getRegionWidth(), playerTexture.getRegionHeight());
//now i implement the InputListener. This doesnt seem to get called, since the "touch started" message doesn't appear in LogCat.
//In Theory the testevent should move the meeple one space to the right.
//To do that i call event.getTarget() which returns the actor of which the TouchDown is called. I then cast it to my Actor class (Player) and set xval to xval+1.
this.addListener(new InputListener(){
public boolean touchDown(InputEvent event, float x, float y, int pointer, int buttons){
Gdx.app.log("Example", "touch started at (" + x + ", " + y + ")");
((Player)event.getTarget()).setXf(((Player)event.getTarget()).getXf()+1);
return true;
}
});
}
//This is my draw method, which just sets the color of my meeple and then draws it to its current position.
public void draw(Batch batch, float alpha){
batch.setColor(color);
batch.draw(playerTexture, getX(), getY());
}
//deleted: several getters and setters
}
So if i got it right, the gdx inputProcessor is managing all inputs. If i set the Gdx InputProcessor to the stage which contains the actors, it will in case of an event (for example a touchDown) call the inputProcessors of all actors on the stage. Thus since i just added one to my class which includes a touchDown, this should handle the touchDown event in the case that the actor is actually touched. The hitbox to verify that is set by the statement "setBounds" in my Player class.
I implemented this in my ApplicationListener class:
//deleted: imports
public class QuoridorApplicationListener implements ApplicationListener{
Stage stage;
OrthographicCamera camera;
//deleted: several variable declarations.
//deleted: constructor - this only fills some of my variables with values depending on game settings.
#Override
public void create() {
//set stage - since "stage = new Stage(800,400,false)" doesn't seem to work anymore, i did set up a viewport manually. If you got suggestions how to this straightforward, please leave a note :)
Gdx.app.log("Tag", "game started");
camera = new OrthographicCamera();
camera.setToOrtho(false, 800, 480);
Viewport viewport = new Viewport() {};
viewport.setCamera(camera);
stage = new Stage(viewport);
Gdx.input.setInputProcessor(stage); //like i described above, this should forward incoming touchevents to the actors on the stage.
//deleted: setting textures, music, setting up the board (the boardtiles are actors which are added to the stage)
//deleted: setting TextureRegion, player colors, player positions and player attributes.
for (int i = 0; i < playercount; i++){
stage.addActor(players[i]);
}
}
//deleted: dispose(), pause(), resume(), resize() methods.
#Override
public void render() {
camera.update();
for (int i=0; i< playercount; i++){
players[i].setX(/*Formula to determine x-coordinates from Player.xval*/);
players[i].setY(/*Formula to determine x-coordinates from Player.yval*/);
}
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
}
}
I can't figure out what i am missing to get the touchevents working. So i hope you can help me :) Thanks a lot in advance!
I think your problem originates from your manual creation of a Viewport object, which handles the unprojecting internally.
Instead of
stage = new Stage(800,400,false);
you should use
stage = new Stage();
To resize the the viewport to the correct size once a resize event occurs you need to call
stage.getViewport().update( newWidth, newHeight )
in your ApplicationListener's resize method.

openGL: cant move along x or y axis and negative scaling culls the wrong faces

So I've implemented a movable camera using all the projection maths and things. I have some input conditions, when I hit A or D I'm meant to move left and right respectively, but I cant do that, the camera only moves on the Z axis. Not only that but when I scale by a negative number on any axis the cube I'm rendering is inverted and weird, the front face is culled instead of the ones i'm not meant to see. This issue is probably not to do with my quaternion, matrix or vector classes as I copied them from github (as well as following the youtube series connected to them).
to move left or right I use:
public void input(){
float movAmt = (float)(10 * Time.getDelta());
float rotAmt = (float)(100 * Time.getDelta());
if(Input.getKey(Input.KEY_W)){
move(getForward(), movAmt);
}
if(Input.getKey(Input.KEY_S)){
move(getForward(), -movAmt);
}
if(Input.getKey(Input.KEY_A)){
move(getLeft(), movAmt);
}
if(Input.getKey(Input.KEY_D)){
move(getRight(), movAmt);
}
if(Input.getKey(Input.KEY_UP)){
rotateX(-rotAmt);
}
if(Input.getKey(Input.KEY_DOWN)){
rotateX(rotAmt);
}
if(Input.getKey(Input.KEY_LEFT)){
rotateY(-rotAmt);
}
if(Input.getKey(Input.KEY_RIGHT)){
rotateY(rotAmt);
}
}
public void move(Vector3f dir, float amt){
pos = pos.add(dir.mult(amt));
}
public void rotateY(float angle){
Vector3f Haxis = yAxis.cross(forward);
Haxis.normalize();
forward.rotate(angle, yAxis);
forward.normalize();
up = forward.cross(Haxis);
up.normalize();
}
public void rotateX(float angle){
Vector3f Haxis = yAxis.cross(forward);
Haxis.normalize();
forward.rotate(angle, Haxis);
forward.normalize();
up = forward.cross(Haxis);
up.normalize();
}
public Vector3f getLeft(){
return forward.cross(up).normalize();
}
public Vector3f getRight(){
return up.cross(forward).normalize();
}
Where should I look to find the issue? (In general throughout the whole program)
Your question is kinda confusing to me, as I think you're talking about two things.
But when you scale with a negative value like glScalef(1f, -1f, 1f); then you need to invert the front face using glFrontFace();
Example:
// Render stuff
glPushMatrix();
glFrontFace(GL_CW);
// Render negatively scaled stuff
glFrontFace(GL_CCW);
glPopMatrix();
// Render stuff
Note: I'm only using the deprecated methods for show, so if you've created your own matrix stack then you can simply change my example to fit your code.

Categories

Resources