Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite...
Transcript of Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite...
![Page 1: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/1.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
© 2011-2012 Depto. Ciencia de la Computación e IA
Gráficos y multimedia
Sesión 6: Motor de físicas y libgdx
![Page 2: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/2.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Puntos a tratar• Motor libgdx• Módulos de libgdx• Gráficos• Escena 2D
• Motor Box2D• Objetos de Box2D• Tipos de cuerpos• Unidades de medida• Simulación física• Detección de colisiones
2
![Page 3: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/3.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Motor libgdx
• Framework para videojuegos en Java SE y Android
• Implementamos dos proyectos para hacerlo multiplataforma
• Proyecto Java SE• Se implementa el juego con Java genérico y libgdx• Se puede ejecutar como aplicación de escritorio
• Proyecto Android• Depende del proyecto anterior• Aporta la actividad principal que ejecuta en Android el juego
3
![Page 4: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/4.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Aplicación libgdx• Gestiona el ciclo OpenGL del juego• Aplicación Android
• Aplicación libgdx
4
public class MiActividad extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... }}
public class MiActividad extends AndroidApplication { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); initialize(new MiApplicationListener(), false); }}
![Page 5: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/5.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
ApplicationListener
5
public class MiApplicationListener implements ApplicationListener { @Override public void create() { ... }
@Override public void pause() { ... }
@Override public void resume() { ... } @Override public void dispose() { ... }
@Override public void resize(int width, int height) { ... }
@Override public void render() { ... }}
Ciclo del juego:•Actualizar la escena•Renderizar gráficos
Cambio del tamaño de la pantalla (p. ej. cambio de orientación)
Liberar recursos
Reanudar el juego
Inicializar el juego
Pausar el juego (p. ej. llamada recibida)
![Page 6: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/6.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Módulos de libgdx
• Utilidades para el desarrollo de videojuegos• Los encontramos disponibles como campos estáticos de Gdx
6
Gdx.graphics Acceso al contexto gráfico (OpenGL)
Gdx.audio Reproducción de música y efectos de sonido
Gdx.input Acceso a la entrada (pantalla táctil y acelerómetro)
Gdx.files Acceso a los ficheros (assets)
![Page 7: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/7.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Gráficos en libgdx
• Tenemos acceso al contexto OpenGL
• Tiempo delta• Indica el tiempo transcurrido desde la anterior iteración
7
int width = Gdx.graphics.getWidth();int height = Gdx.graphics.getHeight(); GL10 gl = Gdx.app.getGraphics().getGL10();gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);gl.glViewport(0, 0, width, height);
Gdx.graphics.getDeltaTime()
![Page 8: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/8.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Clases para gráficos
8
Texture Textura OpenGL (potencia de 2)
TextureAtlas Textura con regiones (TexturePacker)
TextureRegion Zona de una textura en memoria
Sprite Región con posición y rotación
BitmapFont Fuente bitmap (formato .fnt)
SpriteBatch Dibujar múltiples sprites 2D
Mesh Malla 3D
ObjLoader Carga una malla 3D de un fichero .obj
![Page 9: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/9.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Carga de sprite sheets
• Podemos cargar sprite sheets generados con TexturePacker• Contamos con una herramienta gratuita propia de libgdx• El formato generado es distinto al de Cocos2D
• Las texturas en memoria (atlas) deberemos liberarlas con dispose() cuando no se vayan a utilizar más
9
TextureAtlas atlas = new TextureAtlas(Gdx.files.getFileHandle("sheet", FileType.Internal));TextureRegion regionPersonaje = atlas.findRegion("frame01");TextureRegion regionEnemigo = atlas.findRegion("enemigo"); Sprite spritePersonaje = new Sprite(regionPersonaje);Sprite spriteEnemigo = new Sprite(regionEnemigo);
![Page 10: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/10.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Dibujar en un sprite batch
• Dibujar sprites de una misma textura de forma consecutiva
• Al cambiar de textura toda la geometría pendiente se envía a la GPU
• Es importante liberar el batch al finalizar
10
public class MiJuego implements ApplicationListener { SpriteBatch batch; TextureAtlas atlas; Sprite spritePersonaje; Sprite spriteEnemigo; @Override public void create() { // Inicializar atlas y sprites ... batch = new SpriteBatch(); } @Override public void dispose() { batch.dispose(); atlas.dispose(); } @Override public void render() { batch.begin(); spritePersonaje.draw(batch); spriteEnemigo.draw(batch); batch.end(); } }
![Page 11: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/11.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Tilemaps• Soporta formato TMX, pero necesitamos generar un fichero
adicional con una herramienta incluida con libgdx• Creamos TiledMap (mosaico) y TileAtlas (textura en memoria)
• Creamos TileMapRenderer para dibujar el mapa en pantalla
• Dibujamos el mapa dentro del ciclo del juego
11
TiledMap fondoMap = TiledLoader.createMap( Gdx.files.getFileHandle("fondo.tmx", FileType.Internal)); TileAtlas fondoAtlas = new TileAtlas(fondoMap, Gdx.files.getFileHandle(".", FileType.Internal));
tileRenderer = new TileMapRenderer(fondoMap, fondoAtlas, 40, 40);
tileRenderer.render();
![Page 12: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/12.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Grafo de la escena 2D
• Sólo es útil para la UI en libgdx
12
Button Label
ImageLabel Group Action
Stage
![Page 13: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/13.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Acciones en la escena 2D• Creamos la escena
• Añadimos un actor (nodo) a la escena
• Añadimos acciones al actor
• Actializamos y renderizamos la escena en el ciclo del juego
13
stage = new Stage(width, height, false);
Label label = new Label("gameover", fuente, "Game Over");stage.addActor(label);
FadeIn fadeIn = FadeIn.$(1);FadeOut fadeOut = FadeOut.$(1);Delay delay = Delay.$(fadeOut, 1);Sequence seq = Sequence.$(fadeIn, delay);Forever forever = Forever.$(seq);label.action(forever);
@Overridepublic void render() { stage.act(Gdx.graphics.getDeltaTime()); stage.draw();}
![Page 14: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/14.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Motor de físicas Box2D• Físicas de cuerpos rígidos• Escrito en C++• Se incluye en casi todos los motores anteriores• AndEngine, Cocos2D, libgdx
14
![Page 15: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/15.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Conceptos de Box2D
15
Body Cuerpo rígido. Puede ser estático, cinemático, o dinámico
Fixture Fija las propiedades de un cuerpo, incluyendo su forma
Shape Define la forma de un cuerpo (círculo o polígono)
Constraint Limita los grados de libertad de un objeto
Joint Define una unión de diferentes cuerpos
World Simulación del mundo 2D. Contiene un conjunto de cuerpos
![Page 16: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/16.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Mundo de Box2D
• Se encarga de realizar la simulación física
• Podemos añadir al mundo diferentes cuerpos rígidos
• Se crea con el vector de gravedad a aplicar• La gravedad se indica mediante un vector 2D (x, y)• El segundo parámetro nos permite optimizar el rendimiento,
evitando que se simulen los cuerpos inactivos si es true
• Podemos cambiar la gravedad con setGravity
16
World world = new World(new Vector2(0, -‐10), true);
![Page 17: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/17.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Tipos de cuerpos en Box2D
17
Estático
CinemáticoDinámico
![Page 18: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/18.jpg)
• Usa el sistema métrico (m, kg)• Debemos fijar ratio de conversión píxeles-metros• Optimizado para manejar objetos de 1 metro
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Unidades de medida en Box2D
18
BodyDef bodyDef = new BodyDef(); bodyDef.type = BodyType.DynamicBody; bodyDef.position.x = x / PTM_RATIO; bodyDef.position.y = y / PTM_RATIO;
Body body = world.createBody(bodyDef); PolygonShape bodyShape = new PolygonShape(); bodyShape.setAsBox((width/2) / PTM_RATIO, (height/2) / PTM_RATIO); body.createFixture(bodyShape, 1.0f); bodyShape.dispose();
PTM_RATIO = 32width = 32 (1m)height = 64 (2m)
32 px1 m
64 px2 m
![Page 19: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/19.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Límites del escenario• Podemos crearlos como objetos estáticos de tipo arista
19
BodyDef limitesBodyDef = new BodyDef();limitesBodyDef.position.x = x;limitesBodyDef.position.y = y; Body limitesBody = world.createBody(limitesBodyDef);EdgeShape limitesShape = new EdgeShape();limitesShape.set(new Vector2(0.0f / PTM_RATIO, 0.0f / PTM_RATIO), new Vector2(width / PTM_RATIO, 0.0f / PTM_RATIO));limitesBody.createFixture(limitesShape,0).setFriction(2.0f); limitesShape.set(new Vector2(width / PTM_RATIO, 0.0f / PTM_RATIO), new Vector2(width / PTM_RATIO, height / PTM_RATIO));limitesBody.createFixture(limitesShape,0); limitesShape.set(new Vector2(width / PTM_RATIO, height / PTM_RATIO), new Vector2(0.0f / PTM_RATIO, height / PTM_RATIO));limitesBody.createFixture(limitesShape,0); limitesShape.set(new Vector2(0.0f / PTM_RATIO, height / PTM_RATIO), new Vector2(0.0f / PTM_RATIO, 0.0f / PTM_RATIO));limitesBody.createFixture(limitesShape,0);
![Page 20: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/20.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Simulación física• En cada iteración del ciclo del juego debemos actualizar la
simulación del mundo físico• Debemos proporcionar el delta time en la actualización
20
// Ejecuta la simulaciónworld.step(delta, 6, 2);world.clearForces(); // Lee datos de los cuerpos en la simulación físicaVector2 pos = cuerpo.getPosition();float rot = (float)Math.toDegrees(cuerpo.getAngle());
Conviene acortarlo
Iteraciones posición
Iteraciones velocidad
![Page 21: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/21.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Datos de usuario de los cuerpos• Podemos asignar datos de usuarios genéricos a los cuerpos• Útil para adjuntar el sprite asociado al cuerpo
• El cuerpo de Box2D sólo realiza simulación física• Nosotros debemos mostrarlo en pantalla mediante un sprite• Esto lo haremos tras la actualización del mundo
21
body.setUserData(sprite);
Sprite sprite = (Sprite)body.getUserData();Vector2 pos = body.getPosition();float rot = (float)Math.toDegrees(body.getAngle()); sprite.setPosition((int)(pos.x * PTM_RATIO), (int)(pos.y * PTM_RATIO));sprite.setRotation(rot); batch.begin();sprite.draw(batch);batch.end();
![Page 22: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/22.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Detección de colisiones• Podemos definir listener de contacto (ContactListener)
22
@Overridepublic void beginContact(Contact c) { // Se produce un contacto entre dos cuerpos} @Overridepublic void endContact(Contact c) { // El contacto entre los cuerpos ha finalizado } @Overridepublic void preSolve(Contact c, Manifold m) { // Se ejecuta antes de resolver el contacto. // Podemos evitar que se procese } @Overridepublic void postSolve(Contact c, ContactImpulse ci) { // Podemos obtener el impulso aplicado sobre los cuerpos en contacto}
![Page 23: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/23.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Velocidad de impacto
• WorldManifold da información sobre los puntos de contacto
23
public void beginContact(Contact c) { Body bodyA = c.getFixtureA().getBody(); Body bodyB = c.getFixtureB().getBody(); // Obtiene el punto de contacto Vector2 point = c.getWorldManifold().getPoints()[0]; // Calcula la velocidad a la que se produce el impacto Vector2 velA = bodyA.getLinearVelocityFromWorldPoint(point); Vector2 velB = bodyB.getLinearVelocityFromWorldPoint(point); float vel = c.getWorldManifold().getNormal().dot(velA.sub(velB)); ...}
![Page 24: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/24.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
Impulso durante el contacto
• Nos informa de la fuerza ejercida entre los cuerpos mientras dura el contacto
24
public void postSolve(Contact c, ContactImpulse ci) { Body bodyA = c.getFixtureA().getBody(); Body bodyB = c.getFixtureB().getBody(); float impulso = ci.getNormalImpulses()[0];}
![Page 25: Sesión 6: Motor de físicas y libgdx · sonido Gdx.input Acceso a ... • Podemos cargar sprite sheets generados con TexturePacker • Contamos con una herramienta gratuita propia](https://reader031.fdocuments.co/reader031/viewer/2022031116/5bafcd9509d3f2d16a8d73d4/html5/thumbnails/25.jpg)
Especialista Universitario en Desarrollo de Aplicaciones para Dispositivos Móviles
Gráficos y multimedia © 2011-2012 Depto. Ciencia de la Computación e IA Motor de físicas
¿Preguntas...?
25