Top Down Ship (Boat) Movement

Started by
7 comments, last by Weberet 7 years, 10 months ago

I am currently working on a game in Yoyo game's: Game Maker software. The game involves top down ships ( large watercraft ) moving. I'm running into one roadblock so far and that is that I am unsure of how to script movement for a top down vehicle such as this. I'm looking to create movement similar to that in the game "Sunless Sea." (I read somewhere that this would be considered "nonholonomic" movement.)

I am trying to avoid using the game maker physics functions and am planning on eventually having this be controlled by a game-pad (probably an ps3 or ps4 controller emulating a xbox controller.)

Has anybody written any scripts like this for ship movement?

For those unfamiliar with GML I have a super basic script I found for basic top down movement that I am currently using just to test ship interactions with other objects, mainly to show a little bit of the languages syntax and basic functions:


/*Temporary Movement Code*/
if (keyboard_check(vk_up) && keyboard_check(vk_right))
    {x+=2; y-=2;}

    else if (keyboard_check(vk_up) && keyboard_check(vk_left))
    {x-=2; y-=2;}

    else if (keyboard_check(vk_down) && keyboard_check(vk_right))
    {x+=2; y+=2;}

    else if (keyboard_check(vk_down) && keyboard_check(vk_left))
    {x-=2; y+=2;}

    else if keyboard_check(vk_up)
    {x+=0; y-=4;}

    else if keyboard_check(vk_left)
    {x-=4; y-=0;}

    else if keyboard_check(vk_down)
    {x+=0; y+=4;}

    else if keyboard_check(vk_right)
    {x+=4; y-=0;}
// End

Any help, or helpful resources would be much appreciated! Thanks!

:D

Advertisement

You don't need physics to make this movement. I'm not sure what the word "nonholomonic" is either. The video just makes it look like you are moving like Asteroids, where you can spin in any direction, but only move forward(maybe backward) relative to what direction you are turned towards.

You need to make a decision on exactly how you want to do things. If you want it to be exactly how I see it in the video, you will want to calculate movement yourself, or you can let GMStudio do it for you. If you don't expect the ship to keep moving fast while you stop pushing the move button, you could easily just use the built-in movement that GMStudio provides. There are 4 that probably really interest you.

image_angle-this one controls the rotation of your ship, but only the visual part.

direction-this one is the direction, but only for movement purposes. The reason these are separate is because you could move in one direction while facing another if you wanted, like you see in some top-down shooters.

speed-this one is how many pixels per step the thing is moving, and it moves in the direction indicated in the direction variable.

friction-this one is exactly what it sounds like. It slows down your object, nice and smooth, without you having to do anything with it.

So, in your code in your step event, you manually check your input, or you respond to key events. You would increase/decrease your image_angle and direction. I would think you want those to be the same in your game, as you are moving the same way you are facing. You would also detect a thrust key the same way(either step event or key event). And you would set the speed to however fast you want it. If you want to have the smooth stopping, you would put a number on friction to make that happen automatically. Then once you get the idea working, you tweak it how you want to make the speed right, and the friction, and how fast the turning happens.

I don't know how far you are with learning code, but eventually you will want to learn about player custom input, where the player chooses the keys the player wants to use. I have a package on the market for it, as it is a big deal to me. I'm not advertising it though, rather making sure you understand about it. If you choose to respond to input events, it is going to be hard later to convert to those gamepads that you were talking about, and even more so to let the player choose inputs. So, if possible, maybe it is a good idea for you to just do it all in the step event, as that gets you closer to that point, and easier to make changes once you learn how to implement gamepads and custom input(or buy a package that helps with that.)



Yeah, at a basic level, the Left/Right keys (or horizontal axis) changes the direction, the Up/Down (or vertical axis) change the speed, and the boat always moves in the direction that it's facing.

This is all pseudo code: (if you're going to use axis sticks, it becomes less binary, you want to get a value between 0 and 1 for each stick and do currentAngle+= turnSpeed * horizontalAxis for example) I'm making assumptions that GameMaker has access to these kind of things, my experience is more with Unity.



if(left)
currentAngle -= turnSpeed;
if(right)
currentAngle += turnSpeed;

if(forward)
currentSpeed += forwardSpeed
if(back)
currentSpeed -= brakingSpeed;

this.rotation = currentAngle;
this.position += this.forwardvector * currentspeed;


I'm making assumptions that GameMaker has access to these kind of things, my experience is more with Unity.

GameMaker has access to these kinds of things...but it takes a little more code to get to it because it doesn't by default have the input system that Unity has. You CAN easily enough simply call a single function checking the value of an axis on a joystick/gamepad. I myself have set up a system for GMStudio that works like Unity's internal input system, cInput, ReWired, etc... where I just call a single function to check the value of the axis and I don't care what input is actually driving it.



Thanks for the responses everyone!

I did a little bit more research based on kburkhart84 description of "Asteroids" like movement. (After recognizing that would probably be an easier term to look for.) And came back to post my results only to find that ferrous had already written a similar script!

So far the below script is yielding good results and it will work for my testing purposes, however I'd like to refine it more. The biggest change I think I want to implement is to make its so that the faster the ship moves the more it is able to turn and making it so that the ship can not turn when it is stationary.

Would the best way to do this is create a variable that checks for how fast the boat is going currently to its top speed, create a ratio, and then apply that ratio to the ships turn radius?


/*Ship Variables:*/
tradius = .5 //How quickly ship can turn
shipmpeed = 0; // Minimum Speed
shiptspeed = 5; // Maximum Speed
acceleration = .1; //Acceleration
drag = .1; //Friction, slows object down

/*Ship Movement*/
if keyboard_check(vk_left)
{
     direction += tradius;
}
if keyboard_check(vk_right)
{
     direction -= tradius;
}

if keyboard_check(vk_up)
{
    if (speed < shiptspeed){
    speed += acceleration;
    }
    else{speed = shiptspeed};
}
else if keyboard_check(vk_down)
{
    speed -= acceleration;
}
else{
//No keys being pressed;
/*Detect speed and decelerate towards 0*/
if (speed > 0){speed -= drag;}
else if (speed < 0) {speed  += drag;}
}
image_angle = direction;

Actually think I found the results I want by implementing what I mentioned above! Now I just need to adapt my script for game pads which I'll have to do when I have one with me.

Current Script:


/*Movement Code*/
    
if (captained){

/*Turn Radius Limit Script, Based off ship speed:*/
turnradratio = speed/shiptspeed;


if keyboard_check(vk_left)
{
     direction += tradius*turnradratio;
}
if keyboard_check(vk_right)
{
     direction -= tradius*turnradratio;
}

if keyboard_check(vk_up)
{
    if (speed < shiptspeed){
    speed += acceleration;
    }
    else{speed = shiptspeed};
}
else if keyboard_check(vk_down)
{
    speed -= acceleration;
}
else{
//No keys being pressed;
/*Detect speed and decelerate towards 0*/
if (speed > 0){speed -= drag;}
else if (speed < 0) {speed  += drag;}
}
image_angle = direction;

    }

Any suggestions on improvements?

Mostly minor nits, your shiptspeed is the max speed, correct? You're going to possibly exceed it for a frame with the way the code is written. Ie, if current speed is 9, max speed is 10, and acceleration is 3, you'd hit 12 until you hit the loop again, which might look strange when next frame it's brought back down to 10.

I'd modify your code to be something like:

speed = min(speed + acceleration, shiptspeed);

Your drag also has the potential to vacillate back and forth. Example: Speed is 0.5, drag is 1. Every frame, speed will go from 0.5, to -0.5, but never get to 0. I suggest moving to a multiplier, like speed = speed * .9f, and then maybe toss a if (speed < SomeSmallAmount) speed = 0.

EDIT: You may also end up revisiting turn ratio, you might want to play with it to find values you like. Right now it's linear, but you may want to square it, or even go crazy and map it to a curve -- that's my Unity experience talking, as it's really easy to make and map things to Animation Curves. If I recall correctly, the old X-wing vs Tie-Fighter games, the sweet spot for turn radius was actually at 1/3 the max speed. Anything higher or lower and the ship would turn slower.

I've had a lot of games where I tried implementing a 'vehicle'-type of movement.

My approach to designing this is not to consider the end-position, but rather these 2 key elements:

- Direction (angle)

- Speed

Your input should always modify one of the two (either a speed increase/decrease, otherwise known as acceleration/deceleration, or an angle modification by a specific amount of degrees).

As a general rule, I find radians to be more efficient than actual degrees for this as most of the APIs have worked with had much simpler ways of handling this, but I'm not foreign to building such a system from scratch on HTML5 notably, so everything is possible, if you remember your trigonometry!

So you should essentially "listen" for these input, knowing that with every game tick, you're going to modify the position of the ship or vehicle based on what you currently know (direction/speed) regardless of everything else.

In an environment where you don't control the ship via WASD, and would rather prefer using waypoints (click where you need to go, such as in an RTS), the same heuristics will apply, the only difference is that the "AI" will determine its own rotation and speed modifications.

This can be achieved by using a targetRotation variable which stores the direction you're trying to go, and determining what is the fastest rotation to achieve this (not as straightforward as I make it sound as all angle representations have a 'dead point' where it takes a little know-how to find the easiest path when wrapping over 0 or the likes).

As for suggestions on improving your current script, I would question the capitalization of your variables, but they appear to be consistent at least. But capitalizing all but the first word is good practice for readability (I almost missed the 'T' in shipTSpeed).

Thanks for the help and suggestions everyone, its been been very helpful!

This topic is closed to new replies.

Advertisement