Jump to content

Yandu's Arrow Tutorial


Skillsbo

Recommended Posts

What?

This tutorial will be outlining the process and steps of writing my Yandu's Arrow script to hopefully teach others about what is possible in Skript with few lines of code.

 

(If you don't know who Yandu is, he's some blue bad/good guy in the Guardians of The Galaxy movies who uses a magic arrow and it flies through a handful of enemies by itself)

 

d7f711f2197bb4d8744fd94f2b93abf9.gif.9e1bbf299ae9fc40d4c3b8ea124fa88d.gif

 

So that's basically what we will be creating today

 

The Start

So where would I start? I would personally observe how the arrow acts in the movies, you can see that it flies through an enemy once to get the ones behind/next to and each one (mostly) never gets hit more than once.

So how could we emulate this behavior? You might think that just doing them based on the distance to you would be the answer, and you would be right, ish, it would get the job done but it won't not look as clean (see below)

 

sortbydistance.thumb.png.056160455bbe594fba70594c1383978d.png

 

So if that's not quite what we want, what's the better option? What if we still sort by distance but not by the player? What if we first get the closest zombie to the player, and then get the closest one to that one and then rinse and repeat? That would produce a much better looking result (see below)

sortbyzombiedistance.thumb.png.4db86191ff951b2434b465a4944eac3c.png

 

Now that we know how we should sort the zombies we can move onto actually writing the script.

 

Writing The Script

So we can begin writing the script now, we can start by adding the main event, for me I'll just make it activate when you right click with an arrow with a special name and it will just spawn an arrow with no gravity (prevent it from falling)

 

on right click with arrow:
    if name of player's tool = "&9Yandu's arrow":
        spawn 1 arrow at player
        set gravity of last spawned arrow to false

 

So now, how do we get the zombie that's closest to the player? Sadly `all x in radius y of z` does not return the entities in order of distance we will have to make our own part that does that. This is a simple sorting thing, it loops through all the (zombies in this case) in radius 20 and compares the distance to the other ones and the resulting closest zombie is in `{_z}`

 

set {_d} to 21
loop all zombies in radius 20 of player:
    if distance between loop-zombie and player < {_d}:
        set {_z} to loop-zombie
        set {_d} to distance between loop-zombie and player

 

So now we need to sort all the zombies in the order we discussed earlier, one way to do this is to use recursion as it greatly reduces the amount of lines we need (recursion basically means defining something in terms of itself)

So we put this in our main event (keep in mind that the `{_t::*}` variable has not been set and we want it that way)

 

set {_z::*} to closestZombie({_z}, {_t::*})

 

And now we need to define the `closestZombie` function

function closestZombie(a: entity, t: entities) :: entities:
    set {_d} to 100
    loop all zombies in radius 20 of {_a}:
        if {_t::*} does not contain loop-zombie:
            if distance between loop-zombie and {_a} < {_d}:
                set {_z} to loop-zombie
                set {_d} to distance between loop-zombie and {_a}
    add {_z} to {_t::*}
    add {_z} to {_z::*}
    set {_c::*} to closestZombie({_z}, {_t::*})
    add {_c::*} to {_z::*}
    return {_z::*}

 

Basically the function gets the closest zombie to the current zombie and then gets the closest zombie to that one and so on and so forth while also keeping track of which zombie has been already "taken".

 

So once we have that defined we can move on the final part (the visuals)

loop size of {_z::*} times:
    set {_i} to loop-value-1 + 1
    if {_i} <= size of {_z::*}:
        loop 5 times:
            set {_l} to linearBezier((({_z::%loop-value-1%} ~ vector(0, 1.5, 0)) and ({_z::%{_i}%} ~ vector(0, 1.5, 0))), loop-value-2 / 5)
            play 5 of flame at {_l} offset by vector(0, 0, 0) with extra .05
            set {_v} to vector between {_z::%loop-value-1%} and {_z::%{_i}%}
            set vector length of {_v} to .01
            teleport last spawned arrow to {_l}
            set velocity of last spawned arrow to {_v}
            wait 1 tick
        kill {_z::%loop-value-1%}
    kill {_z::%loop-value-1%}
kill last spawned arrow

 

The code may look daunting at first but its pretty simple, we loop through the list of zombies (that are now in order thanks to our epic sorting function) then we check to make sure we are not at the second to last zombie (to make sure the visuals play out nicely between the zombies) and then we use another function called `linearBezier` which basically just means a line from A to B at the position t (time) that will make us a line to where we can teleport the arrow to and create a few particles to add a little pizzazz, we use a vector here just to make the arrow face in the right direction, its nothing too fancy.

function definition for `linearBezier` below

 

function linearBezier(l: locations, t: number) :: location:
    set {_x} to (1 - {_t}) * x-loc of {_l::1} + {_t} * x-loc of {_l::2}
    set {_y} to (1 - {_t}) * y-loc of {_l::1} + {_t} * y-loc of {_l::2}
    set {_z} to (1 - {_t}) * z-loc of {_l::1} + {_t} * z-loc of {_l::2}
    return location({_x}, {_y}, {_z}, world of {_l::1})

 

The End

Slapping it all together and we get

function linearBezier(l: locations, t: number) :: location:
    set {_x} to (1 - {_t}) * x-loc of {_l::1} + {_t} * x-loc of {_l::2}
    set {_y} to (1 - {_t}) * y-loc of {_l::1} + {_t} * y-loc of {_l::2}
    set {_z} to (1 - {_t}) * z-loc of {_l::1} + {_t} * z-loc of {_l::2}
    return location({_x}, {_y}, {_z}, world of {_l::1})

function closestEntity(a: entity, t: entities) :: entities:
    set {_d} to 100
    loop all zombies in radius 20 of {_a}:
        if {_t::*} does not contain loop-zombie:
            if distance between loop-zombie and {_a} < {_d}:
                set {_z} to loop-zombie
                set {_d} to distance between loop-zombie and {_a}
    add {_z} to {_t::*}
    add {_z} to {_z::*}
    set {_c::*} to closestEntity({_z}, {_t::*})
    add {_c::*} to {_z::*}
    return {_z::*}

on right click with arrow:
    if name of player's tool = "&9Yandu's arrow":
        spawn 1 arrow at player
        set gravity of last spawned arrow to false
        set {_d} to 21
        loop all zombies in radius 20 of player:
            if distance between loop-zombie and player < {_d}:
                set {_z} to loop-zombie
                set {_d} to distance between loop-zombie and player

        set {_z::*} to closestEntity({_z}, {_t::*})
        loop size of {_z::*} times:
            set {_i} to loop-value-1 + 1
            if {_i} <= size of {_z::*}:
                loop 5 times:
                    set {_l} to linearBezier((({_z::%loop-value-1%} ~ vector(0, 1.5, 0)) and ({_z::%{_i}%} ~ vector(0, 1.5, 0))), loop-value-2 / 5)
                    play 5 of flame at {_l} offset by vector(0, 0, 0) with extra .05
                    set {_v} to vector between {_z::%loop-value-1%} and {_z::%{_i}%}
                    set vector length of {_v} to .01
                    teleport last spawned arrow to {_l}
                    set velocity of last spawned arrow to {_v}
                    wait 1 tick
                kill {_z::%loop-value-1%}
            kill {_z::%loop-value-1%}
        kill last spawned arrow

 

I might do more of these later one with other cool concepts (maybe laser beams will be next)

  • Like 2
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...