![]() |
[tutorial]How to script a movement system
2 Attachment(s)
Well, maybe this isn't quite a tutorial... or at least, I'll try to make it one!
I've posted a few movement systems before, but I have never really went in depth as to how I made them or the methods I use. I just provide a script... and that's not conductive to a learning environment. So here I will provide a script with lots of comments, and also go a little in-depth as to how I did things. This movement isn't quite as barebones as previous ones I have posted. It does have things like some basic modes(pulling/pushing,swimming...), and then some(functions for replacing ganis, applications of things I've learned over the years). First, I'm going to explain the method of wall collision I use. I use a simple dual-box check. This means I am checking two boxes in the direction the player is moving with onwall2's. This means it checks the whole distance of the player's size, and provides the necessary data for sliding(hitting one side of the player and not the other). Whenever I do this I sketch out a very easy diagram to understand what I am trying to accomplish. I create two checks. CheckOne, CheckTwo. Each denote a box check in front of the player. There are important things to know. 1) the player's "true" size is 3x3 tiles, starting at... of course, player.x and player.y 2) However, the player is graphically starting at player.x+.5 and player.y 3) The collision box of the player is at player.x+.5 and player.y+1 4) The 'center' of the player is player.x+1.5 and player.y+2. This is where things like tile detection are done. The above are all explained more easily in this picture, which also explains the box layout I use: http://forums.graalonline.com/forums...1&d=1268502068 The white transparent box is where we determine the player's collisions should be. The blue outline shows what Graal consider's the full player's size. Now each yellow and red box are checks that are determined in the script. However, only two are done at a time: only in the direction provided. This is not fully accurate, though... the boxes are not always 16x16 pixels(a single tile) in size. They stretch themselves outwards from the player to reflect the size of the player's speed. This means if the player is traveling to the left at 3tiles/frame, the box is going to be 3 tiles in width. This means it's checking for a wall between the new position and the last position(a simpler check means it will simply skip over walls found in-between). Here is the script I use to generate the positions of the two checks: PHP Code:
http://forums.graalonline.com/forums...1&d=1268502709 The red + shows where the checkx/y is, and the red box represents the onwall2 check. Thus, if going to the left, it needs to be displaced 3 tiles to account for the fact onwall2 will be 3 tiles in width. Anyways, for CheckTwo I start the check at the bottom right corner of the collision check. Then if the player is facing up or left, add a tile, or else subtract a tile(too keep it properly offset to represent the original sketch). Now onto the wall checks itself: PHP Code:
Another important thing to do is if you DO hit a wall, account for the space between the player and the wall. If you're moving at 5tiles/frame, that means you can hit a wall way farther than your player actually is. If you just stop the player, chances are they will not be flush against the wall. Thus what we do is we run a simple loop. Now normally this is not fun, but if you take your above wall checks and make a function that will check in a given direction at the given speed, this becomes A LOT easier. Why? Because you're getting rid of redundant checks that you'd normally have to do, like checking for walls, and simplifying them. So what you do is you create a loop that starts at 1/16(explained in the script in comments), runs the length of the player's speed(to account for every pixel between the player and the wall), and increments at... 1/16! Again, this is to account for every pixel. Once you hit a wall, stop the function and use the data found(the length the loop ran), and move the player that much. PHP Code:
PHP Code:
Sliding is quite simple with the two-box set-up. If only ONE box is hitting a wall, then you know your player should be sliding. What you do is simply use some determining factors to decide a direction for the player to slide... then do just what you would when moving the player. Check for a wall in the sliding direction, and if there is no wall slide them! I'll do my best to explain how I calculated the directions. PHP Code:
For the CheckTwo collission, it's somewhat similar except we are doing the opposite. If player's direction is LEFT or DOWN, subtract 1, otherwise add 1. I could have easily took all of this and made it into one big calculation, but for learning purposes I decided to keep it simple. Then you just take the slide direction and use it! PHP Code:
That pretty much accounts for movement and wall collision :D However, things only get more complicated from here. You have to do various other checks to maintain the 'mode' of the player. This essentially reflects what the player's character is doing. Whether they're idle, walking, swimming, pulling... |
This pretty much means what you have to do is make your script prioritize checks. Then you also have to make sure your script knows when and when it can not, apply these priorities. For example, you do a check for swimming(is the player in water?). Then you do grabbing. If you don't account for whether the player is swimming or not, your player could end up being able to grab things while in water. Hence, the swim check goes BEFORE the grab check. That way you can say, "is the player swimming? If not, proceed with grabbing! If they are, ignore even considering if the player can grab." This turns into a whooooole mess of moving things around so each check interacts with the other correctly.
There are ways to lessen the burden, though. A while ago I figured out a nifty way to set the player to idle. Normally I'd constantly set the player's mode back to IDLE at the beginning on the timeout. This worked... except eventually problems arose: movement was always taking gani priority over anything else. So if you had a command like, "/setani sit" that set your player's ani to sit while not on a chair, the script would immediately set the player back to idle. Also, if you have various modes of idle, then you had to constantly figure out which to set it back to(regular idle, carrying object idle, riding a horse idle...). So I created a function: ReturnIdle(). Whenever you want to return the player to idle mode, run it. I also made it public so that external scripts could call it(for example, I had a sign NPC that had to set the player back to idle. If I used setani("idle",null)... 1) it didn't account for the custom gani names the movement used, and 2) if the player was say... riding a horse, it would cause the player's horse to disappear until the player was done reading. I also allowed the function to account for a parameter: ReturnIdle(omitsettinggani?). If set to true, it wouldn't force set the player's ani. It would only set the player's MODE to idle. This is used in the actual movement script so loops are not being made(setani("idle",null) > setani("walk",null) > setani("idle",null) > setani("walk",null) > setani("idle",null) > setani("walk",null)). However, for external NPC's I can leave it as null, or false, and it will forcefully set the player's ani along with it(in case the player is frozen, and thus the movement script is not processing said data). Another cool thing I figured out is the ability to replicate the replaceani() function. How? When the script is created, establish the base gani array(player.ganis). This is an array that the script uses to set the player's gani's. It contains all the arrays that reflect the various modes of the player. This is not a static copy of gani's, however. We create a copy of that array as a static, and call it "default gani's". Then, we can take the player.gani array and change the gani's as we like. When we need to, we can revert back to the back-up default gani's. It's fairly simply :) PHP Code:
PHP Code:
One more thing... you'll notice I use vars like player.speed to store variables. This is strictly to simplify the script. However, they are not secure! You should instead use more secure methods to store important gameplay data like the player's speed(clientr.vars). |
PHP Code:
|
Very nice :o...
|
That box approach is nice, I think I will give that a try in the future.
Edit: I think when rounding the coordinates, though, you are better off using shared.roundto(), and rounding towards 1/16: PHP Code:
|
very nice, but you kinda lost me, i guess i didn't read it too well.
|
Quote:
|
wow- you're amazing! Good job, never once not been amazed by your work! ^^ ^^
|
most helpful NAT ever.
|
wow, this is an outstanding job.
Good Job Dusty! |
Good job.
|
Good work!
|
If you are in the GDT Dusty then ignore this, but if your not; This is the type of work they should be doing imo. Teaching others how to script instead of just making scripts
|
Quote:
|
I forget how I made mine, but this seems like a much better approach.
|
All times are GMT +2. The time now is 04:01 AM. |
Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2025, vBulletin Solutions Inc.
Copyright (C) 1998-2019 Toonslab All Rights Reserved.