Tecnologias de Jogos de
Vídeo
Abel J. P. Gomes & Gonçalo Amador
LAB. 5
Departamento de Informática
Universidade da Beira Interior
Portugal
2012
LAB. 5
GESTÃO DE CENAS
1. Objectivos
2. Conceitos
Lab. 5
GESTÃO DE CENAS 3D
Nesta lição prático-laboratorial aprender-se-á como manipular e gerir o grafo de cenas 3D do sub-motor
gráfico (graphics engine) do motor de jogos GameProject.
1.
Objectivos específicos de aprendizagem
Terminada esta ficha de trabalho, o aluno deve saber e ser capaz de:
1. Adicionar ou remover objectos do grafo de cena, i.e., jogadores, obstáculos, etc.
2. Alterar objectos que fazem parte do grafo de cena, i.e., o chão e a caixa delimitadora do
mundo.
3. Adicionar objectos distintos com texturas distintas.
2.
Grafo de Cena
O grafo de cena do motor gráfico do GameProject é uma árvore de dois níveis, como se ilustra na Fig.1:
Na raiz da árvore da Fig. 1 encontra-se a cena que corresponde à classe scene.java. A cena contém um
nó de transformação (TransformNode) referente a ela própria. Os jogadores, obstáculos e as balas
presentes na cena estão armazenados numa tabela de hash e em duas listas de nós de transformação,
respectivamente. Cada nó de transformação da cena, com exceção do nó que representa a própria cena,
tem associada uma malha de triângulos ou, mais geralmente, uma malha de polígonos. A cena tem ainda
associados dois nós de transformação individuais, um para a caixa que representa os limites da cena
(SkyBox) e um nó que representa o chão da cena (Floor).
Diga-se desde já que cada jogador, cada obstáculo e cada bala presente na cena têm associada uma
“bounding box”. No entanto, o conceito de “bounding box” será abordado mais tarde com a profundidade
devida numa outra ficha, mais concretamente aquela referente à temática de deteção de colisões.
Na Fig. 2, como se assinala com uma elipse vermelha, estão indicadas as classes que dizem respeito ao
grafo de cena, que aparece incluído no módulo referente à geometria.
3.
Texturas e COLLADA
Como se mostra na Fig. 3, todas as texturas do mundo ou dos modelos geométricos encontram-se na
diretoria data.
Mais propriamente, os modelos geométricos (no formato Collada 1.4.1) estão guardados na sub-diretoria
models. Tenha-se em atenção que estes modelos foram previamente submetidos a um processo de
desindexação, através de uma ferramenta disponível em:
https://collada.org/mediawiki/index.php/COLLADA_Refinery
Todos os modelos desindexados têm um nome de ficheiro que termina em “_out.dae”.
Note-se que os modelos texturizados que não passaram por este processo de desindexação não serão
desenhados corretamente, ou, para ser mais preciso, as suas texturas não serão desenhadas
corretamente.
Note-se ainda que todas as texturas que tenham a palavra “world” nos seus nomes são tidas como tendo
nomes fixos, pelo que se os nomes forem alterados no código fonte do motor, então os nomes dos
ficheiros respectivos terão também de ser alterados.
Figure 3: Modelos e texturas do Tie Beam’Em.
4.
Exercícios de programação
Exercício 1
:
Na versão disponibilizada do projeto não é possível carregar dois modelos, e.g. um Tie Fighter e um
X-Wing com texturas distintas. A razão para tal comportamento prende-se com o facto de que o código
apenas permite que uma textura seja definida para qualquer objeto do tipo jogador/avatar. Esta textura é
definida como variável de sistema no construtor da classe principal do jogo, GameRenderer.java, após
definida o construtor da cena vai utilizar o valor que tiver atribuído para apenas permitir uma textura para
todos os modelos carregados. De notar que as texturas dos limites da cena são carregadas à parte, na
classe Scene.java. Vamos alterar o código para que seja possível.
0. try {
1. if ("true".equals(System.getProperty("gridgameproject.tie"))) {
2. texture = TextureIO.newTexture(Scene.class.getResourceAsStream("/data/ 3. textures/tie/tieskin.jpg"), true, TextureIO.JPG);
4. }
5.
6. if ("true".equals(System.getProperty("gridgameproject.xwing"))) {
7. texture = TextureIO.newTexture(Scene.class.getResourceAsStream("/data/ 8. textures/xwing/xwingskin.jpg"), true, TextureIO.JPG); 9. }
10.
11. if ("true".equals(System.getProperty("gridgameproject.cow"))) {
12. texture = TextureIO.newTexture(Scene.class.getResourceAsStream("/data/ 13. textures/cow/Cow.png"), true, TextureIO.PNG); 14. }
15.
16. if ("true".equals(System.getProperty("gridgameproject.duck"))) {
17. texture = TextureIO.newTexture(Scene.class.getResourceAsStream("/data/ 18. textures/duck/duckCM.jpg"), true, TextureIO.JPG); 19. }
20. }
21. catch (IOException e) {
22. throw new RuntimeException(e);
23. //e.printStackTrace();
24. }
De seguida, altere-se o seguinte método, designado por
loadModel, entre as linhas 28 e 55:
25. @SuppressWarnings({"unchecked"})26. public static TransformGroup loadModel(String modelName, String id) {
27. . . . (código intermédio)
28. for (CommonNewparamType commonNewparamType : commonNewparamTypes) { 29. if (commonNewparamType.getSurface() != null) { 30. geometryMesh.setTexture(texture); 31. } 32. } 33. } 34. 35. scale = 1; 36. 37. //if (modelName.contains("cow")) { 38. if ("true".equals(System.getProperty("gridgameproject.cow"))) { 39. scale = 1.1f; 40. } 41. 42. //if (modelName.contains("duck")) { 43. if ("true".equals(System.getProperty("gridgameproject.duck"))) { 44. scale = 250; 45. } 46. 47. //if (modelName.contains("tie")) { 48. if ("true".equals(System.getProperty("gridgameproject.tie"))) { 49. scale = 70; 50. } 51.
52. //if (modelName.contains("xwing")) { 53. if ("true".equals(System.getProperty("gridgameproject.xwing"))) { 54. scale = 3300; 55. } 56. . . . (código intermédio) 57. }
substituindo-o pelo seguinte código (sem alterar o restante código do método):
@SuppressWarnings({"unchecked"})
public static TransformGroup loadModel(String modelName, String id) {
. . . (código intermédio)
for (CommonNewparamType commonNewparamType : commonNewparamTypes) { if (commonNewparamType.getSurface() != null) {
try {
if (modelName.contains("cow")) {
texture = TextureIO.newTexture(Scene.class.getResourceAsStream("/data/ textures/cow/Cow.png"), true, TextureIO.PNG);
}
if (modelName.contains("duck")) {
texture = TextureIO.newTexture(Scene.class.getResourceAsStream("/data/ textures/duck/duckCM.jpg"), true, TextureIO.JPG);
}
if (modelName.contains("tie")) {
texture = TextureIO.newTexture(Scene.class.getResourceAsStream("/data/ textures/tie/tieskin.jpg"), true, TextureIO.JPG);
}
if (modelName.contains("xwing")) {
texture = TextureIO.newTexture(Scene.class.getResourceAsStream("/data/ textures/xwing/xwingskin.jpg"), true, TextureIO.JPG); }
}
catch (IOException e) {
throw new RuntimeException(e);
//e.printStackTrace(); } geometryMesh.setTexture(texture); } } } scale = 1; if (modelName.contains("cow")) { scale = 1.1f; } if (modelName.contains("duck")) { scale = 250; } if (modelName.contains("tie")) { scale = 70;
} if (modelName.contains("xwing")) { scale = 3300; } . . . (código intermédio) }
Por fim, falta realizar algumas alterações na classe principal do jogo: GameRenderer.java.
No método main, comente-se a seguinte porção de código:
58. if (spaceGame) { 59. //System.setProperty("gridgameproject.xwing","true"); 60. System.setProperty("gameproject.tie","true"); 61. } 62. 63. if (stupidGame) { 64. //System.setProperty("gridgameproject.duck","true"); 65. System.setProperty(gameproject.cow","true"); 66. }
Se não pretender visualizar as “bounding boxes”, comente também:
67. System.setProperty("gameproject.drawbb","true");
De seguida, vamos alterar o método initGame e substituir o seguinte código:
68. TransformGroup playerRotate = null; 69. if (spaceGame) {
70. if ("true".equals(System.getProperty("gameProject.xwing"))) {
71. //playerRotate = Scene.loadModel("/data/models/xwing.dae", playerId);
72. playerRotate = Scene.loadModel("/data/models/xwing_out.dae",playerId); 73. playerRotate.setRotationY(new Point3f( 0.0f, 180.0f, 0.0f)); 74. } 75. if ("true".equals(System.getProperty("gameProject.tie"))) { 76. //playerRotate = Scene.loadModel("/data/models/tiefighter.dae", 77. // playerId); 78. playerRotate = Scene.loadModel("/data/models/tiefighter_out.dae", 79. playerId); 80. } 81. } 82. else 83. if (stupidGame) { 84. if ("true".equals(System.getProperty("gameProject.duck"))) { 85. //playerRotate = Scene.loadModel("/data/models/duck_triangulate.dae", 86. // playerId); 87. playerRotate = Scene.loadModel("/data/models/ 88. duck_triangulate_out.dae", playerId); 89. playerRotate.setRotationY(new Point3f( 0.0f, -90.0f, 0.0f)); 90. } 91. if ("true".equals(System.getProperty("gameProject.cow"))) {
92. //playerRotate = Scene.loadModel("/data/models/cow.dae", playerId);
93. playerRotate = Scene.loadModel("/data/models/cow_out.dae", 94. playerId);
95. playerRotate.setRotationZ(new Point3f( 0.0f, 0.0f, 90.0f)); 96. playerRotate.setRotationX(new Point3f( -90.0f, 0.0f, 0.0f)); 97. }
98. } 99. else { 100. //playerRotate = Scene.loadModel("/data/models/Duke_posed.dae", 101. // playerId); 102. playerRotate = Scene.loadModel("/data/models/Duke_posed_out.dae", 103. playerId); 104. playerRotate.setRotationY(new Point3f(0, 180, 0)); 105. } 106. 107. . . . (código intermédio)
108. TransformGroup player2Rotate = null; 109. if (spaceGame) { 110. if ("true".equals(System.getProperty("gameProject.xwing"))) { 111. player2Rotate = Scene.loadModel("/data/models/xwing_out.dae", 112. "player2"); 113. player2Rotate.setRotationY(new Point3f( 0.0f, 180.0f, 0.0f)); 114. } 115. if ("true".equals(System.getProperty("gameProject.tie"))) { 116. player2Rotate = Scene.loadModel("/data/models/tiefighter_out.dae", 117. "player2"); 118. player2Rotate.setRotationY(new Point3f(0, 180, 0)); 119. } 120. } 121. else 122. if (stupidGame) { 123. if ("true".equals(System.getProperty("gameProject.duck"))) { 124. player2Rotate = Scene.loadModel("/data/models/ 125. duck_triangulate_out.dae","player2"); 126. player2Rotate.setRotationY(new Point3f( 0.0f, -90.0f, 0.0f)); 127. } 128. if ("true".equals(System.getProperty("gameProject.cow"))) { 129. player2Rotate = Scene.loadModel("/data/models/cow_out.dae", 130. "player2"); 131. player2Rotate.setRotationZ(new Point3f( 0.0f, 0.0f, 90.0f)); 132. player2Rotate.setRotationX(new Point3f( -90.0f, 0.0f, 0.0f)); 133. } 134. } 135. else { 136. player2Rotate = Scene.loadModel("/data/models/Duke_posed_out.dae", 137. "player2"); 138. player2Rotate.setRotationY(new Point3f(0, 180, 0)); 139. }
pelo seguinte código para o 1º jogador:
TransformGroup playerRotate = null; if (spaceGame) {
playerRotate = Scene.loadModel("/data/models/tiefighter_out.dae", playerId); }
else
if (stupidGame) {
playerRotate = Scene.loadModel("/data/models/cow_out.dae", playerId); playerRotate.setRotationZ(new Point3f( 0.0f, 0.0f, 90.0f));
playerRotate.setRotationX(new Point3f( -90.0f, 0.0f, 0.0f)); }
else {
playerRotate = Scene.loadModel("/data/models/Duke_posed_out.dae", playerId); playerRotate.setRotationY(new Point3f(0, 180, 0));
}
TransformGroup player2Rotate = null; if (spaceGame) {
player2Rotate = Scene.loadModel("/data/models/xwing_out.dae", "player2"); player2Rotate.setRotationY(new Point3f( 0.0f, 180.0f, 0.0f)); //player2Rotate = Scene.loadModel("/data/models/tiefighter_out.dae", // "player2"); } else if (stupidGame) { player2Rotate = Scene.loadModel("/data/models/duck_triangulate_out.dae", "player2"); player2Rotate.setRotationY(new Point3f( 0.0f, -90.0f, 0.0f));
//player2Rotate = Scene.loadModel("/data/models/cow_out.dae", "player2"); //player2Rotate.setRotationZ(new Point3f( 0.0f, 0.0f, 90.0f)); //player2Rotate.setRotationX(new Point3f( -90.0f, 0.0f, 0.0f)); } else { player2Rotate = Scene.loadModel("/data/models/Duke_posed_out.dae", "player2"); player2Rotate.setRotationY(new Point3f(0, 180, 0)); }
Neste momento o código, ao ser executado, carregará um modelo de um Tie fighter que pode ser
controlado pelo rato e/ou teclado, bem como um X-Wing que não faz nada.
Exercício 2
.
Quando os modelos são carregados não ficam necessariamente na posição ou com a rotação pretendida.
Vamos então agora adicionar um terceiro jogador (player3Player) com o vosso primeiro nome, e.g.,
Miguel, na posição inicial (30, 0, 40). Ter em atenção que o modelo que é controlado pelo teclado e pelo
rato aparece numa posição aleatória, o que é feito no ciclo do-while aquando na adição do primeiro
jogador à cena. O modelo utilizado pode ser qualquer um dos disponíveis, i.e., pato, vaca, x-xing, etc, ter
em atenção os nomes no código anteriormente alterado.
Para realizar este exercício tome atenção ao código que vem a seguir a:
140. TransformGroup playerRotate = null;
e
141. TransformGroup player2Rotate = null;
até à inclusão dum modelo na cena. Ao carregar o modelo, tenha particular atenção que após a seguinte
chamada ao método loadModel:
142. player2Rotate = Scene.loadModel("/data/models/xwing_out.dae", "player2");
existe normalmente um ou mais métodos de rotação para reposicionar o objecto, como por exemplo:
143. player2Rotate.setRotationY(new Point3f( 0.0f, 180.0f, 0.0f));
Experimente comentar uma ou mais das rotações referidas para qualquer um dos modelos (em alguns dos
modelos são feitas várias), e.g., o modelo da vaca.
Tente indicar o conjunto de passos necessários para adicionar um nó de transformação do tipo jogador à
cena.
Exercício 4
.
Vamos agora alterar ou remover os limites da cena. Para tal basta comentar na secção de código no
método initGame da classe principal do motor/jogo GameRenderer.java:
144. scene.setFloor(floor);
145. …
146. scene.setSkyBox(skyBox);
Seguidamente vamos correr o jogo/motor. O código neste mesmo método que precede estas chamadas
inicializa o chão ou os limites do mundo, i.e., associa a informação da geometria a elementos das texturas,
carrega texturas, etc. Estas chamadas apenas incluem o skybox e o chão na cena.
Agora descomente o código comentado previamente, e substitua a chamada (assumindo que no início da
classe GameRenderer, stupidGame e dukeBeanEm estão a false e spaceGame está a true):
147. skyBox = Scene.buildSkyBox(size/5, "/data/textures/World_5/SkyBox2");
Por uma das seguintes opções, cujas texturas estão na diretoria data:
148. skyBox = Scene.buildSkyBox(size/5, "/data/textures/World_4/SkyBox2"); 149. skyBox = Scene.buildSkyBox(size/5, "/data/textures/World_3/SkyBox2"); 150. skyBox = Scene.buildSkyBox(size/5, "/data/textures/World_2/SkyBox2"); 151. skyBox = Scene.buildSkyBox(size/5, "/data/textures/World_1/SkyBox2");
Exercício 5
.
Vamos por fim adicionar um obstáculo à cena. Em primeiro lugar, no seguinte
snippet de código do
método initGame:
152. if(!spaceGame) { 153. int boxCount = 6;
154. Texture obstacleTexture = null; 155. try {
156. TextureData textureData = TextureIO.newTextureData(GLProfile. 157. getDefault(),Scene.class.getResourceAsStream("/data/textures/ 158. brick1.jpg"), true, TextureIO.JPG);
159. obstacleTexture = TextureIO.newTexture(textureData); 160. }
161. catch (IOException e) {
162. throw new RuntimeException(e); 163. //e.printStackTrace();
164. } 165.
166. for (int i = 0; i < boxCount; i++) { 167. float rotation = i * (360 / boxCount);
168. TransformGroup obstacle = Scene.buildObstacle("obstacle" + (i + 1), 169. "brick1", obstacleTexture);
170. Point3f obstaclePostion = new Point3f();
171. obstaclePostion.x = (float) Math.sin(Math.toRadians(rotation)) * 15; 172. obstaclePostion.y = 0;
173. obstaclePostion.z = (float) Math.cos(Math.toRadians(rotation)) * 15; 174. obstacle.setTranslation(obstaclePostion);
175. scene.addObstacle(obstacle);
176. System.out.println("Obstacle " + (i + 1) + " added"); 177. }
178. }
comente:
if(!spaceGame) {
e
}