ISGL3D: The Basics of Game Scene 1

step by step

As I promised here is a step by step description of the ISGL3D version of Game Scene 1. In a future tutorial I will show how to implement the Bullet engine in a project, but for now I just wanted to show the basics.

The Back Wall

So I start the creation the 3D room by creating a plane that I will use as the back wall.

In this first step, I set up a basic camera, create an empty 3D object I'll use as a container of all the elements in the scene and I load the png file I'll use to texture the walls.

#import "HelloWorldView.h"
#import 
#import 

@implementation HelloWorldView

- (id) init {
	
    if ((self = [super init])) {
        
        //room dimensions and element positions
        _wallHeight = 4;
		_roomSide = 12;
		_boxSide = 1.4;
        _playerWidth = 1.5;
        _playerHeight = 3;
        
        
        //SET UP BASIC CAMERA
        [self.camera setPosition:iv3(0, 1, 7)];
        [self.camera lookAt:0 y:0 z:0];
        
        //create an empty container to receive all 3D elements
        _container = [[self.scene createNode] retain];
        
        //MATERIAL
        //set up texture to be used with walls (plane meshes)
        Isgl3dTextureMaterial * wallMaterial = [Isgl3dTextureMaterial materialWithTextureFile:@"wall.png" shininess:0 precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO];
        
        //CREATE WALL
        Isgl3dGLMesh * wallMesh = [Isgl3dPlane meshWithGeometry:_roomSide height:_wallHeight nx:10 ny:10];

        //back wall
        Isgl3dMeshNode * backWall = [_container createNodeWithMesh:wallMesh andMaterial:wallMaterial];
        backWall.position = iv3(0, 0, -_roomSide);
        
	}
	return self;
}

-(void) dealloc {
	
	[_container release];
	[super dealloc];
}

@end

You will notice that as far as position and dimensions go I use very low values. This happens in most 3D engines since positioning and dimensions are more complex magnitudes in 3D than they are in 2D, mainly because you have a camera and you can move it in 3D space and zoom in or out, so scalars may represent different things. Most 3D engines seem to favor this meter-based scale. You may have noticed in my previous example that I used a 100:1 scale when I brought my code from Away3D to ISGL3D. This

Next comes the camera and container. Most demo projects that come bundled with ISGl3D make use of a Camera Controller object and it's a good idea to go over that class. Basically, as the name implies it affords more control over the camera, particularly regarding interactivity. But here I used a basic camera like the one I used in the Away3D version.

Notice that I don't have to add the _container object as a child. By creating a node inside the scene container I accomplish that very thing.

Next comes the creation of a plane. The Plane is a type of mesh so the creating process will be pretty much the same for all other types of meshes.

So going backwards in the code for a moment, in order to create a MeshNode which is the thing that gets added to your scene, you need a material (texture) and a mesh.

 Isgl3dGLMesh * wallMesh = [Isgl3dPlane meshWithGeometry:_roomSide height:_wallHeight nx:10 ny:10];

The mesh here is that of a Plane object, and it receives as parameters the size (width and height) and the number of x and y segments. I used an incredibly high value for segments for such a simple scene, but I wanted to test performance.

The material, which in this case is a TextureMaterial, needs a texture file, the specular value (shininess), the quality level, and the repeats which will be used for eventual changes in the UV coordinates of the texture (meaning, how will the texture tile on the surface, if indeed it will tile.)

Isgl3dTextureMaterial * wallMaterial = [Isgl3dTextureMaterial materialWithTextureFile:@"wall.png" shininess:0 precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO];

And this is the result of that code.

The Other Walls

The process is repeated for the other walls, by creating multiple planes and rotating and positioning them accordingly.

#import "HelloWorldView.h"
#import 
#import 


@implementation HelloWorldView

- (id) init {
	
    if ((self = [super init])) {
        
        //room dimensions and element positions
        _wallHeight = 4;
		_roomSide = 12;
		_boxSide = 1.4;
        _playerWidth = 1.5;
        _playerHeight = 3;
        
        
        //SET UP BASIC CAMERA
        [self.camera setPosition:iv3(0, 1, 7)];
        [self.camera lookAt:0 y:0 z:0];
        
        //create an empty container to receive all 3D elements
        _container = [[self.scene createNode] retain];
        
        
        //MATERIAL
        //set up texture to be used with walls (plane meshes)
        Isgl3dTextureMaterial * wallMaterial = [Isgl3dTextureMaterial materialWithTextureFile:@"wall.png" shininess:0 precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO];
        
        //CREATE WALL
        Isgl3dGLMesh * wallMesh = [Isgl3dPlane meshWithGeometry:_roomSide height:_wallHeight nx:10 ny:10];

        //back wall
        Isgl3dMeshNode * backWall = [_container createNodeWithMesh:wallMesh andMaterial:wallMaterial];
        backWall.position = iv3(0, 0, -_roomSide);
        
        //left wall
        Isgl3dMeshNode * leftWall = [_container createNodeWithMesh:wallMesh andMaterial:wallMaterial];
        leftWall.position = iv3(-_roomSide*0.5, 0, -_roomSide*0.5);
        leftWall.rotationY = 90;
        
        //right wall
        Isgl3dMeshNode * rightWall = [_container createNodeWithMesh:wallMesh andMaterial:wallMaterial];
        rightWall.position = iv3(_roomSide*0.5, 0, -_roomSide*0.5);
        rightWall.rotationY = -90;
        
        //ceiling
        Isgl3dGLMesh * ceilingMesh = [Isgl3dPlane meshWithGeometry:_roomSide height:_roomSide nx:10 ny:10];
        Isgl3dMeshNode * ceiling = [_container createNodeWithMesh:ceilingMesh andMaterial:wallMaterial];
        ceiling.position = iv3(0, _wallHeight*0.5, -_roomSide*0.5);
        ceiling.rotationX = 90;
        
	}
	return self;
}

-(void) dealloc {
	
	[_container release];
	[super dealloc];
}


@end

And this is the result of that code.

The Ground

Now for the ground I wanted the texture to tile. So I had to alter the UV coordinates of the texture.

//MATERIAL
Isgl3dTextureMaterial * groundMaterial = [Isgl3dTextureMaterial materialWithTextureFile:@"ground.png" shininess:0 precision:Isgl3dTexturePrecisionMedium repeatX:YES repeatY:YES];

//make texture tile on the ground (2x horizontally, 2x vertically)
Isgl3dUVMap * groundUVMap = [Isgl3dUVMap uvMapWithUA:0.0 vA:0.0 uB:2.0 vB:0.0 uC:0.0 vC:2.0];
Isgl3dPlane * groundMesh = [Isgl3dPlane meshWithGeometryAndUVMap:_roomSide height:_roomSide nx:2 ny:2 uvMap:groundUVMap];
Isgl3dMeshNode * ground = [_container createNodeWithMesh:groundMesh andMaterial:groundMaterial];
ground.position = iv3(0,-_wallHeight*0.5,-_roomSide*0.5);
ground.rotationX = -90;

I need to add a new step in the creation of the MeshNode. I need now a UVMap object which will dictate the direction of the tiling and the number of tiles. Honestly, those values are better understood by messing with them. Just keep in mind that altering those values will only show results if the MeshNode has the shape, and orientation to support them. (trust me, you will see what I mean once you start playing with them.)

Now I use a different method to create the Plane, one that receives a UVMap object as a parameter. Bellow you can see the result.

The Crates

//ADD CRATES
NSArray *cratePosition = [NSArray arrayWithObjects:
						  [NSValue valueWithCGPoint:CGPointMake(6,6)], 
						  [NSValue valueWithCGPoint:CGPointMake(2,6)],
						  [NSValue valueWithCGPoint:CGPointMake(4,10)],
						  [NSValue valueWithCGPoint:CGPointMake(8,5)],
						  [NSValue valueWithCGPoint:CGPointMake(6,4)],
						  [NSValue valueWithCGPoint:CGPointMake(7,9.5f)],
						  nil];
//MATERIAL
Isgl3dTextureMaterial * crateMaterial = [Isgl3dTextureMaterial materialWithTextureFile:@"crate.png" shininess:0 precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO];


//create crates
_crates = [[NSMutableArray array] retain];
CGPoint pos;
int i;
Isgl3dCube * cubeMesh = [[Isgl3dCube alloc] initWithGeometry:_boxSide height:_boxSide depth:_boxSide nx:2 ny:2];
Isgl3dMeshNode * crate;
for (i = 0; i < [cratePosition count]; i++) {
	pos =  [[cratePosition objectAtIndex:i] CGPointValue];
	crate = [_container createNodeWithMesh:cubeMesh andMaterial:crateMaterial];
	crate.z = -1 * pos.y;
	crate.x = pos.x - _roomSide * 0.5;
	crate.y = -_wallHeight*0.5 + _boxSide*0.5;
	crate.rotationY = (random() * RAND_MAX) * 180 / M_PI;
	[_crates addObject:crate];
}

When I first created the crates for this scene I thought texturing them would work the same way it does in Away3D, and I was pleasantly proved wrong. In Away3D I must alter the UV coordinates of the texture so I can repeat the image on every face of the cube, otherwise the texture gets wrapped all around the mesh.

But in ISGL3D this is the default behavior. Add a texture to a cube and it will be applied individually to each face.

There is an alternative to the Cube object called a MultiMaterialCube which in fact is not a cube at all but 6 planes arranged so as to make a cube. And each plane can have its own material. So if you need to create objects like dices, or buildings, or anything that requires unique faces or a combination of texture materials and color materials you can use that instead. It only requires an array of the material objects you wish to use.

Here is the result of adding the crates:

The Player

//MATERIAL
Isgl3dTextureMaterial * playerMaterial_front0 = [Isgl3dTextureMaterial materialWithTextureFile:@"player_front_0.png" shininess:0 precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO];

//add player plane
Isgl3dGLMesh * playerMesh = [Isgl3dPlane meshWithGeometry:_playerWidth height:_playerHeight nx:1 ny:1];
_player = [_container createNodeWithMesh:playerMesh andMaterial:playerMaterial_front0];
_player.position = iv3(0, -_wallHeight*0.5 + _playerHeight*0.5, 0);
_player.doubleSided = YES; //make it double sided so player can be flipped

The player is just another plane. Again the default behavior in ISGL3D differs from the one in Away3D. In the Flash version I had to specify that the texture's transparency should be kept. With ISGL that is the default.

So you are able to see the texture like this:

I set the plane mesh node to be doubleSided because I need to flip the plane when the player is moving to the left. One interesting thing with ISGL is that I had to rotate the plane on its Y axis in order to flip it, since using scaleX produced weird results. I think this is a bug with the framework.

Another bug with ISGL3D, and something to keep in mind when developing your scenes is the order you add elements to a scene. In this example when I add the Player before I add the crates, even though the player has a Z value that puts it closer to the camera, something weird happens with its alpha channel, you can see it in the picture below.

The alpha channel in the player plane voids all the objects behind the plane.

Interactivity

In this example I decided that I wanted the ground plane to receive the user input. And I accomplish this by adding these lines right after I instantiate the ground plane :

//make ground plane receive TOUCH EVENTS
ground.interactive = YES;
[ground addEvent3DListener:self method:@selector(objectTouched:) forEventType:TOUCH_EVENT];
[ground addEvent3DListener:self method:@selector(objectReleased:) forEventType:RELEASE_EVENT];
[ground addEvent3DListener:self method:@selector(objectMoved:) forEventType:MOVE_EVENT];

The events are added to the class and, at least in my previous example, duly processed.

- (void) objectTouched:(Isgl3dEvent3D *)event {
    UITouch * touch = [[event.touches allObjects] objectAtIndex:0];
    CGPoint uiPoint = [touch locationInView:touch.view];
    _touchLocation = [self convertUIPointToView:uiPoint];
}
- (void) objectMoved:(Isgl3dEvent3D *)event {
	UITouch * touch = [[event.touches allObjects] objectAtIndex:0];
    CGPoint uiPoint = [touch locationInView:touch.view];
    _touchLocation = [self convertUIPointToView:uiPoint];
}
- (void) objectReleased:(Isgl3dEvent3D *)event {
}

The Entire Code

#import "HelloWorldView.h"
#import 
#import 


@implementation HelloWorldView

- (id) init {
	
    if ((self = [super init])) {
        
        //room dimensions and element positions
        _wallHeight = 4;
		_roomSide = 12;
		_boxSide = 1.4;
        _playerWidth = 1.5;
        _playerHeight = 3;
        
        
        //SET UP BASIC CAMERA
        [self.camera setPosition:iv3(0, 1, 7)];
        [self.camera lookAt:0 y:0 z:0];
        
        //create an empty container to receive all 3D elements
        _container = [[self.scene createNode] retain];
        
        
        //MATERIAL
        //set up texture to be used with walls (plane meshes)
        Isgl3dTextureMaterial * wallMaterial = [Isgl3dTextureMaterial materialWithTextureFile:@"wall.png" shininess:0 precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO];
        
        
        //CREATE WALL
        Isgl3dGLMesh * wallMesh = [Isgl3dPlane meshWithGeometry:_roomSide height:_wallHeight nx:10 ny:10];
        
        //back wall
        Isgl3dMeshNode * backWall = [_container createNodeWithMesh:wallMesh andMaterial:wallMaterial];
        backWall.position = iv3(0, 0, -_roomSide);
        
        //left wall
        Isgl3dMeshNode * leftWall = [_container createNodeWithMesh:wallMesh andMaterial:wallMaterial];
        leftWall.position = iv3(-_roomSide*0.5, 0, -_roomSide*0.5);
        leftWall.rotationY = 90;
        
        //right wall
        Isgl3dMeshNode * rightWall = [_container createNodeWithMesh:wallMesh andMaterial:wallMaterial];
        rightWall.position = iv3(_roomSide*0.5, 0, -_roomSide*0.5);
        rightWall.rotationY = -90;
        
        //ceiling
        Isgl3dGLMesh * ceilingMesh = [Isgl3dPlane meshWithGeometry:_roomSide height:_roomSide nx:10 ny:10];
        Isgl3dMeshNode * ceiling = [_container createNodeWithMesh:ceilingMesh andMaterial:wallMaterial];
        ceiling.position = iv3(0, _wallHeight*0.5, -_roomSide*0.5);
        ceiling.rotationX = 90;
        
        
        //MATERIAL
        Isgl3dTextureMaterial * groundMaterial = [Isgl3dTextureMaterial materialWithTextureFile:@"ground.png" shininess:0 precision:Isgl3dTexturePrecisionMedium repeatX:YES repeatY:YES];

        //make texture tile on the ground (2x horizontally, 2x vertically)
        Isgl3dUVMap * groundUVMap = [Isgl3dUVMap uvMapWithUA:0.0 vA:0.0 uB:2.0 vB:0.0 uC:0.0 vC:2.0];
        Isgl3dPlane * groundMesh = [Isgl3dPlane meshWithGeometryAndUVMap:_roomSide height:_roomSide nx:2 ny:2 uvMap:groundUVMap];
        Isgl3dMeshNode * ground = [_container createNodeWithMesh:groundMesh andMaterial:groundMaterial];
        ground.position = iv3(0,-_wallHeight*0.5,-_roomSide*0.5);
        ground.rotationX = -90;
        
        //make ground plane receive TOUCH EVENTS
        ground.interactive = YES;
        [ground addEvent3DListener:self method:@selector(objectTouched:) forEventType:TOUCH_EVENT];
        [ground addEvent3DListener:self method:@selector(objectReleased:) forEventType:RELEASE_EVENT];
        [ground addEvent3DListener:self method:@selector(objectMoved:) forEventType:MOVE_EVENT];
        
        
        
        //ADD CRATES
        NSArray *cratePosition = [NSArray arrayWithObjects:
                                  [NSValue valueWithCGPoint:CGPointMake(6,6)], 
                                  [NSValue valueWithCGPoint:CGPointMake(2,6)],
                                  [NSValue valueWithCGPoint:CGPointMake(4,10)],
                                  [NSValue valueWithCGPoint:CGPointMake(8,5)],
                                  [NSValue valueWithCGPoint:CGPointMake(6,4)],
                                  [NSValue valueWithCGPoint:CGPointMake(7,9.5f)],
                                  nil];
        //MATERIAL
        Isgl3dTextureMaterial * crateMaterial = [Isgl3dTextureMaterial materialWithTextureFile:@"crate.png" shininess:0 precision:Isgl3dTexturePrecisionMedium repeatX:YES repeatY:NO];
        
        
        //create crates
        _crates = [[NSMutableArray array] retain];
        CGPoint pos;
        int i;
        Isgl3dCube * cubeMesh = [[Isgl3dCube alloc] initWithGeometry:_boxSide height:_boxSide depth:_boxSide nx:2 ny:2];
        Isgl3dMeshNode * crate;
        for (i = 0; i < [cratePosition count]; i++) {
            pos =  [[cratePosition objectAtIndex:i] CGPointValue];
            crate = [_container createNodeWithMesh:cubeMesh andMaterial:crateMaterial];
            crate.z = -1 * pos.y;
            crate.x = pos.x - _roomSide * 0.5;
            crate.y = -_wallHeight*0.5 + _boxSide*0.5;
            crate.rotationY = (random() * RAND_MAX) * 180 / M_PI;
            [_crates addObject:crate];
        }
        
        
        //MATERIAL
        Isgl3dTextureMaterial * playerMaterial_front0 = [Isgl3dTextureMaterial materialWithTextureFile:@"player_front_0.png" shininess:0 precision:Isgl3dTexturePrecisionMedium repeatX:NO repeatY:NO];
        
        //add player plane
        Isgl3dGLMesh * playerMesh = [Isgl3dPlane meshWithGeometry:_playerWidth height:_playerHeight nx:1 ny:1];
        _player = [_container createNodeWithMesh:playerMesh andMaterial:playerMaterial_front0];
        _player.position = iv3(0, -_wallHeight*0.5 + _playerHeight*0.5, 0);
        _player.doubleSided = YES; //make it double sided so player can be flipped
	}
	return self;
}

- (void) objectTouched:(Isgl3dEvent3D *)event {
    UITouch * touch = [[event.touches allObjects] objectAtIndex:0];
    CGPoint uiPoint = [touch locationInView:touch.view];
    _touchLocation = [self convertUIPointToView:uiPoint];
}

- (void) objectMoved:(Isgl3dEvent3D *)event {
	UITouch * touch = [[event.touches allObjects] objectAtIndex:0];
    CGPoint uiPoint = [touch locationInView:touch.view];
    _touchLocation = [self convertUIPointToView:uiPoint];
    
}

- (void) objectReleased:(Isgl3dEvent3D *)event {
}

-(void) dealloc {
	[_crates release];
	[_container release];
	[super dealloc];
}


@end