Improved Ship Simulator: ShipSim2


Improved Ship Simulator: ShipSim2

The previous project was good but not much fun to use. In this project, we will make the simulation much more interesting by adding two islands, a region with shallow water in which you can run aground if you are not careful, three mines which can end the game if you hit them, a dock that you must maneuver the ship into at a controlled speed, camera controls that allow you to move the camera independently of the ship, and overhead views of various portions of the game scene. Figure 6.3 shows a screenshot of a game in progress, and Figure 6.4 gives an overhead view of the entire layout.

click to expand
Figure 6.3: ShipSim2 screenshot. The north side of main island.
click to expand
Figure 6.4: Overhead view of the game scene. The dock is on the north side of the island.

In the previous program, we used the CameraFollow command so that the camera would always keep the ship in sight. There are two problems associated with doing this. First, we cannot look around as we might wish (say for maneuvering into the dock to get a better view angle) because the camera is locked in position ”attempts to move it using one of the camera move commands are fruitless since the follow command prevents this. Second, as you may have noticed, the camera changes its orientation as the ship turns, which can give the illusion that the ship is moving in a direction that it s not. This is because we specified a distance x and z from which to follow, and when the ship turns 90 degrees (pi/2 radians!), the x and z values are interchanged with reference to the ship.

Since the ship s motion will involve a relatively large area, we need the camera to move around, so we will use the Walk command to enable us to move the camera around using the arrow keys:

 ocamera1.Walk(); 

But, even moving the camera around can be laborious, especially if your hands are full maneuvering into port, so we will also view the scene from above height. Before we came to this solution, we first tried having one camera view in a separate window, but in order to see the entire game scene, the height of the camera had to be so high that it made the ship invisible. We then decided to have four overhead views: a South view that showed the ship on the south side of the island where it first appears when the game begins, an East Channel view that shows the east end of the island where the shallow water is located, a Mid-Channel view that shows the mine locations and useful paths for heading to the dock on the north side of the island, and a Dock view to help you with close maneuvering at the dock.

Instead of having four separate windows , one child window is used for these overhead views, and the program user selects which of the four views to see. The following code shows the main window and child window being made, and Autorefresh being disabled:

 owindow1 = New Window("Ship Simulator2",640,480,Window.STANDARD);// one line owindow2 = New Window(owindow1,"Overhead View",200,150,Window.GAME);// one line owindow2.SetPosition(390,5); //window maintenance owindow1.SetAutoRefresh(OFF); owindow2.SetAutoRefresh(OFF); owindow1.Show(); owindow2.Show(); 

We then make the four buttons that enable the specific camera views to be selected. Notice that functions dochange , dochange2 , and so on are called when the buttons are clicked:

 mybutton7 = New Button(owindow1,"Dock Overview",5,5,120,15);//one line mybutton7.OnClick = dochange; mybutton8 = New Button(owindow1,"East Channel Overview",5,25,120,15);// one line mybutton8.OnClick = dochange2; mybutton9 = New Button(owindow1,"Mid-Channel Overview",5,45,120,15);// one line mybutton9.OnClick = dochange3; mybutton10 = New Button(owindow1,"South Overview",5,65,120,15);// one line mybutton10.OnClick = dochange4; 

Function dochange is shown in the following code, which illustrates how the camera is positioned for each overhead view:

 Function dochange { ocamera2.SetPosition(440,1000,3400); } 

As mentioned earlier, this simulation also features an area near the east end of the island where the water is very shallow. If the user fails to keep his eye on the depth gauge on the instrument panel, the ship will run aground. Running the ship aground will cause a sprite (named aground ) to appear on the screen informing the user that he has lost his command and promotion ”running a ship aground is not a career-building maneuver in the naval services!

The very shallow area (depth = 10 feet) is surrounded by a moderately shallow area with a depth of 20 feet. Normally, the depth in this game is 100 feet, so if the user sees the depth reduce to 20 feet, he knows he d better change course. We first identified the moderately shallow warning region as a rectangular area located at x ( xpos1 in the program) between “2000 and “3000, and z ( zpos1 in the program) between “2800 and “3800. The very shallow area is at x between “2000 and “2600 and z between “3000 and “3600. By checking the position of the ship using some if statements we can tell if the ship is in either zone. The code below tells it all:

 // Check for depthvery shallow area first If(xpos1<2000 && xpos1>2600)   {   If(zpos1<3000 && zpos1>3600)     {     depth = 10;     aground.Show();     }   } // Check depthwarning area   If(xpos1<2000 && xpos1>3000 &&  zpos1<2800 && zpos1>3800)//one line    {      depth = 20;    }   Else    {     depth = 100;    } 

You may be wondering how we knew the exact location of the shallow areas. This was a fairly laborious process: once the islands were imported into the previous game, a small sphere about ten units above sea level was created and moved around the islands with the Walk command, followed by the camera. At the same time, the position of the sphere (x and z coordinates) was sent to text boxes on the screen so that we could know where the sphere was at any time.

We knew we wanted the shallow area near the east end of the island (to the right side when the game first begins), so we moved the sphere around and wrote down the coordinates when it looked like a suitable location was found. This is the same procedure used to determine where to place the mines and the dock pilings on the north side of the island. It was a lot of work, but there did not seem to be any alternative since we were not sure of the size of the islands. As you might recall, they were drawn with MilkShape 3D , imported into our game, and scaled up.

Mines were placed between the two islands, on the north side of the main island, and pilings to dock against were placed on the southwest side. The pilings were made using the CreateBrick command (see the following code), which is a very useful tool for making rectangular shaped 3D objects. In the parentheses of the CreateBrick command go the x, y, and z dimensions of the brick.

 mine1 = oworld.CreateSphere(10,10,10); Mine1.SetPosition(1220,0,3700); mine2 = oworld.CreateSphere(10,10,10); Mine2.SetPosition(975,0,3525); mine3 = oworld.CreateSphere(10,10,10); Mine3.SetPosition(400,0,3680); //Pilings to dock against Pile1 = oworld.CreateBrick(5,10,5); Pile1.SetPosition(500,0,3300); pilemat = New Material(oworld,GetRGB(255,100,100),"pilemat"); Pilemat.SetFlat(ON); Pile1.SetGouraud(ON); Pile1.ReplaceMaterial(pilemat); Pile2 = oworld.CreateBrick(5,10,5); Pile2.SetPosition(500,0,3260); Pile2.ReplaceMaterial(Pilemat); 

We then decided that the following criteria would apply for a successful docking maneuver:

  • The ship s speed must be less than two knots (this is a little fast, but we can probably still walk away without too much damage).

  • We must be able to determine the location of the ship in a rectangular area near the pilings (this can be determined very laboriously by moving the ship around).

  • The bow thruster setting, thrustfac, cannot be greater than 0.0008 (about mid-setting).

The code below describes how we determine if a proper docking was made:

 xcrit = myship.GetX;   zcrit = myship.GetZ;   If(xcrit< 455 && xcrit>430 && zcrit<3260 && zcrit >     3424)// one line   {     If(knots<2)      {       SetText("NICE!");      }      Else      {      SetText("TOO FAST!");      }   } 

Why is the determination of the area where the ship is in reference to the dock laborious if we can get the x and z coordinates of the ship easily and we know the location of the pilings? The reason is that the x and z location of the ship is somewhere near the center of the ship ”not at its edges. We are not sure where the edges of the ship are located because the ship is irregularly shaped! As a result, we must experiment to find out exactly when a collision takes place.

This brings up a very important point: collision detection is not exact with complex objects like ships. If you try running the ship into the island, you will see that collision is not detected until the ship is about halfway into the island. In the same way, running over a mine may not be detected as a collision. For more exact collision detection, you will have to use a scheme similar to the one we just described, based on the location of the ship in relation to the mines or the island. This is not an easy task with the island because its shape is also irregular!

 On the CD    You should now type in the following code. You will need to import the island (the same island, shipisle1 , is used twice to make two islands), the sprite aground the ship from the previous project, as well as the various sprites from the CD-ROM. The island is textured using the picture grass, which can be found in Jamagic/Help/Tutorials/Simple/Final Project/Pictures. This is in the same location as the water image used to texture the water surface.

Once you have run the program, keep in mind that you can adjust several things: you can move the main camera with the arrow keys and you can change overhead views with the buttons. Also, remember that when the ship is facing east, you should watch for the shallow parts near the east end of the island!

Ship Simulator 2 Code

 //ShipSim2 program. // Import two islands drawn with MilkShape. owindow1 = New Window("Ship Simulator2",640,480,Window.STANDARD);// one line owindow2 = New Window(owindow1,"Satellite //View",200,150,Window.GAME);//one line owindow2.SetPosition(390,5); //Window maintenance owindow1.SetAutoRefresh(OFF); owindow2.SetAutoRefresh(OFF); owindow1.Show(); owindow2.Show(); oworld = New World(); //Import ship. oworld.Load("bismark"); myship= oworld.GetObject(oworld.GetNObjects()1); myship.Scale(0.008); myship.SetPosition(0,0,2300); myship.TurnRight(Pi/2); myship.SetGouraud(ON); //Import island. oworld.Load("shipisle1"); isle1= oworld.GetObject(oworld.GetNObjects()1); isle1.Scale(50,30,30); isle1.SetPosition(0,0,3000); otexture2 = New Texture(oworld,"grass"); matisle = New Material(oworld,otexture2); matisle.SetMapped(ON); isle1.SetGouraud(ON); isle1.ReplaceMaterial(matisle); otexture2.SetTiling(20,20); isle1.SetStatic(); //Import second island. oworld.Load("shipisle1"); isle2= oworld.GetObject(oworld.GetNObjects()1); isle2.Scale(20,15,10); isle2.SetPosition(0,0,4500); isle2.TurnRight(Pi/4); isle2.SetGouraud(ON); isle2.ReplaceMaterial(matisle); isle2.SetStatic(); // Mines mine1 = oworld.CreateSphere(10,10,10); Mine1.SetPosition(1220,0,3700); mine2 = oworld.CreateSphere(10,10,10); Mine2.SetPosition(975,0,3525); mine3 = oworld.CreateSphere(10,10,10); Mine3.SetPosition(400,0,3680); Mine1.SetStatic; Mine2.SetStatic; Mine3.SetStatic; //Pilings to dock against Pile1 = oworld.CreateBrick(5,10,5); Pile1.SetPosition(500,0,3300); pilemat = New Material(oworld,GetRGB(255,100,100),"pilemat");//one line Pilemat.SetFlat(ON); Pile1.SetGouraud(ON); Pile1.ReplaceMaterial(pilemat); Pile2 = oworld.CreateBrick(5,10,5); Pile2.SetPosition(500,0,3260); Pile2.ReplaceMaterial(Pilemat); Pile1.SetStatic; Pile2.SetStatic; //Put in sea surface. myplane = oworld.CreatePlane(20000,20000); myplane.SetPosition(0,0,0); otexture = New Texture(oworld,"water"); matplane = New Material(oworld,otexture); matplane.SetMapped(ON); myplane.SetGouraud(ON); myplane.ReplaceMaterial(matplane); otexture.SetTiling(20,20); //Move the texture for illusion of moving water. matplane.AnimUV(0,0.00025); //Other plane stuff myplane.SetStatic(); myplane.SetAngle(Pi/2.,0,0); //Camera ocamera1 = New Camera(oworld,owindow1); ocamera1.SetBackgroundColor(GetRGB(0,150,200)); ocamera1.SetPosition(0,200,1000); ocamera1.TurnRight(Pi); ocamera1.Walk(); oworld.Optimize(ocamera1); // Brighten it up. mylight = New Light(oworld, ocamera1, GetRGB(255,255,255), 10000,200);//one line ocamera2 = New Camera(oworld,owindow2); ocamera2.SetPosition(600,2500,2500); ocamera2.SetBackgroundColor(GetRGB(0,150,200)); ocamera2.TurnDown(Pi/2.0); oworld.Optimize(ocamera2); //Initialize tf = 0.0; //neg = 1.0; //time = 0.0; speed = 0.0; movefac = 0.; turnfac = 0.0; knots = 0.0; rudderangle = 0.0; depth = 100; thrustfac = 0.0; //Buttons for ship control mybutton1 = New Button(owindow1,"Forward",160,355,55,15); mybutton1.OnClick = speedup; mybutton2 = New Button(owindow1,"Back",160,440,55,15); mybutton2.OnClick = speeddown; mybutton3 = New Button(owindow1,"Right",55,440,40,15); mybutton3.OnClick = rightrudder; mybutton4 = New Button(owindow1,"Left",10,440,40,15); mybutton4.OnClick = leftrudder; mybutton5 = New Button(owindow1,"L",100,425,15,15); mybutton5.OnClick = leftthruster; mybutton6 = New Button(owindow1,"R",135,425,15,15); mybutton6.OnClick = rightthruster; mybutton7 = New Button(owindow1,"Dock Overview",5,5,120,15);//one line mybutton7.OnClick = dochange; mybutton8 = New Button(owindow1,"East Channel Overview",5,25,120,15);// one line mybutton8.OnClick = dochange2; mybutton9 = New Button(owindow1,"Mid-Channel     Overview",5,45,120,15);// one line mybutton9.OnClick = dochange3; mybutton10 = New Button(owindow1,"South Overview",5,65,120,15);//one line mybutton10.OnClick = dochange4; // text boxes mytext1 = New StaticText(owindow1,"Bow Thruster",90,370,65,15);//one line mytext1.SetBackColor(GetRGB(0,0,0)); mytext1.SetColor(GetRGB(255,255,255)); mytext2 = New StaticText(owindow1," ",15,350,75,15); mytext2.SetBackColor(GetRGB(0,0,0)); mytext2.SetColor(GetRGB(255,255,255)); mytext3 = New StaticText(owindow1,"Depth: ",90,350,65,15);//one line mytext3.SetBackColor(GetRGB(0,105,50)); mytext3.SetColor(GetRGB(255,255,255)); time1 = System.GetElapsedTime; xpos1 = myship.GetX; zpos1 = myship.GetZ; //Instruments mypanel = New Sprite(0,350,"panel"); mypanel.SetScale(0.75,0.75); mywheel = New Sprite(50,400,"wheel"); mywheel.SetScale(0.75,0.75); indich = New Sprite(180,380,"hindic"); indich.SetScale(0.35,0.5); thruster = New Sprite(155,385,"thruster"); thruster.SetScale(0.60,0.60); thruster.SetAngle(90,FALSE); vindic = New Sprite(108,400,"vindic"); vindic.SetScale(0.4,0.2); aground = New Sprite(100,200,"aground"); aground.Hide(); //Set collision detection. myship.SetCollision(TRUE,Object.COLLISION_TYPE_STOP); oworld.OnCollide = docollision; //Game loop While(1) { //Display depth. mytext3.SetText("Depth: "+ depth); // Window cleaning ocamera1.ActivateRender(); owindow1.Refresh(); ocamera2.ActivateRender(); owindow2.Refresh(); //Move the ship // //straight ahead. myship.Move(movefac); //Due to rudders myship.TurnRight(turnfac); //Due to thrusters If(knots<2) { myship.TurnRight(thrustfac); } // Calculate ship speed.  time2 = System.GetElapsedTime;  dift = Abs(time2  time1);   // Check the distance covered every second.   If (dift > 1000)      {    //Calculate speed.     xdif = myship.GetX  xpos1;     zdif = myship.GetZ  zpos1;     dist = Sqrt(xdif * xdif + zdif*zdif);     speedfps = dist;        knots = speedfps * 36./60.;        time1 = System.GetElapsedTime;        mytext2.SetText("Knots: " + knots);        xpos1 = myship.GetX;     zpos1 = myship.GetZ;       } // Check for depthvery shallow area first. If(xpos1<2000 && xpos1>2600)   {   If(zpos1<3000 && zpos1>3600)     {     depth = 10;        aground.Show();        }    } // Check depthwarning area.   If(xpos1<2000 && xpos1>3000 && zpos1<2800 && zpos1>   3800)//one line    {      depth = 20;        aground.Hide();    }   Else    {     depth = 100;        aground.Hide();    } // Check for proper or improper docking.    //are at (500,0,3260) // and (500,0,3300). // Criteria will be: speed less than 2 knots, // turning rate (thrustfac) less than 0.0008(half of max //thruster setting), // within 20 pixels in x coordinate, and in between two z //coordinates.   xcrit = myship.GetX;   zcrit = myship.GetZ;   If(xcrit< 455  && xcrit>430 && zcrit<3260 && zcrit >    3424)//one line   {     If(knots<2)      {       SetText("NICE!");      }      Else      {      SetText("TOO FAST!");      }   } }  Function speedup  {   If (knots<30)    {    movefac = movefac + 0.04;    }    turnfac = rudderangle* knots/10.;     If(movefac <0)    {     turnfac = turnfac* 1;    }    //Move the speed indicator indich. movefac = + 1.2    //corresponds    // to a 10 pixel change on the screen.    //movefac = 0 corresponds to y = 380    hpos = 380  (movefac/1.2)*10.;    indich.Set(180,hpos);  } Function speeddown  {    movefac = movefac  0.04;    If(movefac< 0.6)     {      movefac = 0.6;      }    turnfac = rudderangle* knots/10.;    If(movefac <0)    {     turnfac = turnfac*1.;    }    hpos = 380  (movefac/1.2)*10;    indich.Set(180,hpos);  } Function rightrudder  {    rudderangle = rudderangle + 0.00010;    If (rudderangle>0.001)     {         rudderangle = 0.001;        }    turnfac = rudderangle* knots/10.;    //.001 corresponds to 30 degrees of rudder, so    // conversion    // factor is 30,000.    rudderconvert = rudderangle* 30000;    If(movefac<0)     {          turnfac = turnfac*1;        }     mywheel.SetAngle(rudderconvert*2.0,FALSE); }  Function leftrudder  {    rudderangle = rudderangle  0.00010;    If (rudderangle<0.001)     {         rudderangle = 0.001;         }    turnfac = rudderangle* knots/10.;    rudderconvert = rudderangle* 30000;    If(movefac<0)     {     turnfac = turnfac*1;     }      mywheel.SetAngle(rudderconvert*2.0,FALSE);  } Function rightthruster { thrustfac = thrustfac +  0.00008;  If(thrustfac>0.0016)  {   thrustfac = 0.0016;  }  //Move the thruster indicator indich.  //thrustfac = 0.0008  //corresponds  // to a 20-pixel change on the screen to the right.  // Indicator vindic originally at 108,400.    vpos = 108 + (thrustfac/0.0016)*20;    vindic.Set(vpos,400); } Function leftthruster { thrustfac = thrustfac   0.00008;  If(thrustfac<0.0016)  {   thrustfac = 0.0016;  }  //Move the thruster indicator indich.  //thrustfac = 0.0008  // corresponds  // to a 20-pixel change on the screen to the left.  // Indicator vindic originally at 108,400.    vpos = 108 + (thrustfac/0.0016)*20;    vindic.Set(vpos,400); } Function docollision {  aground.Show; } Function dochange { ocamera2.SetPosition(440,1000,3400); } Function dochange2 { ocamera2.SetPosition(2000,6000,3800); } Function dochange3 { ocamera2.SetPosition(500,6000,3600); } Function dochange4 { ocamera2.SetPosition(600,2500,2500); } 

That was our last program. You should experiment with it and see if you can make any improvements. Here are some suggestions: add some additional overhead views since the ship sometimes goes out of view when it is coming around the east end of the island, place additional mines to make the game more challenging or to prevent the ship from coming from the easier west end, and place a timer on the screen to make time a factor in keeping score.




Elementary Game Programming and Simulators Using Jamagic
Elementary Game Programming & Simulations Using Jamagic (Charles River Media Game Development)
ISBN: 1584502614
EAN: 2147483647
Year: 2002
Pages: 105
Authors: Sergio Perez

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net