For the purposes of this topic, we will assume that you have an Emitter (or other unit/s) on a blank map, which you wish to move around. Here's a guide to doing this with PRPL.
Assumed knowledge: Nothing that you won't be able to find in
the CRPL overview. (Well, you need to know how to work the map editor, but...)
First, we need to know exactly what we need to do. "Build a ladder to the moon" is simple as a sentence, but actually doing it is not.
We need to:
- Locate the Emitter(s) which we want to move.
- Find a point to move it to.
- Actually move it.
Fortunately, Virgil has done most of the hard work for us.
Step 1: Locating the Emitter.
This only needs to be done once, so we use a "once" block. We'll assume that the PRPL Core is directly over the Emitter(s).
Virgil has provided a function GetAllUnitsInRange, which is useful here. It takes four arguments. The first two put on the stack are the coordinates where the command starts looking. The next is the range. The final one relates to whether the we look for units in a circle or a square.
In most cases we will have other Emitters on the map. Where do we look for the one we want? For this script, we'll assume that the Core which is running this script will be sitting directly over the Emitter(s) which we want. The CurrentCoords command gets this location for us.
We only want Emitters which are on exactly the same spot, so the range is 0. We can make the code slightly more efficient by doing a box-search, which means that we supply 1 as the last argument to GetAllUnitsInRange.
Putting it all together, this is the first part of the script.
once
CurrentCoords 0 1 GetAllUnitsInRange ->Units
endonce
Step 2: Where do we want to move it?
There are various ways to talk about this section. I could blather on about parametric forms of oriented curves in 2-space, but I'll use the programmers form, or "easy way".
For a simple movement, we will move back and forth along a line. We'll move, say, up to 400 pixels left and right of where we started. We can do more complicated things, but this is a good start.
Moving based on the time is easy, but the time since when? Let's start our clock in that once block we defined already. It needs to tick every frame (i.e. increase by 0.01), so we'll do that too. While we're at it, we'll need to know where we started.
So, the next version of the code:
once
CurrentCoords 0 1 GetAllUnitsInRange ->Units
0 ->Clock
CurrentPixelCoords ->StartY ->StartX
endonce
<-Clock 0.01 add ->Clock
Now, we need to actually work out where the Emitter needs to go. We'll use the sine function for positioning, because it looks good and will work easily. It's easier to show visually what this will do, so bear with it for now.
A sine curve will be between 1 and -1, inclusive, depending on the number passed in. If we multiply this by 400 (our maximum pixel offset), we'll get the offset from the starting position which we want. We can the add this to the starting position, and we'll get the adjusted X position. We won't change Y at the moment.
So, our code becomes:
once
CurrentCoords 0 1 GetAllUnitsInRange ->Units
0 ->Clock
CurrentPixelCoords ->StartY ->StartX
endonce
<-Clock sin <-StartX add ->NewX
<-StartY ->NewY
<-Clock 0.01 add ->Clock
Step 3: Moving the Emitter(s)
Now we have the position, we need to move the Emitters. Fortunately, Virgil allows us to set the coordinates of units. We will move everything we found at the start, including Emitters, Omnis, caches, spawners, PRPL Cores, ...; the lot. To achieve this, we will use a do loop. We begin at position 0 and stop when we reach the end of the list. Note that <-List[<-Index] is the PRPL shorthand for accessing the element of List at position Index, and that I is the counter of the do loop. To better understand I, look at CRPL, which is better documented. We'll use the Accessor Notation (AcNot) form, because it is easier to read. Essentially , this means that instead of writing...
<-Unit CONST_UNITPIXELX <-NewX SetUnitAttribute
...we can write...
<-NewX ->Unit.UnitPixelX
Our latest script:
once
CurrentCoords 0 1 GetAllUnitsInRange ->Units
0 ->Clock
CurrentPixelCoords ->StartY ->StartX
endonce
<-Clock sin 400 mul <-StartX add ->NewX
<-StartY ->NewY
<-Units GetListCount 0 do
<-Units[I] ->UID
<-NewX ->UID.UnitPixelCoordX
<-NewY ->UID.UnitPixelCoordY
loop
<-Clock 0.01 add ->Clock
At this point we're probably done. Our unit moves, and goes where we wanted. But it's a little dull, and the script is hard-coded. What if we wanted to reuse it, slightly differently?
Step 4: Adding extras.
Many more things are possible, and here are some of the quickest:
- Making a circling unit.
- Making this adaptable.
Circling:
The sine function is inherently related to circles, and it has a similar and useful friend: the cosine. We can use this for a Y offset, and have our unit move in a circle. If we used sine for both X and Y, then we would end up offsetting by the same amount in both directions, and move in a diagonal line.
Replace this:
<-StartY ->NewY
With this:
<-Clock cos 400 mul <-StartY add ->NewY
Adaptability:
This script is all very well, but what if we want to go 800 pixels out? Or 1000? What if we want a number of different range sizes on the same map? Or to start the "clock" at 100 frames in? Or to move 10x as fast? We can use input variables. We set these whenever we run a new copy of the script, and they don't have to be the same every time.
$ClockStart:0
$Range:400
$Speed:0.01
once
CurrentCoords 0 1 GetAllUnitsInRange ->Units
<-ClockStart ->Clock
CurrentPixelCoords ->StartY ->StartX
endonce
<-Clock sin <-Range mul <-StartX add ->NewX
<-StartY ->NewY
<-Units GetListCount 0 do
<-Units[I] ->UID
<-NewX ->UID.UnitPixelCoordX
<-NewY ->UID.UnitPixelCoordY
loop
<-Clock <-Speed add ->Clock
At this point, I'm probably running close to the character limit for the forum, so I'll stop. The attached map has the "adaptable" form of the script, running with defaults. It doesn't have the "circling" addition, because I was testing.
Enjoy.