# Basic Ship Simulator Program: Ship Simulator

On the CD    To get started, we first import the ship (called Bismark , from the Jamagic full-capability CD-ROM under ships, or from under the 3D category if you are using the Jamagic version that came with this book) and then scale it down so that its length corresponds to about 400 units on the screen. Since ships of this type are about 400 feet long, we will then have a very convenient way to measure speed later.

` oworld.Load("bismark"); myship= oworld.GetObject(oworld.GetNObjects()1); myship.Scale(0.008); myship.SetPosition(0,0,0); myship.TurnRight(Pi/2); `

How did we know a scaling factor of 0.008 would result in the ship measuring 400 screen units? By trial and error, of course! To figure this out, a small program was written to move the ship 400 units, using the command myship.move(400) . If it looked like the ship moved more than a ship s length, the scale of the ship was increased and the experiment was repeated. The scaling factor was adjusted repeatedly until the motion with 400 units corresponded to about one ship length.

After we have the ship scaled properly, we create the sea surface. This is done by creating a large plane that measures 10000 10000 and texturing it using the water picture (Program Files/Jamagic/Help/Tutorials/Simple/Final Project/Pictures). We tile this picture 20 times in each direction on the plane, and then we set it in motion to give the illusion of moving water using the AnimUV command:

` myplane = oworld.CreatePlane(10000,10000); 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 to give the illusion of moving // water. matplane.AnimUV(0,0.00025); // //Make it static to reduce the burden on computer, // and rotate it 90 degrees so it's down. myplane.SetStatic(); myplane.SetAngle(Pi/2.,0,0); `

Next we install a camera and give the screen a light blue background to give the illusion of sky.

` ocamera1 = New Camera(oworld,owindow1); ocamera1.SetBackgroundColor(GetRGB(0,150,200)); oworld.Optimize(ocamera1); `

We then set the Autorefresh mode to Off to allow for more frequent screen updates than the default setting of every 0.02 seconds if it s left On:

` owindow1.SetAutoRefresh(OFF); `

On the CD    Next we bring the sprites in for the instrument panel. panel is the instrument panel with no moving components ; wheel is the ship s steering wheel, which will rotate as the rudder buttons are clicked; hindic and vindic are the two handles that will be used for the throttle and bow thruster controls ”these handles will move as the control buttons activating throttle and bow thrusters are clicked; and thruster is the bow thruster background, which does not move (it should have been part of panel , but it was added after panel had already been drawn). These should be in the CD-ROM accompanying this book, and they were made using the Jamagic picture editor. Alternately, you can find them in the  ShipSimulator game on the book s CD-ROM. The code below shows how to import the sprites:

` //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); `

You will find that making decent graphics is probably the most time-consuming and difficult task in making a game. A great deal of time was used making and positioning the relatively simple graphics for this ship simulator instrument panel. How did we know, for example, to put the vindic sprite on the screen at (108, 400)? You have probably guessed by now that it was trial and error: A bit to the right, run the program to check it, a bit more to the left, a bit up you get the picture. After a while it can get very tedious .

All of these images were made with the hot spot at the default location ( upper-left corner), but sometimes the hot spot changes when you are importing files, especially if you changed the default location. If you get very strange locations on some of the panel components, either check the hot spot using the Jamagic picture editor or fiddle with the location of the sprites until everything lines up properly.

The ship is moved on the screen using the Move command, which moves an object in the direction it s pointing. You will recall that the first parameter inside the parentheses indicates the distance to be moved, while the second parameter, if present, indicates the time in seconds over which the motion is to take place. If the time parameter is omitted, the motion is immediate.

` myship.Move(movefac); `

This command is inside the game loop, so it is continually being performed. You may say that movefac is not a number; however, it represents a number that is given a value in the function speedup , which is activated whenever a button labeled Forward is clicked, as shown here:

` Function speedup  {   If(knots<30)   {    movefac = movefac + 0.01;   }  } `

In other words, whenever the Forward button is clicked, the value of movefac is increased by 0.01, which causes the ship to speed up. The speed is increased by 0.01 only if the speed is below 30 knots, which is the speed we are specifying as the maximum for this ship.

The speed of the ship increases slowly when the Forward button is clicked because 0.01 is a relatively small value (at least it is for the computer used to create this program, with a 1-GHz processor). Your computer may require a little tweaking of the 0.01 value to make the ship have a realistic acceleration. In this way, even clicking the Forward button as quickly as possible results in an acceleration that looks right.

To make the ship slow down and eventually move in reverse, the function speeddown is called whenever the Back button is clicked. In this case, 0.01 is subtracted from movefac with every click of the Back button. Please note that the function does not allow movefac to get smaller than “0.6, which corresponds to our maximum of 20 knots in reverse (that movefac of “0.6 corresponds to 20 knots was determined by sending the value of movefac to the screen while this program was being developed):

` Function speeddown  {    movefac = movefac  0.01;    If(movefac< 0.6)     {  movefac = 0.6;     } } `

In order to calculate the speed of the ship, we must be first be able to find the distance between two points. We can do this by using the Pythagorean theorem covered earlier. We measure the difference between the x coordinates ( deltax ) of the two points and the difference between their z coordinates ( deltaz ). Incidentally, delta is another Greek letter that is used frequently in mathematics. Typically it is used to denote a change or difference ”hence deltax is a good way to express the difference between the two x coordinates. We can then use the Pythagorean formula

` distance between two points = Sqrt(deltax* deltax + deltaz* deltaz), `

where Sqrt is the square root of the quantity inside the parentheses. Note that because the ship s altitude is not changing we have no need to worry about any deltay changes.

The speed of the ship is calculated by measuring the distance covered by the ship in one second. The code below for finding the ship s speed is placed inside the game loop, so the speed is being calculated every second. At the beginning of the program, before the code below and the game loop are reached, an initial time time1 is remembered by the computer using a command that says time1 = System.GetElapsedTime . When the code for finding the speed is reached, time2 is found, and if more than 1000 milliseconds (one second) have elapsed since time1 was noted, the speed is calculated. Otherwise the speed calculation loop is skipped until the elapsed time is at least 1000 milliseconds.

The distance covered by the ship is calculated by first finding the x and z coordinates of the ship at the beginning of the program and before the game loop is reached. These coordinates are called xpos1 and zpos1 . After one second has elapsed, the x and z coordinates are found by using the myship.GetX and myship.GetX commands. It s then possible to find the distance covered in one second using the formula given earlier for the distance between two points.

You probably remembered how carefully we scaled the ship at the beginning of the program so that one screen unit was about one foot . As a result, we also know that if the distance covered by the ship is 400 screen units in 40 seconds, the ship has moved 400 feet in 40 seconds, or 10 feet every second. We know that there are about 6000 feet per nautical mile and 3600 seconds per hour, so we can convert easily from feet per second to nautical miles per hour (knots) by multiplying the feet per second by 3600/6000, or 36/60.

The following code gives the full procedure for calculating the ship s speed. Also note that the values of xpos1 , time1 , and zpos1 are recalculated at the end of the speed-determining loop.

` time2 = System.GetElapsedTime;  dift = Abs(time2  time1); // Loop for finding the speed   If (dift > 1000)      {       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;      } `

The ship is turned right or left by the following command inside the game loop:

` myship.TurnRight(turnfac); `

turnfac is similar to movefac except that it is used to turn the ship rather than move it forward or backward. Positive values turn the ship right and negative values make it turn left. movefac is initialized to zero at the beginning of the program, and it changes whenever the program user clicks buttons marked Left Rudder or Right Rudder. Clicking Right Rudder calls function rightrudder in the code below, with each mouse click increasing turnfac by 0.0001. Again, through trial and error, this value makes the turning rate feel satisfactory within the limits dictated by inertia.

The rudder angle cannot go beyond a certain value, or else the ship will spin about its y-axis faster than a clock s second hand. For this reason, the value of rudderangle , which is used to calculate turnfac , is limited to 0.001 (determined by trial and error as an acceptable value).

And now we must explain something that will seem a little complicated at first. Recall that if the ship is moving slowly, it should be doing very little turning (it s really the rotation of the ship about its center that we are controlling with the TurnRight command); indeed, there must be no rotation at all if the rudder is activated when the ship is standing still, since forward motion is required for the rudders to work. Likewise, the faster the ship is moving forward, the greater the rotation rate of the ship. In other words, the rate of rotation of the ship depends on both the rudder angle and the speed of the ship. In mathematics-speak, we say that the rate of rotation is a function of both the ship s speed and rudder angle ”now that you can speak like a mathematician , go and impress your friends !

So, we need some type of equation that allows the ship to rotate more (the equation should make a bigger value of turnfac in the TurnRight command mentioned earlier) when the rudder angle is bigger and when the ship is moving faster. The easiest type of equation to try first is an equation with powers of one in the variables rudderangle and knots :

` turnfac = rudderangle * knots * K , `

where K is some number that we need to figure out by trial and error. If we experiment with this equation by trying different values of K, and we find that we get realistic-looking turns at all different rudder angles, then we have been successful. If we fail, as evidenced by unrealistic behavior of the ship, then we must look at other more complicated forms of equations (like the type with powers greater than one, those with the form turnfac = rudderangle * rudderangle * knots * knots * K). Fortunately for us, the linear form with K = 0.1 seems to give satisfactory results. Here is the resulting equation:

` turnfac = rudderangle * knots * 0.1 `

By simply looking at it, you can probably see that this equation has many of our requirements. For example, if the ship s speed is zero ( knots = 0), then turnfac must be zero, and there will be no rotation. If the rudder angle is zero, then turnfac must again be zero, also resulting in no rotation. And finally, as rudderangel or knots increase or decrease, so must turnfac .

One of the last jobs to be performed is associated with the rudders ”moving the steering wheel sprite as the rudder angle changes. By experimentation, we know that the maximum value of rudderangle had to be 0.001 radians. We want this to correspond to a reasonable amount of real-world rudder angle and a reasonable amount of turning on the ship s wheel. In other words, when rudderangle is a maximum of 0.001 radians, the ship s wheel must have turned through about 60 degrees, and the rudder must have moved through about 30 degrees.

The last nasty piece of business to take care of, in as far as rudders go, is that they must turn the ship in an opposite direction when moving backward than they do when they are going forward. This is remedied very easily by multiplying by “1, which gives the opposite sign of the original number. So if movefac is less than zero, (indicating that the ship is moving backward) then the value of turnfac is changed from a positive to negative.

Check out this code. There is a lot to think about...

` 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    // the conversion    // factor is 30,000    rudderconvert = rudderangle* 30000;    If(movefac<0)     {          turnfac = turnfac*1;        }      // We want the wheel to move about twice      // as much as the      //rudder so the helmsman has      // a good feel for the wheel         mywheel.SetAngle(rudderconvert*2.0,FALSE);  } `

The code for turning left is very similar, but the signs are different:

` 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);  } `

On the instrument panel (Figure 6.2), we also wish to indicate how much throttle is being applied and how much the bow thrusters are being used. As mentioned earlier, on a real ship, the throttle is a handle that is pushed forward or back, so we will move the sprite called hsprite back and forth as the Forward or Back buttons are clicked. In a real ship, the bow thruster is a handle that is moved to the left or right, so we will move vsprite back and forth as the bow thruster L or R buttons are clicked.

Figure 6.2: The instrument panel. The throttle indicator is at the right and the bow thruster is near the center.

The more the Forward or Back buttons are pushed, the more the indicators have to move, so we need an equation that moves the indicators accordingly . By experimenting with the command that positions the throttle indicator indich on the screen, we know that indich must be at position (180, 380) when Forward or Back have not been clicked (in other words, when the throttle is at neutral). We also know from trial and error that we want the indicator indich to move up a maximum of 20 screen units, corresponding to the maximum speed of the boat and movefac = 1.2 (recall that moving up on the screen means a decreasing y coordinate).

The following code moves indich up or down depending on the value of movefac , starting at (180,380) when movefac is zero, decreasing linearly to (180,360) when movefac is 1.2, and increasing linearly to (180,400) when movefac is “1.2:

hpos = 380 “ (movefac/1.2)*20;

` indich.Set(180,hpos); `

The code for moving the bow thruster indicator is very similar, except that the indicator is moved right or left instead of up or down. The indicator vindic is set to (108,400) when the thrusters are not on, and it moves 20 pixels right or left at the maximum bow thruster setting. The maximum bow thruster setting is 0.0008:

` vpos = 108 + (thrustfac/0.0008)*20;    vindic.Set(vpos,400); `

OK, you are ready to create a new program in Jamagic. Just type in the code on the next page. Don t forget to import the panel , indich , vindic , thruster , wheel , and water pictures from the CD-ROM. Experiment with moving the ship around the screen using the different commands, and make sure you understand the instruments ”you will need these skills for the next project!

### Basic Ship Simulator Code: ShipSimulator

` //Ship simulator program. //This will include the following: // Sprite of ship's console // Clickable controls on the console: click wheel to right //and it turns, turning ship, // click thrusters and they move, moving the ship. Click the //throttle and it moves. // Right and left bow thrusters // Ship experiences inertia // Controls show, water depth, speed, // throttle setting, thruster //setting // Data for write-up. Ship is scaled so 400 // pixels = 400 //feet, ship assumed 400 feet long // All ahead speed = 30 knots // owindow1 = New Window("Ship // Simulator",640,480,Window.STANDARD); //Window maintenance owindow1.SetAutoRefresh(OFF); 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); //Put in sea surface. myplane = oworld.CreatePlane(10000,10000); 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); //Cameras ocamera1 = New Camera(oworld,owindow1); ocamera1.SetBackgroundColor(GetRGB(0,150,200)); ocamera1.SetPosition(0,200,1000); ocamera1.TurnRight(Pi); ocamera1.Walk(); oworld.Optimize(ocamera1); //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; // 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); mytext3.SetBackColor(GetRGB(0,105,50)); mytext3.SetColor(GetRGB(255,255,255)); ocamera1.ActivateRender(); //time1 = System.GetElapsedTime; time1 = System.GetElapsedTime; xpos1 = myship.GetX; zpos1 = myship.GetZ; //instruments mypanel = New Sprite(0,350,"panel"); mypanel.SetScale(0.75,0.75); //myvsprite.SetTransparent(ON); //The following one is in degrees, the true refers to //antialiasing. //myvsprite.SetAngle(90,FALSE); //myvsprite.Set(580,60); 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); //Game loop While(1) { mytext3.SetText("Depth: "+ depth); // window cleaning owindow1.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;       } }  Function speedup  {   If(knots<30)   {    movefac = movefac + 0.01;    turnfac = rudderangle* knots/10.;   }     If(movefac <0)    {     turnfac = turnfac* 1;    }    //Move the speed indicator indich. movefac = + 1.2    //corresponds    // to a 20 pixel change on the screen.    //movefac = 0 corresponds to y = 380    hpos = 380  (movefac/1.2)*20;    indich.Set(180,hpos);  } Function speeddown  {    movefac = movefac  0.01;    If(movefac< 0.6)     {         movefac = 0.6;        }    turnfac = rudderangle* knots/10.;    If(movefac <0)    {     turnfac = turnfac*1.;    }    hpos = 380  (movefac/1.2)*20;    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); } `

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