// File : DogFight.c // Author : John Wojcik ( QuietBloke ) // Created : September 2000 // Desc : My first game written using my 2D Game framework. // The game is very simple. Two biplanes battle it out in the // Yaroze sky for air supremacy. // // History : // Date Author Description // -------- --------------- ---------------------------------------- // 08/09/00 QuietBloke Got the biplanes up and running. // 09/09/00 QuietBloke Wrote a nice pad handler. Removed it and placed // it into the framwork as a Pad Manager. // 10/09/00 QuietBloke Added shots for each plane. // 11/09/00 QuietBloke Spent ages putting large amounts of comments in // 28/09/00 QuietBloke Added Anti Aircraft fire at top of screen // Changed to interlaced mode // 30/09/00 QuietBloke Added collision detection for planes against enemy bullets. // 01/10/00 QuietBloke Added hangers and floor // 02/10/00 QuietBloke Completed collision detection fro planes and bullets. // Added player explosion upon death // 04/10/00 QuietBloke Added Stages to plane lifecycle. Players start in Hangers, // Take off along the ground till flying speed achieved the // Player can control to fly. // Added wall to seperate the two hangers. // Dying players now falls to ground ( or wall, hanger ) before // exploding. // 05/10/00 QuietBloke Added call to new Framework function to make the bounding // box of the planes a percentage of the actual plane actor size. // 10/10/00 QuietBloke Added Eclipse logo Advert at start of game. // Changed the look of the AAFire animation and randomly rotate // it for each explosion for variety. // 13/10/00 QuietBloke Changed startup screen to display YarozeScene Logo #include #include #include "fw.h" #include "pad.h" #define TEXTURE_PLANE ( 0x080090000 ) #define TEXTURE_SHOT ( 0x80090A20 ) #define TEXTURE_AAFIRE ( 0x80090A90 ) #define TEXTURE_GROUND ( 0x800924B0 ) #define TEXTURE_HANGER ( 0x80092900 ) #define TEXTURE_WALL ( 0x80093550 ) #define TEXTURE_DOGFIGHT ( 0x800937E0 ) #define TEXTURE_ELOGO ( 0x80096400 ) #define STAGE_SHIFT 8 #define MAX_SHOTS 10 #define PLANE_ACCEL_SHIFT 2 // Each player can be in one of several stages. #define PLAYER_LANDED 1 #define PLAYER_TAKE_OFF 2 #define PLAYER_FLYING 3 #define PLAYER_FALLING 4 // Global vars for all actors and thier costumes. int PlaneCostume; // Store the 2 plane costume id's int ShotCostume; // Store the shot costume id int AAFireCostume; // Store the 6 Anti Aircraft fire costume id's int GroundCostume; // Store the Ground Costume id int HangerCostume; // Store the 2 Hanger costume id's int WallCostume; // Store the Wall costume id int DogfightCostume; // Store the Dogfight Text costume id int ELogoCostume; // Store the Eclipse Logo costume id int WallActor; // Store the wall actor id int HangerActor[2]; // Store the two hanger actor id's int GroundActor[10]; // Store the ground actor id's ( many as required to fill screen width ) int PlaneActor[2]; // Store the two actor id's int ShotActor[2][MAX_SHOTS]; // Store the shot actor id's. 5 shots per plane. int AAFireActor[12]; // Store the Anti Aircraft fire actor id's. 5 + 2 ( one for each plane ) int XPlaneAnim[2]; // Store the animation id's for the x movement for each plane. int YPlaneAnim[2]; // Store the animation id's for the y movement for each plane. int XShotAnim[2][MAX_SHOTS]; // Store the animation id's for the x movement for each shot. int YShotAnim[2][MAX_SHOTS]; // Store the animation id's for the y movement for each shot. int CostumeAAFireAnim[12]; // Costume animations for each AAFire actor. int PlayerStage[2]; // Current stage of each player. int PlaneAccel[2]; // How fast does a plane accelerate in the direction its pointing. // All global variables used to customize gameplay int PlaneAcceleration=(1<>2; // Gravity on planes void AcceleratePlane ( int CurrentPlayer ); int CheckPlaneCollisions ( int CurrentPlayer ); void CheckShotCollisions ( int CurrentPlayer ); void SetPlayerStage ( int CurrentPlayer, int NewStage ); void init ( void ); void initGame ( void ); void initPlayer ( int Player ); void fireShot ( int CurrentPlayer, int CurrentShot ); void fireAAFire ( int CurrentAAFire ); void ShowLogo ( void ); void main ( void ) { int finished = 0; long padd; int CurrentPlayer; int CurrentShot; int CurrentAAFire; int TempSpeed; // Initialise our game framwork fwDirectInit (); // Tell the framework how many bit to right shift our stage positions in order to // convert them to real pixel positions. fwStageSetPixelShift (STAGE_SHIFT); // For debug purposes only. FntLoad ( 960,256); FntOpen (16,16,256,200,0,512); // Display the Eclipse Logo ShowLogo (); // Once only initialisation of costumes for the game init (); // Initialise everything to start a new game initGame (); while ( finished == 0 ) { // Get the latest state of the pads. fwPadRead(); for ( CurrentPlayer = 0; CurrentPlayer < 2; CurrentPlayer++ ) { // Find out which buttons are up/down padd = fwPadGetDown ( CurrentPlayer ); // If either player hits start then the game is over. if ( padd & PADstart ) { finished = 1; } switch ( PlayerStage[CurrentPlayer] ) { case PLAYER_LANDED: // Do nothing // If player hits fire the we take off // Find out which buttons are up/down if ( padd & PADRup ) { SetPlayerStage ( CurrentPlayer, PLAYER_TAKE_OFF ); } break; case PLAYER_TAKE_OFF: if ( CurrentPlayer == 0 ) fwActorAddVelocity ( PlaneActor[0], 90, 4 ); else fwActorAddVelocity ( PlaneActor[1], 270, 4 ); TempSpeed = fwAnimateGetChangeValue( XPlaneAnim[CurrentPlayer] ); if ( TempSpeed >= ( PlaneAcceleration*1.5 ) || TempSpeed <= ( (PlaneAcceleration) * -1.5 ) ) { // Lift the plane up one pixel ( otherwise we will collide with the ground ) fwActorAddVelocity ( PlaneActor[CurrentPlayer] ,0, 1<> PLANE_ACCEL_SHIFT)< PlaneStallSpeed ) { SetPlayerStage ( CurrentPlayer, PLAYER_FALLING ); } // Now see what the player is is trying to do // Turning clockwise ? if ( padd & PADLright ) { fwActorSetAngle ( PlaneActor[CurrentPlayer], fwActorGetAngle ( PlaneActor[CurrentPlayer] ) + PlaneRotation ); } // Turning anticlockwise ? if ( padd & PADLleft ) { fwActorSetAngle ( PlaneActor[CurrentPlayer], fwActorGetAngle ( PlaneActor[CurrentPlayer] ) - PlaneRotation ); } // Has the player decided to fire a shot ? // Here we want to trap whether the button has just been pressed. // The player must release and press the button again in order to fire the next shot. if ( fwPadGetPressed ( CurrentPlayer ) & PADRdown ) { // See if we have a spare shot available for ( CurrentShot = 0; CurrentShot < MaxShots; CurrentShot++ ) { if ( fwActorGetOnStage ( ShotActor [CurrentPlayer][CurrentShot ] ) == OFF_STAGE ) { // We have a spare shot.. so lets fire it fireShot ( CurrentPlayer, CurrentShot ); break; } } } } break; case PLAYER_FALLING: // if we have hit something then its all over // show a pretty explosion and put the plane back in its hanger if ( CheckPlaneCollisions ( CurrentPlayer ) == 0 ) { // Now see what the player is is trying to do // Turning clockwise ? if ( padd & PADLright ) { fwActorSetAngle ( PlaneActor[CurrentPlayer], fwActorGetAngle ( PlaneActor[CurrentPlayer] ) + PlaneRotation ); } // Turning anticlockwise ? if ( padd & PADLleft ) { fwActorSetAngle ( PlaneActor[CurrentPlayer], fwActorGetAngle ( PlaneActor[CurrentPlayer] ) - PlaneRotation ); } // If strating engine then allow it // If the speed is not fast enough then it'll just stall again if ( padd & PADRup ) { SetPlayerStage ( CurrentPlayer, PLAYER_FLYING ); } } else { // Kick off an AAFire explosion wherever the collision took place fwActorPos ( AAFireActor [TotalAAFire-1-CurrentPlayer], fwAnimateGetValue ( XPlaneAnim[CurrentPlayer] ), fwAnimateGetValue ( YPlaneAnim[CurrentPlayer] ) ); // Set the lifespan fwActorSetLifeSpan ( AAFireActor[TotalAAFire-1-CurrentPlayer], 60 ); // Start the animation fwAnimateStart ( CostumeAAFireAnim[TotalAAFire-1-CurrentPlayer] ); // Mark the AAFire as being on stage fwActorSetOnStage ( AAFireActor[TotalAAFire-1-CurrentPlayer], ON_STAGE ); SetPlayerStage ( CurrentPlayer, PLAYER_LANDED ); } break; } } // At random intervals // Kick off any Anti Aircraft fire which are currently off stage if ( rand() < 2000 ) { for ( CurrentAAFire = 0; CurrentAAFire < ( TotalAAFire -2 ); CurrentAAFire++ ) { if ( fwActorGetOnStage ( AAFireActor[CurrentAAFire] ) == OFF_STAGE ) { fireAAFire ( CurrentAAFire ); // Stop now.. no point in firing off more than 1 at a time. break; } } } fwDirectRefresh (); } // Just to ram the point home Display the Eclipse Logo again ShowLogo (); } /* void AcceleratePlane ( int CurrentPlayer ) { int TempSpeed; // static int Counter; int AllowedSpeed; int Multiplier; int SpeedRange; // Before we do anything decelerate the plane TempSpeed = fwAnimateGetChangeValue( XPlaneAnim[CurrentPlayer] ); TempSpeed = TempSpeed * PlaneDeceleration / 100; fwAnimateSetChangeValue ( XPlaneAnim[CurrentPlayer], TempSpeed ); TempSpeed = fwAnimateGetChangeValue( YPlaneAnim[CurrentPlayer] ); TempSpeed = TempSpeed * PlaneDeceleration / 100; fwAnimateSetChangeValue ( YPlaneAnim[CurrentPlayer], TempSpeed ); // Calculate the actual plane acceleration // This varies depending on the angle of the plane // Counter++; // if ( Counter > 10 ) // { // Counter=0; // PlaneAccel[CurrentPlayer] = PlaneAccel[CurrentPlayer] + ( fwAngleGetCos ( fwActorGetAngle(PlaneActor[CurrentPlayer] ) ) >> 8); // if ( PlaneAccel[CurrentPlayer] > PlaneMaxAccel ) // PlaneAccel[CurrentPlayer] = PlaneMaxAccel; // if ( PlaneAccel[CurrentPlayer] < PlaneMinAccel ) // PlaneAccel[CurrentPlayer] = PlaneMinAccel; // } // Multiplier = abs(PlaneAcceleration * ( fwAngleGetCos ( ( fwActorGetAngle(PlaneActor[CurrentPlayer] ) >> 1 ) +90 ) ) >> 8); Multiplier = abs(fwAngleGetCos ( ( fwActorGetAngle( PlaneActor[CurrentPlayer] ) >> 1 ) +90 ) ); SpeedRange = PlaneMaxAccel - PlaneMinAccel; AllowedSpeed = PlaneMinAccel + ( SpeedRange * Multiplier / 256 ); if ( PlaneAccel[CurrentPlayer] < AllowedSpeed ) PlaneAccel[CurrentPlayer]++; if ( PlaneAccel[CurrentPlayer] > AllowedSpeed ) PlaneAccel[CurrentPlayer]--; // Now apply the plane acceleration fwActorAddVelocity ( PlaneActor[CurrentPlayer], fwActorGetAngle ( PlaneActor[CurrentPlayer] ), PlaneAccel[CurrentPlayer] ); // NOTE : deceleration is performed as a percentage of the previous // speed. Acceleration is a simple addition. Therefore drag increases // as speed increases and we naturally get a max speed for the plane. // Now add the gravity factor fwActorAddVelocity ( PlaneActor[CurrentPlayer] ,180, Gravity ); } */ void SetPlayerStage ( int CurrentPlayer, int NewStage ) { switch ( NewStage ) { case PLAYER_LANDED: initPlayer ( CurrentPlayer ); // Player does not collide with anything fwActorCollisionLayers ( PlaneActor[CurrentPlayer], 0 ); break; case PLAYER_TAKE_OFF: // Player can collide with the other player and his/her shots if ( CurrentPlayer == 0 ) fwActorCollisionLayers ( PlaneActor[0], STAGE_LAYER_2 | STAGE_LAYER_7 ); else fwActorCollisionLayers ( PlaneActor[1], STAGE_LAYER_1 | STAGE_LAYER_6 ); break; case PLAYER_FLYING: // Player can collide with the other player and his/her shots + AA Fire + Ground + Wall + Hanger if ( CurrentPlayer == 0 ) fwActorCollisionLayers ( PlaneActor[0], STAGE_LAYER_2 | STAGE_LAYER_3 | STAGE_LAYER_4 | STAGE_LAYER_5 | STAGE_LAYER_7 ); else fwActorCollisionLayers ( PlaneActor[1], STAGE_LAYER_1 | STAGE_LAYER_3 | STAGE_LAYER_4 | STAGE_LAYER_5 | STAGE_LAYER_6 ); break; case PLAYER_FALLING: // Player 0 can collide with ground, wall and hangers fwActorCollisionLayers ( PlaneActor[CurrentPlayer], STAGE_LAYER_4 | STAGE_LAYER_5 ); break; } PlayerStage [CurrentPlayer] = NewStage; } int CheckPlaneCollisions ( int CurrentPlayer ) { int CollidedActor; int CurrentShot; int ReturnValue; ReturnValue = 0; CollidedActor = fwActorFirstCollision ( PlaneActor[CurrentPlayer] ); while ( CollidedActor != -1 ) { ReturnValue = 1; // If the collision was with a bullet then the bullet is dead for ( CurrentShot = 0; CurrentShot < MaxShots; CurrentShot++ ) { if ( ShotActor[0][CurrentShot] == CollidedActor || ShotActor[1][CurrentShot] == CollidedActor ) { fwActorSetOnStage( CollidedActor, OFF_STAGE ); break; } } CollidedActor = fwActorNextCollision ( PlaneActor[CurrentPlayer], CollidedActor ); // The player shall die hahahahahaha SetPlayerStage ( CurrentPlayer, PLAYER_FALLING ); // initPlayer ( CurrentPlayer ); } return ( ReturnValue ); } void CheckShotCollisions ( int CurrentPlayer ) { int CollidedActor; int CurrentShot; int TargetShot; // Now see if our shots have hit anything ( other than the other player ) for ( CurrentShot = 0; CurrentShot < MaxShots; CurrentShot++ ) { CollidedActor = fwActorFirstCollision ( ShotActor[CurrentPlayer][CurrentShot] ); if ( CollidedActor != -1 ) { // We have hit something therefore we must die fwActorSetOnStage ( ShotActor[CurrentPlayer][CurrentShot], OFF_STAGE ); // If we collided with another shot then kill that too for ( TargetShot = 0; TargetShot < MaxShots; TargetShot++ ) { if ( CollidedActor == ShotActor[0][TargetShot] ) { fwActorSetOnStage ( ShotActor[0][TargetShot], OFF_STAGE ); } if ( CollidedActor == ShotActor[1][TargetShot] ) { fwActorSetOnStage ( ShotActor[1][TargetShot], OFF_STAGE ); } } } } } void init ( void ) { // Lets talk layers : // Shots from player 1 = Layer 1 // Shots from player 2 = Layer 2 // AA Fire = Layer 3 // Ground = Layer 4 // Hangers = Layer 5 // Player 1 = Layer 6 // Player 2 = Layer 7 int CurrentPlayer; int CurrentShot; int CurrentAAFire; int WallYPos; int HangerYPos; int CurrentGround; int GroundWidth; int NextGroundXPos; int GroundYPos; int PlaneStartY; fwActorReset (); fwAnimateReset (); fwDirectSetBackGround ( 0, 0, 128 ); // Load up our costumes. PlaneCostume = fwCostumeAdd ( TEXTURE_PLANE, 2, 1 ); ShotCostume = fwCostumeAdd ( TEXTURE_SHOT, 1, 1 ); AAFireCostume = fwCostumeAdd ( TEXTURE_AAFIRE, 2, 3 ); GroundCostume = fwCostumeAdd ( TEXTURE_GROUND, 1, 1 ); HangerCostume = fwCostumeAdd ( TEXTURE_HANGER, 1, 2 ); WallCostume = fwCostumeAdd ( TEXTURE_WALL, 1, 1 ); // Set up the screen details GroundActor[0] = fwActorAdd ( GroundCostume ); fwActorSetLayer( GroundActor[0], STAGE_LAYER_4 ); fwActorSetOnStage ( GroundActor[0], ON_STAGE ); GroundYPos = ( fwDirectScreenHeight() - ( fwActorGetPixelHeight ( GroundActor[0] ) >> 1 ) ) << STAGE_SHIFT; fwActorPos ( GroundActor[0], ( fwActorGetPixelWidth( GroundActor[0] ) >> 1 ) << STAGE_SHIFT, GroundYPos ); CurrentGround = 0; GroundWidth = fwActorGetPixelWidth ( GroundActor[0] ) << STAGE_SHIFT; NextGroundXPos = GroundWidth; while ( NextGroundXPos < ( fwDirectScreenWidth() << STAGE_SHIFT ) ) { CurrentGround++; GroundActor[CurrentGround] = fwActorAdd ( GroundCostume ); fwActorPos ( GroundActor[CurrentGround], NextGroundXPos+ ( GroundWidth >> 1 ), GroundYPos ); fwActorSetLayer( GroundActor[CurrentGround], STAGE_LAYER_4 ); NextGroundXPos+=GroundWidth; } WallActor = fwActorAdd ( WallCostume ); fwActorSetLayer( WallActor, STAGE_LAYER_4 ); fwActorSetOnStage ( WallActor, ON_STAGE ); WallYPos = ( ( fwDirectScreenHeight() - fwActorGetPixelHeight ( GroundActor[0] ) ) - ( fwActorGetPixelHeight ( WallActor ) >> 1 ) ) << STAGE_SHIFT; fwActorPos ( WallActor, fwDirectScreenWidth() << (STAGE_SHIFT-1), WallYPos ); HangerActor[0] = fwActorAdd ( HangerCostume ); fwActorSetLayer( HangerActor[0], STAGE_LAYER_5 ); HangerActor[1] = fwActorAdd ( HangerCostume+1 ); fwActorSetLayer( HangerActor[1], STAGE_LAYER_5 ); HangerYPos = ( ( fwDirectScreenHeight() - fwActorGetPixelHeight ( GroundActor[0] ) ) - ( fwActorGetPixelHeight ( HangerActor[0] ) >> 1 ) ) << STAGE_SHIFT; fwActorPos ( HangerActor[0], ( fwActorGetPixelWidth( HangerActor[0] ) >> 1 ) << (STAGE_SHIFT), HangerYPos ); fwActorPos ( HangerActor[1], ( fwDirectScreenWidth() - ( fwActorGetPixelWidth( HangerActor[1] ) >> 1 ) ) << (STAGE_SHIFT), HangerYPos ); // initialise the animations // The planes and shots will be wraping round the stage // The parameters for the animations are ( To save on looking up the fwanimat.h file ): // 1. Kind of animation. In this case its repeat so the object will wrap around on the screen // 2. How many times to repeat. -1 indicates that the animation is set to repeat indefinately // 3. What is the initial value of the animation. ( Start position of actor ) // 4. Minimum value allowed. // 5. Maximum value allowed // 6. Initial change value for the animation. 0 = stationary. The main game loop will modify // the change values to move the actors about. // 7. Pasue time. Length of frames to wait between changing animation value. Set to zero as // in our case we want to apply the animation for every frame. PlaneStartY = ( ( fwDirectScreenHeight() - fwActorGetPixelHeight ( GroundActor[0] ) ) - ( fwActorGetPixelHeight ( PlaneActor[0] ) >> 1 ) ); XPlaneAnim[0] = fwAnimateAdd ( ANIMATE_REPEAT, -1, ( ( fwActorGetPixelWidth(PlaneActor[0] ) >>1 )<> 1 ) ) << STAGE_SHIFT, 0, fwDirectScreenWidth()<> 15) << STAGE_SHIFT, fwActorGetPixelHeight ( AAFireActor [CurrentAAFire] ) << (STAGE_SHIFT-1) ); // Set the lifespan fwActorSetLifeSpan ( AAFireActor[CurrentAAFire], 60 ); // Start the animation fwAnimateStart ( CostumeAAFireAnim[CurrentAAFire] ); // Mark the AAFire as being on stage fwActorSetOnStage ( AAFireActor[CurrentAAFire], ON_STAGE ); // To give a bit of variety randomly rotate the actor fwActorSetAngle ( AAFireActor[CurrentAAFire], ( rand () * 360) >> 15 ); } void ShowLogo ( void ) { int ELogoActor; int xpos; int ypos; int pause; int brightness; fwActorReset (); fwAnimateReset (); ELogoCostume = fwCostumeAdd ( TEXTURE_ELOGO, 1, 1 ); ELogoActor = fwActorAdd ( ELogoCostume ); ypos = ( fwDirectScreenHeight() - fwActorGetPixelWidth ( ELogoActor ) >> 1 ) << STAGE_SHIFT; xpos = ( fwDirectScreenWidth() - fwActorGetPixelHeight ( ELogoActor ) >> 1 ) << STAGE_SHIFT; ypos = ( fwDirectScreenHeight() >> 1 ) << STAGE_SHIFT; xpos = ( fwDirectScreenWidth() >> 1 ) << STAGE_SHIFT; fwActorPos ( ELogoActor, xpos, ypos ); fwActorSetAngle (ELogoActor, 90 ); fwActorSetScale ( ELogoActor, 4096 << 1 ); fwActorSetOnStage ( ELogoActor, ON_STAGE ); brightness = 255; fwDirectSetBackGround ( brightness, brightness, brightness ); fwActorSetRGB ( ELogoActor, brightness>>1, brightness>>1, brightness>>1 ); for ( pause = 0; pause < 255; pause++ ) { fwDirectRefresh (); } for ( brightness = 255; brightness > 0; brightness-- ) { fwDirectSetBackGround ( brightness, brightness, brightness ); fwActorSetRGB ( ELogoActor, brightness>>1, brightness>>1, brightness>>1 ); fwDirectRefresh (); } } void AcceleratePlane ( int CurrentPlayer ) { int TempSpeed; int TargetSpeedModifier; int AllowedSpeed; int PlaneAngle; // Before we do anything decelerate the plane ( Plane drag ) TempSpeed = fwAnimateGetChangeValue( XPlaneAnim[CurrentPlayer] ); TempSpeed = TempSpeed * PlaneDeceleration / 100; fwAnimateSetChangeValue ( XPlaneAnim[CurrentPlayer], TempSpeed ); TempSpeed = fwAnimateGetChangeValue( YPlaneAnim[CurrentPlayer] ); TempSpeed = TempSpeed * PlaneDeceleration / 100; fwAnimateSetChangeValue ( YPlaneAnim[CurrentPlayer], TempSpeed ); PlaneAngle = fwActorGetAngle( PlaneActor[CurrentPlayer] ); // Sin/Cos functions return a value between 0 and 255 TargetSpeedModifier = ( fwAngleGetCos ( PlaneAngle ) * fwAngleGetCos ( PlaneAngle ) ) >> 8; // Modifier becomes a value between 0 and .25 * PlaneAcceleration TargetSpeedModifier = (TargetSpeedModifier * PlaneAcceleration ) >> 10; if ( PlaneAngle < 90 || PlaneAngle > 270 ) TargetSpeedModifier *= -1; // Because we are dealing with int's the PlaneAccel is stored as a fixed point field // Convert calculations appropriately. AllowedSpeed = ( PlaneAcceleration + TargetSpeedModifier ) << PLANE_ACCEL_SHIFT; if ( PlaneAccel[CurrentPlayer] < AllowedSpeed ) PlaneAccel[CurrentPlayer]++; if ( PlaneAccel[CurrentPlayer] > AllowedSpeed ) PlaneAccel[CurrentPlayer]--; // Now apply the plane acceleration fwActorAddVelocity ( PlaneActor[CurrentPlayer], fwActorGetAngle ( PlaneActor[CurrentPlayer] ), ( PlaneAccel[CurrentPlayer] >> PLANE_ACCEL_SHIFT ) ); // NOTE : deceleration is performed as a percentage of the previous // speed. Acceleration is a simple addition. Therefore drag increases // as speed increases and we naturally get a max speed for the plane. // Now add the gravity factor fwActorAddVelocity ( PlaneActor[CurrentPlayer] ,180, Gravity ); }