630 likes | 889 Views
Android Game Development. Android Game Engine Basics. Base project. Download our base project and open in NetBeans cg.iit.bme.hu/ gamedev /KIC/11_AndroidDevelopment/11_02_Android_EngineBasics_Base.zip Similar to our OpenGL example Init is called by resize
E N D
Android Game Development Android Game EngineBasics
Base project • Downloadourbase project and openinNetBeans • cg.iit.bme.hu/gamedev/KIC/11_AndroidDevelopment/11_02_Android_EngineBasics_Base.zip • SimilartoourOpenGLexample • Init is called by resize • Changetheandroidsdklocationpath • Per-userproperties (local.properties) • sdk.dir=changepath here • Start an emulator • Build • Run
MainEngine • One instant is createdinMainRenderer • Alleventsareforwardedtothisclass • Windowinited, windowresized, draw a newframe, onTouchevent • New frame: update() • Calculateselapsedtime • Changes „game” state (rotationangles) • Rendersscene (quad)
UEngine.Engine • Createnewpackage: • UEngine.Engine • Reusableclasseswill be created here
Basic classes - Globals • UEngine.Engine.Globals • Wecancreate a classthatwill hold globalvalues • Staticvariables • E.g.: public class Globals { publicstatic float importantThing = 2; } • Easyaccess • Alwaysuseglobalvariablescarefully!
Basic classes • Copyallclasssourcefilesfromtmp_baseclassestosrc\gamedev\android\UEngine\Engine\ • Basic mathutilclasses • Vector • Quaternion • Matrix
Basic Classes – Vectors I. • Vectorclass public class Vector { public float[] d; … • Undefineddimension • Basic operations • addition/substraction • Multiplication/division (elementbyelement) • dotproduct • length • Normalization • add oraddSelf? • Selfwillmodifytheobjectitselfwill (notcreatenewinstance) • Usethiswheneverpossible (efficient)
Basic classes-Vectors II. • Vector3 • Specialization of Vectorto 3 dimension • Definecrossoperation • Defineoftenusedvectors (unit, zero, x dir, y dir etc.) • Vector4 • Specialization of Vectorto 4 dimension • Usedtorepresentvectors of theprojectivespace (homogenouscoordinates) • or RGBA colorvalues • Defineoftenusedvectors (unit, zero, x dir, y dir etc.)
Basic Classes - Quaternion • 4 dimensionalVectorrepresenting a quaternion • Orientationdescribedwith a rotationaxis and angle • Defineinitializationmethodsfromjaw-pitch-roll • Define Vector3 transformation • Defineconcatenation of quaternions (mul/mulSelf) • …
Basic Classes - Matrix • 4x4 matrixclass • Usedtodescribetransformation • Usuallydescribes a rigidtransformationinourcode • Translation • Rotation • Defineinitializationfortranslation and rotation • Defineconcatenation (mul) • Defineinverseifthematrix is a rigidtransform
Resources • Createpackage: UEngine.Engine.Resources • Here weplace: • Textures • Materials • Meshes • Theyareusuallyloadedfrom a file • Theyhave a unique ID • Should be loadedonlyone • Can be sharedbyobjects
AssetManager • Globals.java: import android.content.res.AssetManager; public class Globals { public static AssetManagerassetManager = null; } • MainActivity, onCreate(): gamedev.android.UEngine.Engine.Globals.assetManager = getAssets();
Resource • Createnewclass: Resource package gamedev.android.UEngine.Engine.Resources; public class Resource { String name; public Resource(String name){ this.name = name; } public String getName(){ return this.name; } public void load(){ } public void destroy(){ } }
Resources - Texture • Createnewclass: Texture public class Texture extends Resource { protected int[] id = new int[1]; protected String filename = null; protected float repeatU = 1; protected float repeatV = 1; public Texture(String name) { super(name); id[0] = -1; } public intgetID() { return this.id[0]; } public void setFileName(String filename) { this.filename = filename; } public void setRepeatU(float v){repeatU = v;} public void setRepeatV(float v){repeatV = v;} OpenGLid Howmanytimesweshouldrepeatethetextureoneachdirection (coordinatescaling)
ResourcesTexture II. public void load(){ super.load(); GLES10.glGenTextures(1, this.id, 0); Bitmap bitmap; try{ bitmap = BitmapFactory.decodeStream(Globals.assetManager.open(this.filename == null ? this.name : this.filename)); } catch (IOException e){ e.printStackTrace(); return; } GLES10.glBindTexture(GLES10.GL_TEXTURE_2D, this.id[0]); GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE_MIN_FILTER, GLES10.GL_NEAREST); GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE_MAG_FILTER, GLES10.GL_NEAREST); GLUtils.texImage2D(GLES10.GL_TEXTURE_2D, 0, bitmap, 0); bitmap.recycle(); }
ResourcesTexture III. public void destroy() { super.destroy(); GLES10.glDeleteTextures(1, this.id, 0); } public void enable() { GLES10.glEnable(GLES10.GL_TEXTURE_2D); GLES10.glBindTexture(GLES10.GL_TEXTURE_2D, this.id[0]); GLES10.glMatrixMode(GLES10.GL_TEXTURE); GLES10.glLoadIdentity(); if(repeatU * repeatV != 1){ GLES10.glScalef(repeatU, repeatV, 1.0f); } GLES10.glMatrixMode(GLES10.GL_MODELVIEW); } public static void disable() { GLES10.glBindTexture(GLES10.GL_TEXTURE_2D, 0); GLES10.glDisable(GLES10.GL_TEXTURE_2D); } }
Resources – Material I. public class Material extends Resource{ public static int ALPHA_BLEND_MODE_NONE = 0; public static int ALPHA_BLEND_MODE_BLEND = 1; public static int ALPHA_BLEND_MODE_ADD = 2; protected Texture texture= null; protected boolean lighting = false; protected intalphaBlendMode = ALPHA_BLEND_MODE_NONE; public Material(String name) { super(name); } …
Resources – Material II. public void setTexture(Texture texture) { this.texture = texture; } public Texture getTexture() { return this.texture; } public void setLighting(boolean on) { this.lighting = on; } public booleangetLighting() { return this.lighting; } public intgetAlphaBlendMode() { return alphaBlendMode; } public void setAlhpaBlendMode(int mode){ alphaBlendMode = mode; } …
Resources – Material III. public void load() { } public void destroy(){ } public void set(){ if (this.texture != null) this.texture.enable(); else Texture.disable(); if(alphaBlendMode != ALPHA_BLEND_MODE_NONE){ GLES10.glEnable(GLES10.GL_BLEND); if(alphaBlendMode == ALPHA_BLEND_MODE_ADD) GLES10.glBlendFunc(GLES10.GL_ONE, GLES10.GL_ONE); else GLES10.glBlendFunc(GLES10.GL_SRC_ALPHA, GLES10.GL_ONE_MINUS_SRC_ALPHA); } else{ GLES10.glDisable(GLES10.GL_BLEND); } } }
Resources – Mesh • Createclass: Mesh public class Mesh extends Resource{ protected FloatBuffer VB = null; protected FloatBuffer TB = null; protected FloatBuffer NB = null; protected intvertexcount = 0; public Mesh(String name){ super(name); } public void load(){ super.load(); } public void destroy(){ super.destroy(); this.VB = null; this.TB = null; this.NB = null; }
Resources – Mesh II. publicvoidrender(){ if (this.VB == null) { return; } GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY); GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 0, this.VB); if (this.TB != null) { GLES10.glEnableClientState(GLES10.GL_TEXTURE_COORD_ARRAY); GLES10.glTexCoordPointer(2, GLES10.GL_FLOAT, 0, this.TB); } if (this.NB != null) { GLES10.glEnableClientState(GLES10.GL_NORMAL_ARRAY); GLES10.glNormalPointer(GLES10.GL_FLOAT, 0, this.NB); } GLES10.glDrawArrays(GLES10.GL_TRIANGLES, 0, this.vertexcount); GLES10.glDisableClientState(GLES10.GL_VERTEX_ARRAY); GLES10.glDisableClientState(GLES10.GL_TEXTURE_COORD_ARRAY); GLES10.glDisableClientState(GLES10.GL_NORMAL_ARRAY); }
Resources – Quad I. • Createnewclass: Quad publicclassQuadextendsMesh { publicQuad(Stringname){ super(name); this.vertexcount = 6; } publicvoidload(){ float[] VCoords = { -1.0F, -1.0F, 0.0F, 1.0F, -1.0F, 0.0F, 1.0F, 1.0F, 0.0F, //triangle 1 -1.0F, -1.0F, 0.0F, 1.0F, 1.0F, 0.0F, -1.0F, 1.0F, 0.0F }; //triangle 2 float[] TCoords = { 0.0F, 1.0F, 1.0F, 1.0F, 1.0F, 0.0F, //triangle 1 0.0F, 1.0F, 1.0F, 0.0F, 0.0F, 0.0F }; //triangle 2 float[] NCoords = { 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 1.0F, //triangle 1 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 1.0F }; //triangle 2 …
Resources – Quad II. ByteBuffervbb = ByteBuffer.allocateDirect(VCoords.length * 4); vbb.order(ByteOrder.nativeOrder()); this.VB = vbb.asFloatBuffer(); this.VB.put(VCoords); this.VB.position(0); ByteBuffernbb = ByteBuffer.allocateDirect(NCoords.length * 4); nbb.order(ByteOrder.nativeOrder()); this.NB = nbb.asFloatBuffer(); this.NB.put(NCoords); this.NB.position(0); ByteBuffertbb = ByteBuffer.allocateDirect(TCoords.length * 4); tbb.order(ByteOrder.nativeOrder()); this.TB = tbb.asFloatBuffer(); this.TB.put(TCoords); this.TB.position(0); } }
Tryourclasses I. • MainEngine: • RemoveprotectedFloatBuffer VB = null; • Add: Material m; Texture t; Mesh mesh; • Init: publicvoidinit() { GLES10.glClearColor(0.5F, 0.5F, 1.0F, 1.0F); t = newTexture("TestTexture"); t.setFileName("brick.jpg"); t.load(); m = newMaterial("TestMaterial"); m.setTexture(t); m.load(); mesh = newQuad("TestQuad"); mesh.load(); }
Tryourclasses II. • MainEngine update: • Remove: GLES10.glColor4f(1, 0, 0, 1); GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY); GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 0, this.VB); GLES10.glDrawArrays(GLES10.GL_TRIANGLES, 0, 6); GLES10.glDisableClientState(GLES10.GL_VERTEX_ARRAY); • Add: GLES10.glClearColor(0.5F, 0.5F, 1.0F, 1.0F); m.set(); mesh.render(); }
ResourceManagers • Resourcesshould be loadedonlyonce • Managerstrackthem, placethemin a map • Eachresourcetypewillhaveitsmanager • Allresourcesshould be registeredtoitsresourcemanager • Allresourcesshould be aquiredthroughtheresourcemanager
ResourceManager I. • CreateclassResourceManagerinResourcespackage publicabstractclassResourceManager<R extends Resource>{ protected HashMap<String, R> resources = new HashMap(); public final R get(String name) { if (this.resources.containsKey(name)) { return this.resources.get(name); } return load(name); } protected abstract R loadImpl(String paramString); protected final R load(String name) { if (this.resources.containsKey(name)) { return this.resources.get(name); } R r = loadImpl(name); if (r != null) { this.resources.put(name, r); } return r; } …
ResourceManager II. public final void destroy(String name){ if (this.resources.containsKey(name)) ((Resource)this.resources.get(name)).destroy(); } public final boolean add(R r){ if (!this.resources.containsKey(r.getName())) { this.resources.put(r.getName(), r); } return false; } public void clear(){ for(Resource r : resources.values()){ r.destroy(); } resources.clear(); } }
TextureManager public class TextureManager extends ResourceManager<Texture> { private static TextureManager instance = new null; public static TextureManagergetSingleton(){ if(instance == null) instance = new TextureManager(); return instance; } public static void clearSingleton(){ if(instance != null) instance.clear(); instance = null; } protected Texture loadImpl(String name){ return null; } }
MaterialManager public class MaterialManager extends ResourceManager<Material>{ private static MaterialManager instance = null; public static MaterialManagergetSingleton(){ if(instance == null) instance = new MaterialManager(); return instance; } public static void clearSingleton(){ if(instance != null) instance.clear(); instance = null; } protected Material loadImpl(String name){ return null; } }
MeshManager public class MeshManager extends ResourceManager<Mesh> { private static MeshManager instance = null; private MeshManager(){ Quad quad = new Quad("UnitQuad"); quad.load(); add(quad); } public static MeshManagergetSingleton() { if(instance == null) instance = new MeshManager(); return instance; } public static void clearSingleton(){ if(instance != null) instance.clear(); instance = null; } protected Mesh loadImpl(String name) { return null; } }
Trythemanagers • MainEngine: remove t, m, meshattributes • Init: Material m; Texture t; Meshmesh; t = newTexture("TestTexture"); t.setFileName("brick.jpg"); t.load(); TextureManager.getSingleton().add(t); m = newMaterial("TestMaterial"); m.setTexture(t); m.load(); MaterialManager.getSingleton().add(m); • update: MaterialManager.getSingleton().get("TestMaterial").set(); MeshManager.getSingleton().get("UnitQuad").render();
Trythemanagers • MainEngine add newmethod: publicvoiddestroy() { MaterialManager.clearSingleton(); TextureManager.clearSingleton(); MeshManager.clearSingleton(); } • MainSurfaceView add method: publicvoidonPause() { super.onPause(); this.mRenderer.engine.destroy(); } • MainActivity add method: publicvoidonPause(){ super.onPause(); mGLView.onPause(); } • Compile and run
SceneGraph • All actors of the virtual world will be placed in a scene graph • Scene graph: tree hierarchy of transformations • Each scene graph node (scene node) can contain objects of the virtual world (movables) • The concatenated transformation from the root node will be applied for the objects of scene node • Each scene node and movable should have a unique name • They should be registered to the scene graph • Only objects attached to scene nodes will be rendered • Create new package SceneGraph for the classes
SceneManager I. public class SceneManager { public class NameCollisionException extends Exception { public NameCollisionException(String message) { super(message); } } private static SceneManager instance = null; protected intuniqueID = 0; protected intuniqueIDM = 0; protected HashMap<String, SceneNode> nodes = new HashMap(); protected HashMap<String, Movable> movables = new HashMap(); LinkedList<SceneNode> zOrderedNodes = new LinkedList<SceneNode>(); protected SceneNoderootNode; private SceneManager() {} public static SceneManagergetSingleton() { if(instance == null) instance = new SceneManager(); return instance; } public static void clearSingleton(){ if(instance != null) instance.clear(); instance = null; } …
SceneManager II. void registerNode(SceneNode node) throws Exception { if (node.getName() == null) { while (true) { this.uniqueID += 1; node.name = ("SceneNode" + this.uniqueID); try { registerNode(node); break; } catch (NameCollisionExceptionlocalException) { } } } if (this.nodes.containsKey(node.getName())) { throw new NameCollisionException("SceneNode with the same name already exsists: " + node.getName()); } this.nodes.put(node.getName(), node); } …
SceneManager III. void registerMovable(Movable m) throws Exception { if (m.getName() == null) { while (true) { this.uniqueIDM += 1; m.name = ("UnknownMovable" + this.uniqueIDM); try { registerMovable(m); break; } catch (NameCollisionExceptionlocalException) { } } } if (this.movables.containsKey(m.getName())) { throw new NameCollisionException("Movable with the same name already exsists: " + m.getName()); } this.movables.put(m.getName(), m); } …
SceneManager IV. public void zOrderedRender() { if (zOrderedNodes.size() == 0) { class SceneGraphVisitor { public void visit(SceneNode node) { for (SceneNode n : node.children) { insert(n); visit(n); } } protected void insert(SceneNode node) { int location = 0; for (SceneNode n : zOrderedNodes) { if (n.getPosition().z() > node.getPosition().z()) { break; } location++; } zOrderedNodes.add(location, node); } }; new SceneGraphVisitor().visit(rootNode); } for (SceneNode n : zOrderedNodes) { n.render(false); } } …
SceneManager V. public void update(float t, float dt) { this.rootNode.update(t, dt); } public SceneNodegetNode(String name) { return (SceneNode) this.nodes.get(name); } public Movable getMovable(String name) { return (Movable) this.movables.get(name); } public SceneNodegetRootNode() { return this.rootNode; } public void render() { this.rootNode.render(true); } public void clear(){ nodes.clear(); movables.clear(); zOrderedNodes.clear(); rootNode = null; } }
SceneGraph – SceneNode I. public class SceneNode { protected String name = null; protected SceneNode parent = null; protected LinkedList<SceneNode> children = new LinkedList(); protected LinkedList<Movable> movables = new LinkedList(); protected Vector3 position = new Vector3(); protected Quaternion orientation = new Quaternion(); protected Matrix4 transform = new Matrix4(); protected Matrix4 derivedTransform = this.transform; protected Matrix4 transformInv = new Matrix4(); protected Matrix4 derivedTransformInv = this.transformInv; protected booleaninverseDirty = false; …
SceneGraph – SceneNode II. public SceneNode(String name) throws Exception { this.name = name; if (SceneManager.getSingleton() != null) SceneManager.getSingleton().registerNode(this); this.derivedTransform = this.transform; this.derivedTransformInv = this.transformInv; } public SceneNode() { if (SceneManager.getSingleton() != null) try { SceneManager.getSingleton().registerNode(this); } catch (Exception localException) { } this.derivedTransform = this.transform; this.derivedTransformInv = this.transformInv; } …
SceneGraph – SceneNode III. public SceneNode(String name, SceneNode parent) throws Exception { this.name = name; if (SceneManager.getSingleton() != null) SceneManager.getSingleton().registerNode(this); if (parent != null) { this.parent = parent; this.derivedTransform = new Matrix4(parent.derivedTransform); this.derivedTransformInv = new Matrix4(parent.derivedTransformInv); this.inverseDirty = true; } } public SceneNode(SceneNode parent) { if (SceneManager.getSingleton() != null) { try { SceneManager.getSingleton().registerNode(this); } catch (Exception localException){} } if (parent != null) { this.parent = parent; this.derivedTransform = new Matrix4(parent.derivedTransform); this.derivedTransformInv = new Matrix4(parent.derivedTransformInv); this.inverseDirty = true; } } …
SceneGraph – SceneNode IV. private void parentChanged() { refreshDerivedTransform(); } private void refreshDerivedTransform() { if (this.parent != null) { this.derivedTransform = this.transform.mul(this.parent.derivedTransform); } this.inverseDirty = true; for (SceneNode n : this.children) n.refreshDerivedTransform(); } private void refreshInvTransform() { this.transformInv = this.transform.inverseAffine(); if (this.parent != null) { this.derivedTransformInv = this.derivedTransform.inverseAffine(); } this.inverseDirty = false; } …
SceneGraph – SceneNode V. public SceneNodegetParent(){ return parent; } public Matrix4 getInverseTransform() { if (this.inverseDirty) refreshInvTransform(); return this.transformInv; } public Matrix4 getInverseDerivedTransform() { if (this.inverseDirty) refreshInvTransform(); return this.derivedTransformInv; } public String getName() { return this.name; } public Vector3 getPosition() { return this.position; } public void setPosition(Vector3 p) { this.position = p; this.transform.setTranslate(p); refreshDerivedTransform(); this.inverseDirty = true; } …
SceneGraph – SceneNode VI. public void setPosition(float x, float y, float z) { this.position.set(x, y, z); this.transform.setTranslate(this.position); refreshDerivedTransform(); this.inverseDirty = true; } public Quaternion getOrientation() { return this.orientation; } public void setOrientation(Quaternion q) { this.orientation = q; this.transform.setRotate(q); refreshDerivedTransform(); this.inverseDirty = true; } public void setOrientation(float yaw, float pitch, float roll) { this.orientation.set(yaw, pitch, roll); this.transform.setRotate(this.orientation); refreshDerivedTransform(); this.inverseDirty = true; } …
SceneGraph – SceneNode VII. public SceneNodegetChild(int index) { if (this.children.size() <= index) { return null; } return (SceneNode)this.children.get(index); } public SceneNodecreateChild(String name) throws Exception { SceneNode child = new SceneNode(name, this); this.children.add(child); return child; } public SceneNodecreateChild() { SceneNode child = new SceneNode(this); this.children.add(child); return child; } public void addChild(SceneNode node) { if (!this.children.contains(node)) { this.children.add(node); node.parent = this; node.parentChanged(); } } …
SceneGraph – SceneNode VIII. public void removeChild(SceneNode node) { this.children.remove(node); node.parent = null; node.parentChanged(); } public void removeChild(String name) { for (SceneNode n : this.children) if (name.equals(n.getName())) { this.children.remove(n); n.parent = null; n.parentChanged(); return; } } public void attachObject(Movable o) { if (!this.movables.contains(o)) { this.movables.add(o); o.changeParent(this); } } …
SceneGraph – SceneNode IX. public void detachObject(Movable o) { this.movables.remove(o); } public void detachObject(String name) { for (Movable m : this.movables) if (name.equals(m.getName())) { this.movables.remove(m); return; } } …