Related
I'm kinda new to the LWJGL library and im trying to draw a simple triangle. Only my code seems okay with it won't draw my triangle. It also gives no errors or anything else. I searched everywhere but found no solution at all. Anyone with a solution?
Code:
public class Cube {
private int width, height;
private int ID, bufferID;
private int count;
private float[] vertices;
public Cube() {
vertices = new float[] {
0, 0, 0,
1, 0, 0,
0, 1, 0,
};
count = vertices.length / 3;
FloatBuffer buffer = BufferUtils.createFloatBuffer(vertices.length);
buffer.put(vertices);
buffer.flip();
ID = GL30.glGenVertexArrays();
GL30.glBindVertexArray(ID);
bufferID = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferID);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
GL20.glEnableVertexAttribArray(0);
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 0, 0);
GL30.glBindVertexArray(0);
GL20.glDisableVertexAttribArray(0);
}
public void render() {
GL30.glBindVertexArray(ID);
GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, count);
GL30.glBindVertexArray(0);
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
public class Launcher {
public static void main(String[] args) {
Window window = new Window("Cube Wave", 800, 600);
window.setBackground(1, 0, 1);
window.setup();
}
}
public class Window {
private String title;
private int width, height;
private Vector3f color;
private long window;
private ArrayList<Cube> cubes = new ArrayList<Cube>();
public Window(String title, int width, int height) {
this.title = title;
this.width = width;
this.height = height;
setBackground(0, 0, 0);
}
public void setup() {
if (!glfwInit()) {
return;
}
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
window = glfwCreateWindow(width, height, title, NULL, NULL);
if (window == NULL) {
return;
}
GLFWVidMode monitor = glfwGetVideoMode(glfwGetPrimaryMonitor());
glfwSetWindowPos(window, (monitor.width() - width) / 2, (monitor.height() - height) / 2);
glfwMakeContextCurrent(window);
glfwSwapInterval(1);
GL.createCapabilities();
glfwShowWindow(window);
loop();
}
public long getWindow() {
return window;
}
public void loop() {
init();
glClearColor(color.x, color.y, color.z, 1.0f);
while(!closed()) {
tick();
render();
}
}
public void render() {
for(Cube cube : cubes) {
cube.render();
}
glfwSwapBuffers(window);
}
public void tick() {
glfwPollEvents();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
public void init() {
cubes.add(new Cube());
}
public boolean closed() {
return glfwWindowShouldClose(window);
}
public void setBackground(float r, float g, float b) {
color = new Vector3f(r, g, b);
}
}
It seems that you have a mistake in your render function.
Below is an example for how to use glDrawArrays in a render function.
public void render() {
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
// bind the VBO and enable the attribute
GL30.glBindVertexArray(ID);
GL20.glEnableVertexAttribArray(0);
GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, count);
// disable the VBO and disable the attribute
GL20.glDisableVertexAttribArray(0);
GL30.glBindVertexArray(0);
}
Also it is considered best practice to unbind buffers when you are done with them. So your initialization function should look like this.
public Cube() {
// ... vertex stuff
ID = GL30.glGenVertexArrays();
GL30.glBindVertexArray(ID);
bufferID = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferID);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 0, 0);
// unbind both the Array Buffer and the Vertex Array
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL30.glBindVertexArray(0);
}
You can find a more in depth discussion and example of this from the lwjgl wiki
I am a little bit confused why my FitViewport is not keeping the aspect ratio when resizing the window.
I thought that it should always keep the aspect ratio and then fill up the screen with black bars for areas which are not used.
However for me it is not keeping the aspect ratio and circles become ellipsis f.e. (see screenshots).
Code when creating my game renderer (32 and 18 are my world units)
viewport = new FitViewport(32, 18);
camera = viewport.getCamera();
visibleArea = new Rectangle(0, 0, viewport.getScreenWidth(), viewport.getScreenHeight());
scissors = new Rectangle();
Code when resizing the window
public void resize(int width, int height) {
Gdx.app.debug(TAG, "Resizing to " + width + " x " + height);
viewport.update(width, height);
visibleArea.set(0, 0, viewport.getScreenWidth(), viewport.getScreenHeight());
Render method
public void render(float alpha) {
viewport.calculateScissors(batch.getTransformMatrix(), visibleArea, scissors);
ScissorStack.pushScissors(scissors);
viewport.apply();
setView(camera.combined, visibleArea.x, visibleArea.y, visibleArea.width, visibleArea.height);
batch.begin();
// ...
batch.end();
ScissorStack.popScissors();
}
correct aspect ratio on startup
wrong aspect ratio on resize
Okay the problem actually was with my framebuffer light method (prepareLightFrameBuffer) which also had a call to batch.begin(); and batch.end().
It seems like this messes up the view (or resets it to something?). To solve the issue I just applied the viewport again and set the view again in the render method (Note: I also have a stage so I think that viewport.apply() has to be called here and also in the stage.render() method).
Here is the complete code of the GameRenderer if anyone is interested. I guess somehow it could be simplified but I am no OpenGL/Matrix expert so I have no idea how to do it :)
package com.lok.game;
import java.util.Comparator;
import com.badlogic.ashley.core.ComponentMapper;
import com.badlogic.ashley.core.Entity;
import com.badlogic.gdx.Application;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.maps.MapLayer;
import com.badlogic.gdx.maps.tiled.TiledMapImageLayer;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;
import com.badlogic.gdx.maps.tiled.tiles.AnimatedTiledMapTile;
import com.badlogic.gdx.math.Intersector;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.viewport.FitViewport;
import com.badlogic.gdx.utils.viewport.Viewport;
import com.lok.game.ecs.components.AnimationComponent;
import com.lok.game.ecs.components.CollisionComponent;
import com.lok.game.ecs.components.MapRevelationComponent;
import com.lok.game.ecs.components.SizeComponent;
import com.lok.game.map.Map;
import com.lok.game.map.Map.Portal;
import com.lok.game.map.MapManager;
public class GameRenderer extends OrthogonalTiledMapRenderer {
private final static String TAG = GameRenderer.class.getName();
private static class yPositionComparator implements Comparator<Entity> {
private final ComponentMapper<SizeComponent> sizeComponentMapper;
private yPositionComparator(ComponentMapper<SizeComponent> sizeComponentMapper) {
this.sizeComponentMapper = sizeComponentMapper;
}
#Override
public int compare(Entity o1, Entity o2) {
if (o1 == o2) {
return 0;
} else if (o1 == null) {
return -1;
} else if (o2 == null) {
return 1;
}
return sizeComponentMapper.get(o1).boundingRectangle.y > sizeComponentMapper.get(o2).boundingRectangle.y ? -1 : 1;
}
}
private SizeComponent cameraLockEntitySizeComponent;
private MapRevelationComponent cameraLockEntityRevelationComponent;
private Map map;
private TiledMapTileLayer groundLayer;
private final Array<TiledMapTileLayer> backgroundLayers;
private final Array<TiledMapTileLayer> foregroundLayers;
private TiledMapImageLayer lightMapLayer;
private final yPositionComparator entityComparator;
private final ComponentMapper<SizeComponent> sizeComponentMapper;
private final ComponentMapper<AnimationComponent> animationComponentMapper;
private final Camera camera;
private final Viewport viewport;
private final Rectangle visibleArea;
private final Rectangle scissors;
private final ShapeRenderer shapeRenderer;
private FrameBuffer frameBuffer;
private final AtlasRegion lightTexture;
private final AtlasRegion shadowTexture;
public GameRenderer() {
super(null, MapManager.WORLD_UNITS_PER_PIXEL);
if (Gdx.app.getLogLevel() == Application.LOG_DEBUG) {
Gdx.app.debug(TAG, "Creating in debug mode");
shapeRenderer = new ShapeRenderer();
} else {
Gdx.app.debug(TAG, "Creating in non-debug mode");
shapeRenderer = null;
}
viewport = new FitViewport(32, 18);
camera = viewport.getCamera();
visibleArea = new Rectangle();
scissors = new Rectangle();
this.backgroundLayers = new Array<TiledMapTileLayer>();
this.foregroundLayers = new Array<TiledMapTileLayer>();
this.sizeComponentMapper = ComponentMapper.getFor(SizeComponent.class);
this.animationComponentMapper = ComponentMapper.getFor(AnimationComponent.class);
this.entityComparator = new yPositionComparator(sizeComponentMapper);
final TextureAtlas textureAtlas = AssetManager.getManager().getAsset("lights/lights.atlas", TextureAtlas.class);
lightTexture = textureAtlas.findRegion("light");
shadowTexture = textureAtlas.findRegion("shadow");
frameBuffer = null;
}
public void setMap(Map map) {
this.map = map;
super.setMap(map.getTiledMap());
this.backgroundLayers.clear();
this.foregroundLayers.clear();
this.lightMapLayer = null;
for (MapLayer mapLayer : map.getTiledMap().getLayers()) {
if (mapLayer instanceof TiledMapTileLayer) {
if ("ground".equals(mapLayer.getName())) {
groundLayer = (TiledMapTileLayer) mapLayer;
} else if (mapLayer.getName().startsWith("background")) {
backgroundLayers.add((TiledMapTileLayer) mapLayer);
} else {
foregroundLayers.add((TiledMapTileLayer) mapLayer);
}
} else if (mapLayer instanceof TiledMapImageLayer) {
lightMapLayer = (TiledMapImageLayer) mapLayer;
}
}
}
public void resize(int width, int height) {
Gdx.app.debug(TAG, "Resizing with " + width + "x" + height + " from viewport " + viewport.getScreenWidth() + "x" + viewport.getScreenHeight());
viewport.update(width, height, false);
visibleArea.set(0, 0, viewport.getWorldWidth(), viewport.getWorldHeight());
Gdx.app.debug(TAG, "To viewport " + viewport.getScreenWidth() + "x" + viewport.getScreenHeight());
if (frameBuffer != null) {
frameBuffer.dispose();
}
try {
frameBuffer = FrameBuffer.createFrameBuffer(Pixmap.Format.RGBA8888, viewport.getScreenWidth(), viewport.getScreenHeight(), false);
} catch (GdxRuntimeException e) {
frameBuffer = FrameBuffer.createFrameBuffer(Pixmap.Format.RGB565, viewport.getScreenWidth(), viewport.getScreenHeight(), false);
}
}
public void lockCameraToEntity(Entity entity) {
if (entity == null) {
cameraLockEntitySizeComponent = null;
cameraLockEntityRevelationComponent = null;
} else {
cameraLockEntityRevelationComponent = entity.getComponent(MapRevelationComponent.class);
cameraLockEntitySizeComponent = entity.getComponent(SizeComponent.class);
if (cameraLockEntitySizeComponent == null) {
throw new GdxRuntimeException("Trying to lock camera to an entity without size component: " + entity);
}
}
}
private void interpolateEntities(float alpha) {
for (Entity entity : map.getEntities()) {
final SizeComponent sizeComp = sizeComponentMapper.get(entity);
final float invAlpha = 1.0f - alpha;
sizeComp.interpolatedPosition.x = sizeComp.interpolatedPosition.x * invAlpha + sizeComp.boundingRectangle.x * alpha;
sizeComp.interpolatedPosition.y = sizeComp.interpolatedPosition.y * invAlpha + sizeComp.boundingRectangle.y * alpha;
}
}
public void render(float alpha) {
AnimatedTiledMapTile.updateAnimationBaseTime();
interpolateEntities(alpha);
map.getEntities().sort(entityComparator);
if (cameraLockEntitySizeComponent != null) {
camera.position.set(cameraLockEntitySizeComponent.interpolatedPosition, 0);
visibleArea.setCenter(cameraLockEntitySizeComponent.interpolatedPosition);
}
prepareLightFrameBuffer();
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
viewport.apply();
setView(camera.combined, visibleArea.x, visibleArea.y, visibleArea.width, visibleArea.height);
batch.begin();
viewport.calculateScissors(batch.getTransformMatrix(), visibleArea, scissors);
ScissorStack.pushScissors(scissors);
if (groundLayer != null) {
renderTileLayer(groundLayer);
}
for (Entity entity : map.getEntities()) {
renderEntityShadow(entity);
}
for (TiledMapTileLayer layer : backgroundLayers) {
renderTileLayer(layer);
}
for (Entity entity : map.getEntities()) {
renderEntity(entity);
}
for (TiledMapTileLayer layer : foregroundLayers) {
renderTileLayer(layer);
}
batch.end();
applyLightFrameBuffer();
if (Gdx.app.getLogLevel() == Application.LOG_DEBUG) {
renderDebugInformation();
}
ScissorStack.popScissors();
}
private void renderEntityShadow(Entity entity) {
final AnimationComponent animationComp = animationComponentMapper.get(entity);
if (animationComp.animation != null) {
final SizeComponent sizeComp = sizeComponentMapper.get(entity);
if (!viewBounds.overlaps(sizeComp.boundingRectangle)) {
return;
}
if (cameraLockEntityRevelationComponent != null && !Intersector.overlaps(cameraLockEntityRevelationComponent.revelationCircle, sizeComp.boundingRectangle)) {
return;
}
batch.draw(shadowTexture, sizeComp.interpolatedPosition.x, sizeComp.interpolatedPosition.y - sizeComp.boundingRectangle.height * 0.2f, sizeComp.boundingRectangle.width,
sizeComp.boundingRectangle.height * 0.5f);
}
}
private void renderEntity(Entity entity) {
final AnimationComponent animationComp = animationComponentMapper.get(entity);
if (animationComp.animation != null) {
final SizeComponent sizeComp = sizeComponentMapper.get(entity);
if (!viewBounds.overlaps(sizeComp.boundingRectangle)) {
return;
}
if (cameraLockEntityRevelationComponent != null && !Intersector.overlaps(cameraLockEntityRevelationComponent.revelationCircle, sizeComp.boundingRectangle)) {
return;
}
final TextureRegion keyFrame = animationComp.animation.getKeyFrame(animationComp.animationTime, true);
batch.draw(keyFrame, sizeComp.interpolatedPosition.x, sizeComp.interpolatedPosition.y, sizeComp.boundingRectangle.width, sizeComp.boundingRectangle.height);
}
}
private void prepareLightFrameBuffer() {
if (cameraLockEntityRevelationComponent != null) {
frameBuffer.begin();
final Color mapBackgroundColor = map.getBackgroundColor();
Gdx.gl.glClearColor(mapBackgroundColor.r, mapBackgroundColor.g, mapBackgroundColor.b, mapBackgroundColor.a);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
setView(camera.combined, visibleArea.x, visibleArea.y, visibleArea.width, visibleArea.height);
batch.begin();
if (lightMapLayer != null) {
batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE);
renderImageLayer(lightMapLayer);
}
batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE);
final Rectangle boundingRectangle = cameraLockEntitySizeComponent.boundingRectangle;
batch.draw(lightTexture, cameraLockEntitySizeComponent.interpolatedPosition.x + boundingRectangle.width * 0.5f - cameraLockEntityRevelationComponent.revelationRadius, // x
cameraLockEntitySizeComponent.interpolatedPosition.y + boundingRectangle.height * 0.5f - cameraLockEntityRevelationComponent.revelationRadius, // y
cameraLockEntityRevelationComponent.revelationRadius * 2f, cameraLockEntityRevelationComponent.revelationRadius * 2f);
batch.end();
frameBuffer.end();
}
}
private void applyLightFrameBuffer() {
if (cameraLockEntityRevelationComponent != null) {
batch.setProjectionMatrix(batch.getProjectionMatrix().idt());
batch.setBlendFunction(GL20.GL_ZERO, GL20.GL_SRC_COLOR);
batch.begin();
batch.draw(frameBuffer.getColorBufferTexture(), -1, 1, 2, -2);
batch.end();
}
}
private void renderDebugInformation() {
shapeRenderer.setProjectionMatrix(camera.combined);
shapeRenderer.begin(ShapeType.Line);
shapeRenderer.setColor(Color.RED);
for (Rectangle rect : map.getCollisionAreas()) {
shapeRenderer.rect(rect.x, rect.y, rect.width, rect.height);
}
for (Entity entity : map.getEntities()) {
final CollisionComponent collisionComponent = entity.getComponent(CollisionComponent.class);
final SizeComponent sizeComp = sizeComponentMapper.get(entity);
if (collisionComponent != null) {
shapeRenderer.setColor(Color.RED);
shapeRenderer.rect(sizeComp.interpolatedPosition.x + collisionComponent.rectOffset.x, sizeComp.interpolatedPosition.y + collisionComponent.rectOffset.y,
collisionComponent.collisionRectangle.width, collisionComponent.collisionRectangle.height);
}
if (sizeComp != null) {
shapeRenderer.setColor(Color.BLUE);
shapeRenderer.rect(sizeComp.interpolatedPosition.x, sizeComp.interpolatedPosition.y, sizeComp.boundingRectangle.width, sizeComp.boundingRectangle.height);
}
}
shapeRenderer.setColor(Color.BLUE);
for (Portal portal : map.getPortals()) {
shapeRenderer.rect(portal.getArea().x, portal.getArea().y, portal.getArea().width, portal.getArea().height);
}
if (cameraLockEntityRevelationComponent != null) {
shapeRenderer.setColor(Color.WHITE);
shapeRenderer.circle(cameraLockEntitySizeComponent.interpolatedPosition.x + cameraLockEntitySizeComponent.boundingRectangle.width * 0.5f,
cameraLockEntitySizeComponent.interpolatedPosition.y + cameraLockEntitySizeComponent.boundingRectangle.height * 0.5f,
cameraLockEntityRevelationComponent.revelationCircle.radius, 64);
}
shapeRenderer.end();
}
#Override
public void dispose() {
Gdx.app.debug(TAG, "Disposing Gamerenderer");
super.dispose();
if (shapeRenderer != null) {
shapeRenderer.dispose();
}
if (frameBuffer != null) {
frameBuffer.dispose();
}
}
}
This is the code from a book I am reading. It is just a snake game. you can paste this code into editor and change 3 texture files to what you have on computer. The code works fine and this is probably really stupid, but I am unable to make the connection between snakeBody parts and their position. How exactly does the body of the snake(not the head) knows where it should go after the head of the snake changes position? Can you elaborate on this? Thank you
// class 1/2:
public class MyGdxGame extends Game {
#Override
public void create () {
setScreen(new GameScreen());
}
}
class 2/2:
package com.mygdx.game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.ScreenAdapter;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.utils.Array;
public class GameScreen extends ScreenAdapter{
private static final float MOVE_TIME = 0.5F;
private static final int SNAKE_MOVEMENT = 32;
private static final int RIGHT = 0;
private static final int LEFT = 1;
private static final int UP = 2;
private static final int DOWN = 3;
private SpriteBatch batch;
private Texture snakeHead;
private Texture snakeBody;
private Texture apple;
private boolean appleAvailable = false;
private int appleX, appleY;
private float timer = MOVE_TIME;
private int snakeX = 0, snakeY = 0;
private int snakeXBeforeUpdate = 0, snakeYBeforeUpdate = 0;
private int snakeDirection = RIGHT;
private Array<BodyPart> bodyParts = new Array<BodyPart>();
#Override
public void show() {
super.show();
batch = new SpriteBatch();
snakeHead = new Texture(Gdx.files.internal("snakehead.png"));
snakeBody = new Texture(Gdx.files.internal("snakeBody.png"));
apple = new Texture(Gdx.files.internal("apple.png"));
}
#Override
public void render(float delta) {
super.render(delta);
queryInput();
timer -= delta;
if (timer <= 0) {
timer = MOVE_TIME;
moveSnake();
checkForOutOfBounds();
updateBodyPartsPosition();
}
checkAppleCollision();
checkAndPlaceApple();
clearScreen();
draw();
}
private void queryInput() {
boolean lPressed = Gdx.input.isKeyPressed(Input.Keys.LEFT);
boolean rPressed = Gdx.input.isKeyPressed(Input.Keys.RIGHT);
boolean uPressed = Gdx.input.isKeyPressed(Input.Keys.UP);
boolean dPressed = Gdx.input.isKeyPressed(Input.Keys.DOWN);
if (lPressed) snakeDirection = LEFT;
if (rPressed) snakeDirection = RIGHT;
if (uPressed) snakeDirection = UP;
if (dPressed) snakeDirection = DOWN;
}
private void moveSnake() {
snakeXBeforeUpdate = snakeX;
snakeYBeforeUpdate = snakeY;
switch (snakeDirection) {
case RIGHT: {
snakeX += SNAKE_MOVEMENT;
return;
}
case LEFT: {
snakeX -= SNAKE_MOVEMENT;
return;
}
case UP: {
snakeY += SNAKE_MOVEMENT;
return;
}
case DOWN: {
snakeY -= SNAKE_MOVEMENT;
return;
}
}
}
private void checkForOutOfBounds() {
if (snakeX >= Gdx.graphics.getWidth()) {
snakeX = 0;
}
if (snakeX < 0) {
snakeX = Gdx.graphics.getWidth() - SNAKE_MOVEMENT;
}
if (snakeY >= Gdx.graphics.getHeight()) {
snakeY = 0;
}
if (snakeY < 0) {
snakeY = Gdx.graphics.getHeight() - SNAKE_MOVEMENT;
}
}
private void updateBodyPartsPosition() {
if (bodyParts.size > 0) {
BodyPart bodyPart = bodyParts.removeIndex(0);
bodyPart.updateBodyPosition(snakeXBeforeUpdate, snakeYBeforeUpdate);
bodyParts.add(bodyPart);
}
}
private void checkAndPlaceApple() {
if (!appleAvailable) {
do {
appleX = MathUtils.random(Gdx.graphics.getWidth() / SNAKE_MOVEMENT - 1) * SNAKE_MOVEMENT;
appleY = MathUtils.random(Gdx.graphics.getHeight() / SNAKE_MOVEMENT - 1) * SNAKE_MOVEMENT;
appleAvailable = true;
} while (appleX == snakeX && appleY == snakeY);
}
}
private void checkAppleCollision() {
if (appleAvailable && appleX == snakeX && appleY == snakeY) {
BodyPart bodyPart = new BodyPart(snakeBody);
bodyPart.updateBodyPosition(snakeX, snakeY);
bodyParts.insert(0,bodyPart);
appleAvailable = false;
}
}
private void clearScreen() {
Gdx.gl.glClearColor(Color.BLACK.r, Color.BLACK.g, Color.BLACK.b, Color.BLACK.a);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
}
private void draw() {
batch.begin();
batch.draw(snakeHead, snakeX, snakeY);
for (BodyPart bodyPart : bodyParts) {
bodyPart.draw(batch);
}
if (appleAvailable) {
batch.draw(apple, appleX, appleY);
}
batch.end();
}
private class BodyPart {
private int x, y;
private Texture texture;
public BodyPart(Texture texture) {
this.texture = texture;
}
public void updateBodyPosition(int x, int y) {
this.x = x;
this.y = y;
}
public void draw(Batch batch) {
if (!(x == snakeX && y == snakeY)) batch.draw(texture, x, y);
}
}
}
First it moves the head in moveSnake(), making sure to keep track of where the head was before the move. Then in updateBodyPartsPosition() it puts the last piece of the body where the head was just moved from.
After several days of struggling I came here. I'm trying to pass a custom per-vertex vec3 attribute to a custom shader based on this tutorial. The tutorial describes how to pass a custom uniform which actually works fine. However when I'm trying to modify the code to pass my custom per-vertex attribute it seems that nothing is transferred to vertex shader and I can't figure out how to make it to work.
So far I've done the following:
I've created several boxes with modelBuilder.createBox() (so I know for sure every model has 24 vertexes)
Then I'v generated a FloatBuffer containing actual attribute data like this:
int[] data = new int[]{x1, y1, z1, x1, y1, z1, ...}
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(data.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
mAttributeBuffer = byteBuffer.asFloatBuffer();
mAttributeBuffer.put(data);
mAttributeBuffer.position(0);
Then I'm initializing the corresponding attribute location variable (successfully, a_coord >= 0):
a_coord = program.getAttributeLocation("a_coord");
After that on libgdx side in custom shader's render(Renderable) method I'm passing the buffer to OpenGL like this:
program.setVertexAttribute(a_coord, 3, Gdx.gl20.GL_FLOAT, false, 0, mAttributeBuffer);
My custom vertex shader is as the following:
attribute vec3 a_position;
attribute vec3 a_normal;
attribute vec2 a_texCoord0;
uniform mat4 u_worldTrans;
uniform mat4 u_projTrans;
varying vec2 v_texCoord0;
//my custom attribute
attribute vec2 a_coord;
void main() {
v_texCoord0 = a_texCoord0;
float posY = a_position.y + a_coord.y;
gl_Position = u_projTrans * u_worldTrans * vec4(a_position.x, posY, a_position.z, 1.0);
}
The problem
At the moment a_coord is 0 for every vertex. What am I missing and how to correctly pass custom attribute to vertex shader?
I'm guessing the problem is somewhere in VBO field and the way libGDX passes attribute data to vertexes but I still can't figure out how to make it work.
I'll be glad if anyone can point me in the right direction on this question.
Complete code:
Main AplicationListener class:
public class ProtoGame implements ApplicationListener {
public ProtoGame()
{
super();
}
public PerspectiveCamera cam;
public CameraInputController camController;
public Model model;
public Array<ModelInstance> instances = new Array<ModelInstance>();
public ModelBatch modelBatch;
#Override
public void create () {
cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
cam.position.set(0f, 8f, 8f);
cam.lookAt(0,0,0);
cam.near = 1f;
cam.far = 300f;
cam.update();
camController = new CameraInputController(cam);
Gdx.input.setInputProcessor(camController);
ModelBuilder modelBuilder = new ModelBuilder();
model = modelBuilder.createBox(1f, 1f, 1f,
new Material(),
VertexAttributes.Usage.Position | VertexAttributes.Usage.Normal | VertexAttributes.Usage.TextureCoordinates);
Color colorU = new Color(), colorV = new Color();
for (int x = -5; x <= 5; x+=2) {
for (int z = -5; z<=5; z+=2) {
ModelInstance instance = new ModelInstance(model, x, 0, z);
//this is where I'll put per-vertex attribute data for every instance
//but for now it's hardcoded in the Shader class so the data is the same across instances
TestShader.DoubleColorAttribute attr = new TestShader.DoubleColorAttribute(TestShader.DoubleColorAttribute.DiffuseUV,
colorU.set((x+5f)/10f, 1f - (z+5f)/10f, 0, 1),
colorV.set(1f - (x+5f)/10f, 0, (z+5f)/10f, 1));
instance.materials.get(0).set(attr);
instances.add(instance);
}
}
modelBatch = new ModelBatch(new BaseShaderProvider() {
#Override
protected Shader createShader(Renderable renderable) {
return new TestShader();
}
});
}
#Override
public void render () {
camController.update();
Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
modelBatch.begin(cam);
for (ModelInstance instance : instances)
modelBatch.render(instance);
modelBatch.end();
}
#Override
public void dispose () {
model.dispose();
modelBatch.dispose();
}
}
Custom libgdx shader class:
public class TestShader implements Shader {
private FloatBuffer mAttributeBuffer;
ShaderProgram program;
Camera camera;
RenderContext context;
int u_projTrans;
int u_worldTrans;
int u_colorU;
int u_colorV;
int a_coord;
private static String getCustomVertexShader() {
return Gdx.files.internal("shader/test.vertex.glsl").readString();
}
private static String getCustomFragmentShader() {
return Gdx.files.internal("shader/test.fragment.glsl").readString();
}
#Override
public void init() {
program = new ShaderProgram(getCustomVertexShader(), getCustomFragmentShader());
if (!program.isCompiled())
throw new GdxRuntimeException(program.getLog());
//tutorial's logic to init custom uniform locations
u_projTrans = program.getUniformLocation("u_projTrans");
u_worldTrans = program.getUniformLocation("u_worldTrans");
u_colorU = program.getUniformLocation("u_colorU");
u_colorV = program.getUniformLocation("u_colorV");
//initing custom attribute location
a_coord = program.getAttributeLocation("a_coord");
//generating data and passing it to nio Buffer
float data[] = generateData();
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(data.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
mAttributeBuffer = byteBuffer.asFloatBuffer();
mAttributeBuffer.put(data);
mAttributeBuffer.position(0);
}
private float[] generateData() {
Vector3[] dataArray = new Vector3[1];
dataArray[0] = new Vector3(2, 2, 2);
int components = 3;
int vertexPerModel = 24;
float[] data = new float[dataArray.length * components * vertexPerModel];
for(int i = 0; i < dataArray.length; ++i){
int i3 = i * components;
for(int j = 0; j < vertexPerModel; ++j) {
int j3 = j * components;
data[i3 + 0 + j3] = dataArray[i].x;
data[i3 + 1 + j3] = dataArray[i].y;
data[i3 + 2 + j3] = dataArray[i].z;
}
}
return data;
}
#Override
public void dispose() {
program.dispose();
}
#Override
public void begin(Camera camera, RenderContext context) {
this.camera = camera;
this.context = context;
program.begin();
program.setUniformMatrix(u_projTrans, camera.combined);
context.setDepthTest(GL20.GL_LEQUAL);
context.setCullFace(GL20.GL_BACK);
}
#Override
public void render(Renderable renderable) {
program.setUniformMatrix(u_worldTrans, renderable.worldTransform);
//tutorial's logic to pass uniform
DoubleColorAttribute attribute = ((DoubleColorAttribute) renderable.material.get(DoubleColorAttribute.DiffuseUV));
program.setUniformf(u_colorU, attribute.color1.r, attribute.color1.g, attribute.color1.b);
program.setUniformf(u_colorV, attribute.color2.r, attribute.color2.g, attribute.color2.b);
//passing my custom attributes to the vertex shader
program.setVertexAttribute(a_coord, 3, Gdx.gl20.GL_FLOAT, false, 0, mAttributeBuffer);
renderable.mesh.render(program, renderable.primitiveType,
renderable.meshPartOffset, renderable.meshPartSize);
}
#Override
public void end() {
program.end();
}
#Override
public int compareTo(Shader other) {
return 0;
}
#Override
public boolean canRender(Renderable renderable) {
return renderable.material.has(DoubleColorAttribute.DiffuseUV);
}
}
Finally I was able to pass a custom attribute to vertex shader! Thanks a lot to #Xoppa for pointing me in the right direction.
This is the working solution I've got so far (I'm open for any further advices on how to implement it in a more elegant way):
First of all, as Xoppa stated in the comment it's required to create a model providing custom vertex structure while building it. So model creation may look like this:
VertexAttribute posAttr = new VertexAttribute(VertexAttributes.Usage.Position, 3, ShaderProgram.POSITION_ATTRIBUTE);
...
VertexAttribute customVertexAttr = new VertexAttribute(512, 3, "a_custom");
VertexAttributes vertexAttributes = new VertexAttributes(
posAttr,
...
customVertexAttr);
ModelBuilder modelBuilder = new ModelBuilder();
modelBuilder.begin();
modelBuilder.
part("box", GL20.GL_TRIANGLES, vertexAttributes, new Material()).
box(1f, 1f, 1f);
model = modelBuilder.end();
Or the same with MeshBuilder:
MeshBuilder meshBuilder = new MeshBuilder();
VertexAttributes vertexAttributes = new VertexAttributes(...);
meshBuilder.begin(vertexAttributes);
meshBuilder.part("box", GL20.GL_TRIANGLES);
meshBuilder.setColor(color);
meshBuilder.box(1f, 1f, 1f);
Mesh mesh = meshBuilder.end();
This code will create model with vertices containing additional data according to the provided attributes. It's time to fill the corresponding vertex array. You need a mesh for this - it stores vertices array - a flat array of packed attributes one after another vertex by vertex. So what you need is a number of attributes per vertex as well as an offset for attribute which needs to be modified. Mesh stores all that data:
Mesh mesh = model.meshes.get(0);
int numVertices = mesh.getNumVertices();
// vertex size and offset are in byte so we need to divide it by 4
int vertexSize = mesh.getVertexAttributes().vertexSize / 4;
//it's possible to use usage int here passed previously to VertexAttribute constructor.
VertexAttribute customAttribute = mesh.getVertexAttribute(512)
int offset = customAttribute.offset / 4;
float[] vertices = new float[numVertices * vertexSize];
mesh.getVertices(vertices);
We are ready to pass the data:
List<Vector3> customData ...
for(int i = 0; i < numVertices; ++i){
int index = i * vertexSize + offset;
vertices[index + 0] = customData.get(i).x;
vertices[index + 1] = customData.get(i).y;
vertices[index + 2] = customData.get(i).z;
}
And don't forget to pass the updated vertices array back to the mesh:
mesh.updateVertices(0, vertices);
That's it.
Here's also an implementation of a helper method to create a mix of default attributes using Usage flags alongside with custom attributes:
private VertexAttributes createMixedVertexAttribute(int defaultAtributes, List<VertexAttribute> customAttributes){
VertexAttributes defaultAttributes = MeshBuilder.createAttributes(defaultAtributes);
List<VertexAttribute> attributeList = new ArrayList<VertexAttribute>();
for(VertexAttribute attribute: defaultAttributes){
attributeList.add(attribute);
}
attributeList.addAll(customAttributes);
VertexAttribute[] typeArray = new VertexAttribute[0];
VertexAttributes mixedVertexAttributes = new VertexAttributes(attributeList.toArray(typeArray));
return mixedVertexAttributes;
}
The full source:
public class ProtoGame implements ApplicationListener {
private static final int CUSTOM_ATTRIBUTE_USAGE = 512;
public ProtoGame()
{
super();
}
public PerspectiveCamera cam;
public CameraInputController camController;
public Model model;
public Array<ModelInstance> instances = new Array<ModelInstance>();
public ModelBatch modelBatch;
#Override
public void create () {
cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
cam.position.set(0f, 8f, 8f);
cam.lookAt(0, 0, 0);
cam.near = 1f;
cam.far = 300f;
cam.update();
camController = new CameraInputController(cam);
Gdx.input.setInputProcessor(camController);
Model model = createModelWithCustomAttributes();
Mesh mesh = model.meshes.get(0);
setCustomAttributeData(mesh);
Color colorU = new Color(), colorV = new Color();
for (int x = -5; x <= 5; x+=2) {
for (int z = -5; z<=5; z+=2) {
ModelInstance instance = new ModelInstance(model, x, 0, z);
TestShader.DoubleColorAttribute attr = new TestShader.DoubleColorAttribute(TestShader.DoubleColorAttribute.DiffuseUV,
colorU.set((x+5f)/10f, 1f - (z+5f)/10f, 0, 1),
colorV.set(1f - (x+5f)/10f, 0, (z+5f)/10f, 1));
instance.materials.get(0).set(attr);
instances.add(instance);
}
}
modelBatch = new ModelBatch(new BaseShaderProvider() {
#Override
protected Shader createShader(Renderable renderable) {
return new TestShader();
}
});
}
#Override
public void render () {
camController.update();
Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
modelBatch.begin(cam);
for (ModelInstance instance : instances)
modelBatch.render(instance);
modelBatch.end();
}
private Model createModelWithCustomAttributes() {
int defaultAttributes = VertexAttributes.Usage.Position | VertexAttributes.Usage.Normal | VertexAttributes.Usage.TextureCoordinates;
VertexAttribute customVertexAttr = new VertexAttribute(CUSTOM_ATTRIBUTE_USAGE, 3, "a_custom");
List<VertexAttribute> customAttributeList = new ArrayList<VertexAttribute>();
customAttributeList.add(customVertexAttr);
VertexAttributes vertexAttributes = createMixedVertexAttribute(defaultAttributes, customAttributeList);
ModelBuilder modelBuilder = new ModelBuilder();
modelBuilder.begin();
modelBuilder.
part("box", GL20.GL_TRIANGLES, vertexAttributes, new Material()).
box(1f, 1f, 1f);
return modelBuilder.end();
}
private void setCustomAttributeData(Mesh mesh) {
int numVertices = mesh.getNumVertices();
int vertexSize = mesh.getVertexAttributes().vertexSize / 4;
int offset = mesh.getVertexAttribute(CUSTOM_ATTRIBUTE_USAGE).offset / 4;
float[] vertices = new float[numVertices * vertexSize];
mesh.getVertices(vertices);
for(int i = 0; i < numVertices; ++i){
int index = i * vertexSize + offset;
vertices[index + 0] = i;
vertices[index + 1] = i;
vertices[index + 2] = i;
}
mesh.updateVertices(0, vertices);
}
#Override
public void dispose () {
model.dispose();
modelBatch.dispose();
}
private VertexAttributes createMixedVertexAttribute(int defaultAtributes, List<VertexAttribute> customAttributes){
VertexAttributes defaultAttributes = MeshBuilder.createAttributes(defaultAtributes);
List<VertexAttribute> attributeList = new ArrayList<VertexAttribute>();
for(VertexAttribute attribute: defaultAttributes){
attributeList.add(attribute);
}
attributeList.addAll(customAttributes);
VertexAttribute[] typeArray = new VertexAttribute[0];
VertexAttributes mixedVertexAttributes = new VertexAttributes(attributeList.toArray(typeArray));
return mixedVertexAttributes;
}
#Override
public void resize(int width, int height) {
}
#Override
public void pause() {
}
#Override
public void resume() {
}
}
Vertex shader:
attribute vec3 a_position;
attribute vec3 a_normal;
attribute vec2 a_texCoord0;
uniform mat4 u_worldTrans;
uniform mat4 u_projTrans;
varying vec2 v_texCoord0;
attribute vec3 a_custom;
void main() {
v_texCoord0 = a_texCoord0;
float posX = a_position.x + a_custom.x;
float posY = a_position.y + a_custom.y;
float posZ = a_position.z + a_custom.z;
gl_Position = u_projTrans * u_worldTrans * vec4(posX, posY, posZ, 1.0);
}
Fragment shader
#ifdef GL_ES
precision mediump float;
#endif
uniform vec3 u_colorU;
uniform vec3 u_colorV;
varying vec2 v_texCoord0;
void main() {
gl_FragColor = vec4(v_texCoord0.x * u_colorU + v_texCoord0.y * u_colorV, 1.0);
}
Based on Xoppa's comment, I managed to get custom attributes working. It's actually so simple! Here is an ES 3.0 example:
#version 300 es
//v30.glsl
in vec3 vertexPosition;
in vec3 vertexColor;
out vec3 outColor;
void main()
{
outColor = vertexColor;
gl_Position = vec4(vertexPosition, 1.0);
}
fragment shader:
#version 300 es
//f30.glsl
precision mediump float;
in vec3 outColor;
out vec4 fragColor;
void main()
{
fragColor = vec4(outColor, 1.0);
}
Gl30Mesh.java
package com.example.jgles;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
public class Gl30Mesh extends ApplicationAdapter
{
private Mesh triangleMesh;
private ShaderProgram passShader;
#Override
public void create()
{
final float [] combinedData = {
-0.8f, -0.8f, 0.0f, 1.0f, 0.0f, 0.0f,
0.8f, -0.8f, 0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.8f, 0.0f, 0.0f, 0.0f, 1.0f
};
VertexAttribute vertexChannel = new VertexAttribute(VertexAttributes.Usage.Generic, 3, "vertexPosition");
VertexAttribute colorChannel = new VertexAttribute(VertexAttributes.Usage.Generic, 3, "vertexColor");
String vertexShader = Gdx.files.internal("v30.glsl").readString();
String fragmentShader = Gdx.files.internal("f30.glsl").readString();
passShader = new ShaderProgram(vertexShader, fragmentShader);
if (!passShader.isCompiled()){
throw new IllegalStateException(passShader.getLog());
}
passShader.bind();
triangleMesh = new Mesh(true, 3, 0, vertexChannel, colorChannel);
triangleMesh.setVertices(combinedData);
}
#Override
public void render()
{
Gdx.gl.glViewport(0, 0, 640, 480);
Gdx.gl.glClearColor(0, 0, 1.0f, 0);
Gdx.gl.glClear(GL30.GL_COLOR_BUFFER_BIT);
triangleMesh.render(passShader, GL30.GL_TRIANGLES);
}
}
It seems to me when you are already using custom shaders, you use whatever is named in your shaders instead of POSITION_ATTRIBUTE, NORMAL_ATTRIBUTE etc. And as Xoppa said, ShaderProgram.setVertexAttribute simply won't work.
Actually it is possible to call setVertexAttributes directly and have it work without needing a mesh. Just need to extend ShaderProgram like so
public class ShaderProgramExtended extends ShaderProgram {
public ShaderProgramExtended(String v, String f){
super(v,f);
}
/*
This is VERY NAUGHTY. Mario and Nathan probably made it private for a reason
*/
public int getProgram(){
int result;
try{
Field field = ShaderProgram.class.getDeclaredField("program");
field.setAccessible(true);
Object value = field.get(this);
field.setAccessible(false);
if (value == null) {
result = 0;
}else{
result = (Integer) value;
}
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
return result;
}
public void begin(int program){
Gdx.gl20.glUseProgram(program);
}
public void draw(int mode, int first, int count){
Gdx.gl20.glDrawArrays(mode,first,count);
}
}
And then call it as you would normally with the "minor" change of sending the shader object integer to the new begin method
public class TestlibGDXv2 extends ApplicationAdapter {
private final String tag = (this).getClass().getSimpleName();
String message = "";
private static final int FLOAT_BYTES = 4;
FloatBuffer vertexData;
FloatBuffer colorData;
ShaderProgramExtended shader;
private static final String COLOR_ATTRIBUTE = ShaderProgram.COLOR_ATTRIBUTE;
private int aColourLocation;
private static final String POSITION_ATTRIBUTE = ShaderProgram.POSITION_ATTRIBUTE;
private int aPositionLocation;
/*
Anti-clockwise winding order. Note, we could share two of the vertices. Haven't for clarity.
*/
float[] vertices = {
-0.5f, -0.5f,
0.5f, -0.5f,
-0.5f, 0.5f,
-0.5f, 0.5f,
0.5f, -0.5f,
0.5f, 0.5f
};
/*
Need to colour each vertex, so need 6.
*/
float[] colors = {1.0f, 0.0f, 0.0f, 1.0f,
0.0f,1.0f,0.0f,1.0f,
0.0f,0.0f,1.0f,1.0f,
0.0f,0.0f,1.0f,1.0f,
0.0f,1.0f,0.0f,1.0f,
1.0f,0.0f,0.0f,1.0f
};
#Override
public void create() {
/*
Convert from Dalvik VM to OpenGL native
*/
vertexData = ByteBuffer.allocateDirect(vertices.length * FLOAT_BYTES)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
colorData = ByteBuffer.allocateDirect(colors.length * FLOAT_BYTES)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
vertexData.put(vertices).position(0);
colorData.put(colors).position(0);
initialiseShaders();
}
#Override
public void render() {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
shader.begin(shader.getProgram());
shader.setVertexAttribute(aPositionLocation, 2, GL20.GL_FLOAT, false, 0, vertexData);
shader.enableVertexAttribute(aPositionLocation);
shader.setVertexAttribute(aColourLocation, 4, GL20.GL_FLOAT, false, 0, colorData);
shader.enableVertexAttribute(aColourLocation);
shader.draw(GL20.GL_TRIANGLES, 0, 6);
shader.end();
}
private void initialiseShaders() {
String vertexShaderSource =
"#version 130\n" +
"attribute vec4 " + POSITION_ATTRIBUTE + ";\n" + // x,y,z and w
"attribute vec4 " + COLOR_ATTRIBUTE + ";\n" + // r,g,b and a
"varying vec4 v_color;\n" + // pass to fragment shader
"void main(){\n" +
" v_color = "+ COLOR_ATTRIBUTE + ";\n" +
" gl_Position = " + POSITION_ATTRIBUTE + ";\n" +
"}";
String fragmentShaderSource =
"#version 130\n" +
"#ifdef GL_ES\n" +
" precision mediump float;\n" + // medium a good balance between speed and quality
"#endif\n" +
"varying vec4 v_color;\n" + // incoming from vertex shader
"void main(){\n" +
" gl_FragColor = v_color;\n" +
"}";
shader = new ShaderProgramExtended(vertexShaderSource,fragmentShaderSource);
aPositionLocation = shader.getAttributeLocation(POSITION_ATTRIBUTE);
aColourLocation = shader.getAttributeLocation(COLOR_ATTRIBUTE);
}
}
The attribute "a_color" (from ShaderProgram.COLOR_ATTRIBUTE) could just as easily have been "a_custom".
I used this method when I was trying to learn OpenGL ES from WebGL books - there are a lot more of them than libGDX OpenGL books. However, only use the above for learning, the shader is now no longer being managed by libGDX and so will not work nicely on Android when context is lost. OK for desktop, though.
Cheers
John
Here is the code:
public class BallGame extends JPanel implements Runnable {
JPanel panel1 = new JPanel();
private int ballX = 10, ballY = 110, ...;
Thread aThread;
int toRight=5;
int toLeft= -5;
int upWard=5;
int downWard= -5;
int widthBall, heightBall;
public BallGame(){
game=true;
aThread=new Thread(this);
aThread.start();
}
public void paintComponent(Graphics g){
setOpaque(false);
super.paintComponent(g);
g.setColor(Color.RED);
g.fillOval(ballX, ballY, 7,7);
g.setColor(Color.BLUE);
g.fillOval(ballX + 15, ballY + 10, 7,7);
g.setColor(Color.GREEN);
g.fillOval(ballY - 10, ballY - 15, 7,7);
}
public void positionBall(int sx, int sy)
{
ballX = sx;
ballY = sy;
this.widthBall = this.getWidth();
this.heightBall = this.getHeight();
repaint();
}
public void run() {
boolean leftRight = false;
boolean upDown = false;
while(true){
if(game){
if (leftRight)
{
ballX += toRight;
if (ballX >= (widthBall - 5))
leftRight= false;
}
else
{
ballX += toLeft;
if ( ballX <= 0)
leftRight = true;
}
if (upDown)
{
ballY += upWard;
if (ballY >= (heightBall - 5))
upDown = false;
}
else
{
ballY += downWard;
if ( ballY <= 0)
upDown = true;
}
positionBall(ballX, ballY);
try
{
Thread.sleep(70);
}
catch(InterruptedException ex)
{
}
I don't know if the part where I drew the balls was right. The balls move in the same path. How can I move them in different directions and how can I limit them inside the frame? I need this for our case study immediately. Thank you for your time!
In order for the balls to move independently you need to treat them as 3 balls.
The reason why they always go the same direction, is that you use the same delta, just inverting the sign of delta x and delta y, thus you will always keep the same speed, and bounce at 90 degrees.
In the code below which is basically the same as you had, I keep the state of each ball in separate instances, change speed of delta x and delta y once a side is touched, and use the Swing Timer which is a better approach with respect to timing in Swing, as pointed out by Robin above.
I have updated the example, so that 4 balls start in the middle, and they move away from each other. This should give you enough information to adapt it to your requirements. The picture below is produced by only allowing 10 iterations, and setting
ballGame.setOpaque(true);
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.*;
public class BallGame extends JPanel {
private class Ball {
private int x;
private int y;
private int width;
private int height;
private Color color;
private boolean leftRight;
private boolean upDown;
private int deltaX;
private int deltaY;
Ball(Color color, int x, int y, int width, int height) {
this(color, x, y, width, height, false, false);
}
Ball(Color color, int x, int y, int width, int height, boolean leftRight, boolean upDown) {
this.color = color;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.leftRight = leftRight;
this.upDown = upDown;
updateDelta();
}
private void updateDelta() {
final int minimumMovement = 5;
final int maxExtra = 10;
deltaY = minimumMovement + (int) (Math.random() * maxExtra);
deltaX = minimumMovement + (int) (Math.random() * maxExtra);
}
public void positionBall() {
if (leftRight) {
x += deltaX;
if (x >= (BallGame.this.getWidth() - width / 2)) {
leftRight = false;
updateDelta();
}
} else {
x += -deltaX;
if (x <= 0) {
leftRight = true;
updateDelta();
}
}
if (upDown) {
y += deltaY;
upDown = !(y >= (BallGame.this.getHeight() - height / 2));
if (y >= (BallGame.this.getHeight() - height / 2)) {
upDown = false;
updateDelta();
}
} else {
y += -deltaY;
if (y <= 0) {
upDown = true;
updateDelta();
}
}
}
public Color getColor() {
return color;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
}
private ArrayList<Ball> balls = new ArrayList<>(3);
public BallGame() {
createBalls();
startGame();
}
private void startGame() {
int framesPerSecond = 30;
int timeToWait = 1000 / framesPerSecond;
Timer timer = new Timer(timeToWait, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Ball ball : balls) {
ball.positionBall();
}
repaint();
}
});
timer.start();
}
private void createBalls() {
int startX = 400;
int startY = 200;
balls.add(new Ball(Color.green, startX, startY, 10, 10));
balls.add(new Ball(Color.blue, startX, startY, 15, 15, true, true));
balls.add(new Ball(Color.red, startX, startY, 20, 20, false, true));
balls.add(new Ball(Color.orange, startX, startY, 20, 20, true, false));
}
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
for (Ball ball : balls) {
g2.setColor(ball.getColor());
g2.fillOval(ball.getX(), ball.getY(), ball.getWidth(), ball.getHeight());
g2.setColor(ball.getColor().darker());
g2.drawOval(ball.getX(), ball.getY(), ball.getWidth(), ball.getHeight());
}
g2.dispose();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Ball Game");
BallGame ballGame = new BallGame();
ballGame.setOpaque(false);
frame.getContentPane().add(ballGame);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setMinimumSize(new Dimension(800, 450));
frame.setLocationRelativeTo(null); // Center
frame.pack();
frame.setVisible(true);
}
});
}
}